<commit_before>package quiclatest

import (
	"encoding/binary"

	"github.com/ami-GS/gQUIC/latest/qtype"
)

type PacketParser func(data []byte) (p Packet, length int, err error)

var LongHeaderPacketParserMap = map[LongHeaderPacketType]PacketParser{
	InitialPacketType:          ParseInitialPacket,
	RetryPacketType:            ParseRetryPacket,
	HandshakePacketType:        ParseHandshakePacket,
	ZeroRTTProtectedPacketType: ParseZeroRTTProtectedPacket,
}

type Packet interface {
	GetWire() ([]byte, error)
	GetHeader() PacketHeader
	SetHeader(ph PacketHeader)
	SetFrames(fs []Frame)
}

func ParsePacket(data []byte) (packet Packet, idx int, err error) {
	version := binary.BigEndian.Uint32(data[1:5])
	if version == 0 {
		return ParseVersionNegotiationPacket(data)
	}
	header, idx, err := PacketHeaderParserMap[PacketHeaderType(data[0]&0x80)](data) // ParseHeader
	if err != nil {
		return nil, 0, err
	}
	lh, ok := header.(LongHeader)
	var idxTmp int
	if ok {
		// long header
		packet, idxTmp, err = LongHeaderPacketParserMap[LongHeaderPacketType(lh.PacketType&0x7f)](data)
	} else {
		// short header
		packet, idxTmp, err = ParseOneRTTProtectedPacket(data)
	}
	packet.SetHeader(header)
	idx += idxTmp
	var fs []Frame
	fs, idxTmp, err = ParseFrames(data[idx:])
	if err != nil {
		return nil, 0, err
	}
	packet.SetFrames(fs)
	return packet, idx + idxTmp, err
}

type BasePacket struct {
	Header PacketHeader
	Frames []Frame
}

func (bp *BasePacket) GetHeader() PacketHeader {
	return bp.Header
}
func (bp *BasePacket) SetHeader(h PacketHeader) {
	bp.Header = h
}

func (bp *BasePacket) SetFrames(fs []Frame) {
	bp.Frames = fs
}

func (bp *BasePacket) GetWire() (wire []byte, err error) {
	hWire, err := bp.Header.GetWire()
	if err != nil {
		return nil, err
	}
	fsWire, err := GetFrameWires(bp.Frames)
	if err != nil {
		return nil, err
	}
	// TODO: protect for short header?
	return append(hWire, fsWire...), nil
}

// long header with type of 0x7F
type InitialPacket struct {
	*BasePacket
}

func NewInitialPacket(version qtype.Version, destConnID, srcConnID qtype.ConnectionID, packetNumber qtype.PacketNumber, payloadLen uint64) *InitialPacket {
	return &InitialPacket{
		BasePacket: &BasePacket{
			Header: NewLongHeader(InitialPacketType, version, destConnID, srcConnID, packetNumber, payloadLen),
		},
	}

}

func ParseInitialPacket(data []byte) (p Packet, length int, err error) {
	return
}

// long header with type of 0x7E
type RetryPacket struct {
	*BasePacket
}

func NewRetryPacket(version qtype.Version, destConnID, srcConnID qtype.ConnectionID, packetNumber qtype.PacketNumber, payloadLen uint64) *RetryPacket {
	return &RetryPacket{
		BasePacket: &BasePacket{
			Header: NewLongHeader(RetryPacketType, version, destConnID, srcConnID, packetNumber, payloadLen),
		},
	}
}

func ParseRetryPacket(data []byte) (p Packet, length int, err error) {
	return
}

// long header with type of 0x7D
type HandshakePacket struct {
	*BasePacket
}

func NewHandshakePacket(version qtype.Version, destConnID, srcConnID qtype.ConnectionID, packetNumber qtype.PacketNumber, payloadLen uint64) *HandshakePacket {
	return &HandshakePacket{
		BasePacket: &BasePacket{
			Header: NewLongHeader(HandshakePacketType, version, destConnID, srcConnID, packetNumber, payloadLen),
		},
	}
}

func ParseHandshakePacket(data []byte) (p Packet, length int, err error) {
	return
}

// long header with 0-RTT (type:0x7C)
// short header with 1-RTT
// interface?
type ProtectedPacket interface {
	GetRTT() int
}
type ZeroRTTProtectedPacket struct {
	*BasePacket
}

func NewZeroRTTProtectedPacket(version qtype.Version, destConnID, srcConnID qtype.ConnectionID, packetNumber qtype.PacketNumber, payloadLen uint64) *ZeroRTTProtectedPacket {
	return &ZeroRTTProtectedPacket{
		BasePacket: &BasePacket{
			Header: NewLongHeader(ZeroRTTProtectedPacketType, version, destConnID, srcConnID, packetNumber, payloadLen),
		},
	}
}

func ParseZeroRTTProtectedPacket(data []byte) (p Packet, length int, err error) {
	return
}

type OneRTTProtectedPacket struct {
	*BasePacket
}

func NewOneRTTProtectedPacket(packetType byte, destConnID qtype.ConnectionID, packetNumber qtype.PacketNumber) *OneRTTProtectedPacket {
	return &OneRTTProtectedPacket{
		BasePacket: &BasePacket{
			Header: NewShortHeader(packetType, destConnID, packetNumber),
		},
	}
}

func ParseOneRTTProtectedPacket(data []byte) (p Packet, length int, err error) {
	return
}

/*
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+
   |1|  Unused (7) |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          Version (32)                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |DCIL(4)|SCIL(4)|
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |               Destination Connection ID (0/32..144)         ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                 Source Connection ID (0/32..144)            ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Supported Version 1 (32)                 ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                   [Supported Version 2 (32)]                ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                                  ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                   [Supported Version N (32)]                ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

// Version negotiation doesn't use long header, but have similar form
type VersionNegotiationPacket struct {
	Version           qtype.Version
	DCIL              byte
	SCIL              byte
	DestConnID        qtype.ConnectionID
	SrcConnID         qtype.ConnectionID
	SupportedVersions []qtype.Version
}

func NewVersionNegotiationPacket(destConnID, srcConnID qtype.ConnectionID, supportedVersions []qtype.Version) *VersionNegotiationPacket {
	dcil := 0
	if destConnID != nil {
		dcil = len(destConnID) - 3
	}
	scil := 0
	if srcConnID != nil {
		scil = len(srcConnID) - 3
	}

	return &VersionNegotiationPacket{
		Version:           0,
		DCIL:              byte(dcil),
		SCIL:              byte(scil),
		DestConnID:        destConnID,
		SrcConnID:         srcConnID,
		SupportedVersions: supportedVersions,
	}
}

func ParseVersionNegotiationPacket(data []byte) (Packet, int, error) {
	idx := 0
	packet := NewVersionNegotiationPacket(nil, nil, nil)
	if data[0]&0x80 != 0x80 {
		//TODO: error
	}
	idx++
	packet.Version = qtype.Version(binary.BigEndian.Uint32(data[idx:]))
	if packet.Version != 0 {
		// must be zero
		// TODO: error
	}
	idx += 4
	packet.DCIL = data[idx] >> 4
	packet.SCIL = data[idx] & 0x0f
	idx++
	if packet.DCIL != 0 {
		dcil := int(packet.DCIL + 3)
		qtype.ReadConnectionID(data[idx:], dcil)
		idx += dcil
	}
	if packet.SCIL != 0 {
		scil := int(packet.SCIL + 3)
		qtype.ReadConnectionID(data[idx:], scil)
		idx += scil
	}
	numVersions := (len(data) - idx) / 4
	packet.SupportedVersions = make([]qtype.Version, numVersions)
	for i := 0; i < numVersions; i++ {
		packet.SupportedVersions[i] = qtype.Version(binary.BigEndian.Uint32(data[idx:]))
		idx += 4
	}
	return packet, idx, nil
}

func (p *VersionNegotiationPacket) GetWire() (wire []byte, err error) {
	wire = make([]byte, int(6+p.DCIL+p.SCIL)+len(p.SupportedVersions)*4)
	wire[0] = 0x80
	binary.BigEndian.PutUint32(wire[1:], uint32(p.Version))
	wire[5] = (p.DCIL << 4) | p.SCIL
	idx := 5
	if p.DCIL != 0 {
		for i := 0; i < int(p.DCIL+3); i++ {
			wire[idx+i] = p.DestConnID[i]
		}
		idx += int(p.DCIL + 3)
	}
	if p.SCIL != 0 {
		for i := 0; i < int(p.SCIL+3); i++ {
			wire[idx+i] = p.SrcConnID[i]
		}
		idx += int(p.SCIL + 3)
	}

	for i, version := range p.SupportedVersions {
		binary.BigEndian.PutUint32(wire[idx+i*4:], uint32(version))
	}
	// TODO: VersionNegotiationPacket fills MTU
	return
}

func (p *VersionNegotiationPacket) GetHeader() PacketHeader {
	return nil
}
func (p *VersionNegotiationPacket) SetHeader(h PacketHeader) {
	// no op?
}
func (p *VersionNegotiationPacket) SetFrames(fs []Frame) {
	// no op?
}
func (p *VersionNegotiationPacket) GetConnectionIDPair() (qtype.ConnectionID, qtype.ConnectionID) {
	return p.SrcConnID, p.DestConnID
}
<commit_msg>implement GetFrames<commit_after>package quiclatest

import (
	"encoding/binary"

	"github.com/ami-GS/gQUIC/latest/qtype"
)

type PacketParser func(data []byte) (p Packet, length int, err error)

var LongHeaderPacketParserMap = map[LongHeaderPacketType]PacketParser{
	InitialPacketType:          ParseInitialPacket,
	RetryPacketType:            ParseRetryPacket,
	HandshakePacketType:        ParseHandshakePacket,
	ZeroRTTProtectedPacketType: ParseZeroRTTProtectedPacket,
}

type Packet interface {
	GetWire() ([]byte, error)
	GetHeader() PacketHeader
	SetHeader(ph PacketHeader)
	GetFrames() []Frame
	SetFrames(fs []Frame)
}

func ParsePacket(data []byte) (packet Packet, idx int, err error) {
	version := binary.BigEndian.Uint32(data[1:5])
	if version == 0 {
		return ParseVersionNegotiationPacket(data)
	}
	header, idx, err := PacketHeaderParserMap[PacketHeaderType(data[0]&0x80)](data) // ParseHeader
	if err != nil {
		return nil, 0, err
	}
	lh, ok := header.(LongHeader)
	var idxTmp int
	if ok {
		// long header
		packet, idxTmp, err = LongHeaderPacketParserMap[LongHeaderPacketType(lh.PacketType&0x7f)](data)
	} else {
		// short header
		packet, idxTmp, err = ParseOneRTTProtectedPacket(data)
	}
	packet.SetHeader(header)
	idx += idxTmp
	var fs []Frame
	fs, idxTmp, err = ParseFrames(data[idx:])
	if err != nil {
		return nil, 0, err
	}
	packet.SetFrames(fs)
	return packet, idx + idxTmp, err
}

type BasePacket struct {
	Header PacketHeader
	Frames []Frame
}

func (bp *BasePacket) GetHeader() PacketHeader {
	return bp.Header
}
func (bp *BasePacket) SetHeader(h PacketHeader) {
	bp.Header = h
}

func (bp *BasePacket) SetFrames(fs []Frame) {
	bp.Frames = fs
}

func (bp *BasePacket) GetFrames() []Frame {
	return bp.Frames
}

func (bp *BasePacket) GetWire() (wire []byte, err error) {
	hWire, err := bp.Header.GetWire()
	if err != nil {
		return nil, err
	}
	fsWire, err := GetFrameWires(bp.Frames)
	if err != nil {
		return nil, err
	}
	// TODO: protect for short header?
	return append(hWire, fsWire...), nil
}

// long header with type of 0x7F
type InitialPacket struct {
	*BasePacket
}

func NewInitialPacket(version qtype.Version, destConnID, srcConnID qtype.ConnectionID, packetNumber qtype.PacketNumber, payloadLen uint64) *InitialPacket {
	return &InitialPacket{
		BasePacket: &BasePacket{
			Header: NewLongHeader(InitialPacketType, version, destConnID, srcConnID, packetNumber, payloadLen),
		},
	}

}

func ParseInitialPacket(data []byte) (p Packet, length int, err error) {
	return
}

// long header with type of 0x7E
type RetryPacket struct {
	*BasePacket
}

func NewRetryPacket(version qtype.Version, destConnID, srcConnID qtype.ConnectionID, packetNumber qtype.PacketNumber, payloadLen uint64) *RetryPacket {
	return &RetryPacket{
		BasePacket: &BasePacket{
			Header: NewLongHeader(RetryPacketType, version, destConnID, srcConnID, packetNumber, payloadLen),
		},
	}
}

func ParseRetryPacket(data []byte) (p Packet, length int, err error) {
	return
}

// long header with type of 0x7D
type HandshakePacket struct {
	*BasePacket
}

func NewHandshakePacket(version qtype.Version, destConnID, srcConnID qtype.ConnectionID, packetNumber qtype.PacketNumber, payloadLen uint64) *HandshakePacket {
	return &HandshakePacket{
		BasePacket: &BasePacket{
			Header: NewLongHeader(HandshakePacketType, version, destConnID, srcConnID, packetNumber, payloadLen),
		},
	}
}

func ParseHandshakePacket(data []byte) (p Packet, length int, err error) {
	return
}

// long header with 0-RTT (type:0x7C)
// short header with 1-RTT
// interface?
type ProtectedPacket interface {
	GetRTT() int
}
type ZeroRTTProtectedPacket struct {
	*BasePacket
}

func NewZeroRTTProtectedPacket(version qtype.Version, destConnID, srcConnID qtype.ConnectionID, packetNumber qtype.PacketNumber, payloadLen uint64) *ZeroRTTProtectedPacket {
	return &ZeroRTTProtectedPacket{
		BasePacket: &BasePacket{
			Header: NewLongHeader(ZeroRTTProtectedPacketType, version, destConnID, srcConnID, packetNumber, payloadLen),
		},
	}
}

func ParseZeroRTTProtectedPacket(data []byte) (p Packet, length int, err error) {
	return
}

type OneRTTProtectedPacket struct {
	*BasePacket
}

func NewOneRTTProtectedPacket(packetType byte, destConnID qtype.ConnectionID, packetNumber qtype.PacketNumber) *OneRTTProtectedPacket {
	return &OneRTTProtectedPacket{
		BasePacket: &BasePacket{
			Header: NewShortHeader(packetType, destConnID, packetNumber),
		},
	}
}

func ParseOneRTTProtectedPacket(data []byte) (p Packet, length int, err error) {
	return
}

/*
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+
   |1|  Unused (7) |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                          Version (32)                         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |DCIL(4)|SCIL(4)|
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |               Destination Connection ID (0/32..144)         ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                 Source Connection ID (0/32..144)            ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                    Supported Version 1 (32)                 ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                   [Supported Version 2 (32)]                ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
                                  ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                   [Supported Version N (32)]                ...
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/

// Version negotiation doesn't use long header, but have similar form
type VersionNegotiationPacket struct {
	Version           qtype.Version
	DCIL              byte
	SCIL              byte
	DestConnID        qtype.ConnectionID
	SrcConnID         qtype.ConnectionID
	SupportedVersions []qtype.Version
}

func NewVersionNegotiationPacket(destConnID, srcConnID qtype.ConnectionID, supportedVersions []qtype.Version) *VersionNegotiationPacket {
	dcil := 0
	if destConnID != nil {
		dcil = len(destConnID) - 3
	}
	scil := 0
	if srcConnID != nil {
		scil = len(srcConnID) - 3
	}

	return &VersionNegotiationPacket{
		Version:           0,
		DCIL:              byte(dcil),
		SCIL:              byte(scil),
		DestConnID:        destConnID,
		SrcConnID:         srcConnID,
		SupportedVersions: supportedVersions,
	}
}

func ParseVersionNegotiationPacket(data []byte) (Packet, int, error) {
	idx := 0
	packet := NewVersionNegotiationPacket(nil, nil, nil)
	if data[0]&0x80 != 0x80 {
		//TODO: error
	}
	idx++
	packet.Version = qtype.Version(binary.BigEndian.Uint32(data[idx:]))
	if packet.Version != 0 {
		// must be zero
		// TODO: error
	}
	idx += 4
	packet.DCIL = data[idx] >> 4
	packet.SCIL = data[idx] & 0x0f
	idx++
	if packet.DCIL != 0 {
		dcil := int(packet.DCIL + 3)
		qtype.ReadConnectionID(data[idx:], dcil)
		idx += dcil
	}
	if packet.SCIL != 0 {
		scil := int(packet.SCIL + 3)
		qtype.ReadConnectionID(data[idx:], scil)
		idx += scil
	}
	numVersions := (len(data) - idx) / 4
	packet.SupportedVersions = make([]qtype.Version, numVersions)
	for i := 0; i < numVersions; i++ {
		packet.SupportedVersions[i] = qtype.Version(binary.BigEndian.Uint32(data[idx:]))
		idx += 4
	}
	return packet, idx, nil
}

func (p *VersionNegotiationPacket) GetWire() (wire []byte, err error) {
	wire = make([]byte, int(6+p.DCIL+p.SCIL)+len(p.SupportedVersions)*4)
	wire[0] = 0x80
	binary.BigEndian.PutUint32(wire[1:], uint32(p.Version))
	wire[5] = (p.DCIL << 4) | p.SCIL
	idx := 5
	if p.DCIL != 0 {
		for i := 0; i < int(p.DCIL+3); i++ {
			wire[idx+i] = p.DestConnID[i]
		}
		idx += int(p.DCIL + 3)
	}
	if p.SCIL != 0 {
		for i := 0; i < int(p.SCIL+3); i++ {
			wire[idx+i] = p.SrcConnID[i]
		}
		idx += int(p.SCIL + 3)
	}

	for i, version := range p.SupportedVersions {
		binary.BigEndian.PutUint32(wire[idx+i*4:], uint32(version))
	}
	// TODO: VersionNegotiationPacket fills MTU
	return
}

func (p *VersionNegotiationPacket) GetHeader() PacketHeader {
	return nil
}
func (p *VersionNegotiationPacket) SetHeader(h PacketHeader) {
	// no op?
}
func (p *VersionNegotiationPacket) SetFrames(fs []Frame) {
	// no op?
}
func (p *VersionNegotiationPacket) GetFrames() []Frame {
	return nil
}
func (p *VersionNegotiationPacket) GetConnectionIDPair() (qtype.ConnectionID, qtype.ConnectionID) {
	return p.SrcConnID, p.DestConnID
}
<|endoftext|>


<commit_before>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package xurls

import (
	"regexp"
	"testing"
)

type testCase struct {
	in   string
	want interface{}
}

func doTest(t *testing.T, name string, re *regexp.Regexp, cases []testCase) {
	for _, c := range cases {
		got := re.FindString(c.in)
		want, _ := c.want.(string)
		if got != want {
			t.Errorf(`%s.FindString("%s") got "%s", want "%s"`, name, c.in, got, want)
		}
	}
}

var constantTestCases = []testCase{
	{``, nil},
	{` `, nil},
	{`:`, nil},
	{`::`, nil},
	{`:::`, nil},
	{`::::`, nil},
	{`.`, nil},
	{`..`, nil},
	{`...`, nil},
	{`1.1`, nil},
	{`.1.`, nil},
	{`1.1.1`, nil},
	{`1:1`, nil},
	{`:1:`, nil},
	{`1:1:1`, nil},
	{`://`, nil},
	{`foo`, nil},
	{`foo:`, nil},
	{`mailto:`, nil},
	{`randomxmpp:foo`, nil},
	{`foo://`, nil},
	{`http://`, nil},
	{`:foo`, nil},
	{`://foo`, nil},
	{`foo:bar`, nil},
	{`zzz.`, nil},
	{`.zzz`, nil},
	{`zzz.zzz`, nil},
	{`/some/path`, nil},
	{`localhost`, nil},
	{`com`, nil},
	{`.com`, nil},
	{`http`, nil},

	{`http://foo`, `http://foo`},
	{`http://FOO`, `http://FOO`},
	{`http://FAÀ`, `http://FAÀ`},
	{`https://localhost`, `https://localhost`},
	{`git+https://localhost`, `git+https://localhost`},
	{`foo.bar://localhost`, `foo.bar://localhost`},
	{`foo-bar://localhost`, `foo-bar://localhost`},
	{`mailto:foo`, `mailto:foo`},
	{`MAILTO:foo`, `MAILTO:foo`},
	{`sms:123`, `sms:123`},
	{`xmpp:foo@bar`, `xmpp:foo@bar`},
	{`bitcoin:Addr23?amount=1&message=foo`, `bitcoin:Addr23?amount=1&message=foo`},
	{`http://foo.com`, `http://foo.com`},
	{`http://foo.co.uk`, `http://foo.co.uk`},
	{`http://foo.random`, `http://foo.random`},
	{` http://foo.com/bar `, `http://foo.com/bar`},
	{` http://foo.com/bar more`, `http://foo.com/bar`},
	{`<http://foo.com/bar>`, `http://foo.com/bar`},
	{`<http://foo.com/bar>more`, `http://foo.com/bar`},
	{`.http://foo.com/bar.`, `http://foo.com/bar`},
	{`.http://foo.com/bar.more`, `http://foo.com/bar.more`},
	{`,http://foo.com/bar,`, `http://foo.com/bar`},
	{`,http://foo.com/bar,more`, `http://foo.com/bar,more`},
	{`(http://foo.com/bar)`, `http://foo.com/bar`},
	{`"http://foo.com/bar'`, `http://foo.com/bar`},
	{`"http://foo.com/bar'more`, `http://foo.com/bar'more`},
	{`"http://foo.com/bar"`, `http://foo.com/bar`},
	{`http://a.b/a.,:;-+_()?@|&=#$~!*%'a`, `http://a.b/a.,:;-+_()?@|&=#$~!*%'a`},
	{`http://foo.com/path_(more)`, `http://foo.com/path_(more)`},
	{`(http://foo.com/path_(more))`, `http://foo.com/path_(more)`},
	{`http://foo.com/path_(even)-(more)`, `http://foo.com/path_(even)-(more)`},
	{`http://foo.com/path_(even)(more)`, `http://foo.com/path_(even)(more)`},
	{`http://foo.com/path_(even_(nested))`, `http://foo.com/path_(even_(nested))`},
	{`(http://foo.com/path_(even_(nested)))`, `http://foo.com/path_(even_(nested))`},
	{`http://foo.com/path#fragment`, `http://foo.com/path#fragment`},
	{`http://test.foo.com/`, `http://test.foo.com/`},
	{`http://foo.com/path`, `http://foo.com/path`},
	{`http://foo.com:8080/path`, `http://foo.com:8080/path`},
	{`http://1.1.1.1/path`, `http://1.1.1.1/path`},
	{`http://1080::8:800:200c:417a/path`, `http://1080::8:800:200c:417a/path`},
	{`http://中国.中国/foo中国`, `http://中国.中国/foo中国`},
	{`http://xn-foo.xn--p1acf/path`, `http://xn-foo.xn--p1acf/path`},
	{`http://✪foo.bar/pa✪th`, `http://✪foo.bar/pa✪th`},
	{`✪http://✪foo.bar/pa✪th✪`, `http://✪foo.bar/pa✪th`},
	{`what is http://foo.com?`, `http://foo.com`},
	{`the http://foo.com!`, `http://foo.com`},
	{`https://test.foo.bar/path?a=b`, `https://test.foo.bar/path?a=b`},
	{`ftp://user@foo.bar`, `ftp://user@foo.bar`},
}

func TestRegexes(t *testing.T) {
	doTest(t, "Relaxed", Relaxed, constantTestCases)
	doTest(t, "Strict", Strict, constantTestCases)
	doTest(t, "Relaxed", Relaxed, []testCase{
		{`foo.a`, nil},
		{`foo.com`, `foo.com`},
		{`foo.com bar.com`, `foo.com`},
		{`foo.com-foo`, `foo.com`},
		{`foo.company`, `foo.company`},
		{`foo.comrandom`, nil},
		{`foo.onion`, `foo.onion`},
		{`foo.i2p`, `foo.i2p`},
		{`中国.中国`, `中国.中国`},
		{`中国.中国/foo中国`, `中国.中国/foo中国`},
		{`foo.com/`, `foo.com/`},
		{`1.1.1.1`, `1.1.1.1`},
		{`10.50.23.250`, `10.50.23.250`},
		{`121.1.1.1`, `121.1.1.1`},
		{`255.1.1.1`, `255.1.1.1`},
		{`300.1.1.1`, nil},
		{`1.1.1.300`, nil},
		{`1080:0:0:0:8:800:200C:4171`, `1080:0:0:0:8:800:200C:4171`},
		{`3ffe:2a00:100:7031::1`, `3ffe:2a00:100:7031::1`},
		{`1080::8:800:200c:417a`, `1080::8:800:200c:417a`},
		{`foo.com:8080`, `foo.com:8080`},
		{`foo.com:8080/path`, `foo.com:8080/path`},
		{`test.foo.com`, `test.foo.com`},
		{`test.foo.com/path`, `test.foo.com/path`},
		{`test.foo.com/path/more/`, `test.foo.com/path/more/`},
		{`TEST.FOO.COM/PATH`, `TEST.FOO.COM/PATH`},
		{`TEST.FÓO.COM/PÁTH`, `TEST.FÓO.COM/PÁTH`},
		{`foo.com/path_(more)`, `foo.com/path_(more)`},
		{`foo.com/path_(even)_(more)`, `foo.com/path_(even)_(more)`},
		{`foo.com/path_(more)/more`, `foo.com/path_(more)/more`},
		{`foo.com/path_(more)/end)`, `foo.com/path_(more)/end`},
		{`www.foo.com`, `www.foo.com`},
		{` foo.com/bar `, `foo.com/bar`},
		{` foo.com/bar more`, `foo.com/bar`},
		{`<foo.com/bar>`, `foo.com/bar`},
		{`<foo.com/bar>more`, `foo.com/bar`},
		{`,foo.com/bar.`, `foo.com/bar`},
		{`,foo.com/bar.more`, `foo.com/bar.more`},
		{`,foo.com/bar,`, `foo.com/bar`},
		{`,foo.com/bar,more`, `foo.com/bar,more`},
		{`(foo.com/bar)`, `foo.com/bar`},
		{`"foo.com/bar'`, `foo.com/bar`},
		{`"foo.com/bar'more`, `foo.com/bar'more`},
		{`"foo.com/bar"`, `foo.com/bar`},
		{`what is foo.com?`, `foo.com`},
		{`the foo.com!`, `foo.com`},

		{`foo@bar`, nil},
		{`foo@bar.a`, nil},
		{`foo@bar.com`, `foo@bar.com`},
		{`foo@bar.com bar@bar.com`, `foo@bar.com`},
		{`foo@bar.onion`, `foo@bar.onion`},
		{`foo@中国.中国`, `foo@中国.中国`},
		{`foo@test.bar.com`, `foo@test.bar.com`},
		{`FOO@TEST.BAR.COM`, `FOO@TEST.BAR.COM`},
		{`foo@bar.com/path`, `foo@bar.com`},
		{`foo+test@bar.com`, `foo+test@bar.com`},
		{`foo+._%-@bar.com`, `foo+._%-@bar.com`},
	})
	doTest(t, "Strict", Strict, []testCase{
		{`http:// foo.com`, nil},
		{`http:// foo.com`, nil},
		{`foo.a`, nil},
		{`foo.com`, nil},
		{`foo.com/`, nil},
		{`1.1.1.1`, nil},
		{`3ffe:2a00:100:7031::1`, nil},
		{`test.foo.com:8080/path`, nil},
		{`foo@bar.com`, nil},
	})
}

func TestStrictMatchingError(t *testing.T) {
	for _, c := range []struct {
		exp     string
		wantErr bool
	}{
		{`http://`, false},
		{`https?://`, false},
		{`http://|mailto:`, false},
		{`http://(`, true},
	} {
		_, err := StrictMatching(c.exp)
		if c.wantErr && err == nil {
			t.Errorf(`StrictMatching("%s") did not error as expected`, c.exp)
		} else if !c.wantErr && err != nil {
			t.Errorf(`StrictMatching("%s") unexpectedly errored`, c.exp)
		}
	}
}

func TestStrictMatching(t *testing.T) {
	strictMatching, _ := StrictMatching("http://|ftps?://|mailto:")
	doTest(t, "StrictMatching", strictMatching, []testCase{
		{`foo.com`, nil},
		{`foo@bar.com`, nil},
		{`http://foo`, `http://foo`},
		{`https://foo`, nil},
		{`ftp://foo`, `ftp://foo`},
		{`ftps://foo`, `ftps://foo`},
		{`mailto:foo`, `mailto:foo`},
		{`sms:123`, nil},
	})
}

func bench(b *testing.B, re *regexp.Regexp, str string) {
	for i := 0; i < b.N; i++ {
		re.FindAllString(str, -1)
	}
}

func BenchmarkStrictEmpty(b *testing.B) {
	bench(b, Strict, "foo")
}

func BenchmarkStrictSingle(b *testing.B) {
	bench(b, Strict, "http://foo.foo foo.com")
}

func BenchmarkStrictMany(b *testing.B) {
	bench(b, Strict, ` foo bar http://foo.foo
	foo.com bitcoin:address ftp://
	xmpp:foo@bar.com`)
}

func BenchmarkRelaxedEmpty(b *testing.B) {
	bench(b, Relaxed, "foo")
}

func BenchmarkRelaxedSingle(b *testing.B) {
	bench(b, Relaxed, "http://foo.foo foo.com")
}

func BenchmarkRelaxedMany(b *testing.B) {
	bench(b, Relaxed, ` foo bar http://foo.foo
	foo.com bitcoin:address ftp://
	xmpp:foo@bar.com`)
}
<commit_msg>Add test for a realistic link with @ at its end<commit_after>/* Copyright (c) 2015, Daniel Martí <mvdan@mvdan.cc> */
/* See LICENSE for licensing information */

package xurls

import (
	"regexp"
	"testing"
)

type testCase struct {
	in   string
	want interface{}
}

func doTest(t *testing.T, name string, re *regexp.Regexp, cases []testCase) {
	for _, c := range cases {
		got := re.FindString(c.in)
		want, _ := c.want.(string)
		if got != want {
			t.Errorf(`%s.FindString("%s") got "%s", want "%s"`, name, c.in, got, want)
		}
	}
}

var constantTestCases = []testCase{
	{``, nil},
	{` `, nil},
	{`:`, nil},
	{`::`, nil},
	{`:::`, nil},
	{`::::`, nil},
	{`.`, nil},
	{`..`, nil},
	{`...`, nil},
	{`1.1`, nil},
	{`.1.`, nil},
	{`1.1.1`, nil},
	{`1:1`, nil},
	{`:1:`, nil},
	{`1:1:1`, nil},
	{`://`, nil},
	{`foo`, nil},
	{`foo:`, nil},
	{`mailto:`, nil},
	{`randomxmpp:foo`, nil},
	{`foo://`, nil},
	{`http://`, nil},
	{`:foo`, nil},
	{`://foo`, nil},
	{`foo:bar`, nil},
	{`zzz.`, nil},
	{`.zzz`, nil},
	{`zzz.zzz`, nil},
	{`/some/path`, nil},
	{`localhost`, nil},
	{`com`, nil},
	{`.com`, nil},
	{`http`, nil},

	{`http://foo`, `http://foo`},
	{`http://FOO`, `http://FOO`},
	{`http://FAÀ`, `http://FAÀ`},
	{`https://localhost`, `https://localhost`},
	{`git+https://localhost`, `git+https://localhost`},
	{`foo.bar://localhost`, `foo.bar://localhost`},
	{`foo-bar://localhost`, `foo-bar://localhost`},
	{`mailto:foo`, `mailto:foo`},
	{`MAILTO:foo`, `MAILTO:foo`},
	{`sms:123`, `sms:123`},
	{`xmpp:foo@bar`, `xmpp:foo@bar`},
	{`bitcoin:Addr23?amount=1&message=foo`, `bitcoin:Addr23?amount=1&message=foo`},
	{`http://foo.com`, `http://foo.com`},
	{`http://foo.co.uk`, `http://foo.co.uk`},
	{`http://foo.random`, `http://foo.random`},
	{` http://foo.com/bar `, `http://foo.com/bar`},
	{` http://foo.com/bar more`, `http://foo.com/bar`},
	{`<http://foo.com/bar>`, `http://foo.com/bar`},
	{`<http://foo.com/bar>more`, `http://foo.com/bar`},
	{`.http://foo.com/bar.`, `http://foo.com/bar`},
	{`.http://foo.com/bar.more`, `http://foo.com/bar.more`},
	{`,http://foo.com/bar,`, `http://foo.com/bar`},
	{`,http://foo.com/bar,more`, `http://foo.com/bar,more`},
	{`(http://foo.com/bar)`, `http://foo.com/bar`},
	{`"http://foo.com/bar'`, `http://foo.com/bar`},
	{`"http://foo.com/bar'more`, `http://foo.com/bar'more`},
	{`"http://foo.com/bar"`, `http://foo.com/bar`},
	{`http://a.b/a.,:;-+_()?@|&=#$~!*%'a`, `http://a.b/a.,:;-+_()?@|&=#$~!*%'a`},
	{`http://foo.com/path_(more)`, `http://foo.com/path_(more)`},
	{`(http://foo.com/path_(more))`, `http://foo.com/path_(more)`},
	{`http://foo.com/path_(even)-(more)`, `http://foo.com/path_(even)-(more)`},
	{`http://foo.com/path_(even)(more)`, `http://foo.com/path_(even)(more)`},
	{`http://foo.com/path_(even_(nested))`, `http://foo.com/path_(even_(nested))`},
	{`(http://foo.com/path_(even_(nested)))`, `http://foo.com/path_(even_(nested))`},
	{`http://foo.com/path#fragment`, `http://foo.com/path#fragment`},
	{`http://test.foo.com/`, `http://test.foo.com/`},
	{`http://foo.com/path`, `http://foo.com/path`},
	{`http://foo.com:8080/path`, `http://foo.com:8080/path`},
	{`http://1.1.1.1/path`, `http://1.1.1.1/path`},
	{`http://1080::8:800:200c:417a/path`, `http://1080::8:800:200c:417a/path`},
	{`http://中国.中国/foo中国`, `http://中国.中国/foo中国`},
	{`http://xn-foo.xn--p1acf/path`, `http://xn-foo.xn--p1acf/path`},
	{`http://✪foo.bar/pa✪th`, `http://✪foo.bar/pa✪th`},
	{`✪http://✪foo.bar/pa✪th✪`, `http://✪foo.bar/pa✪th`},
	{`what is http://foo.com?`, `http://foo.com`},
	{`the http://foo.com!`, `http://foo.com`},
	{`https://test.foo.bar/path?a=b`, `https://test.foo.bar/path?a=b`},
	{`ftp://user@foo.bar`, `ftp://user@foo.bar`},
	{`http://foo.com/@"style="color:red"onmouseover=func()`, `http://foo.com/`},
}

func TestRegexes(t *testing.T) {
	doTest(t, "Relaxed", Relaxed, constantTestCases)
	doTest(t, "Strict", Strict, constantTestCases)
	doTest(t, "Relaxed", Relaxed, []testCase{
		{`foo.a`, nil},
		{`foo.com`, `foo.com`},
		{`foo.com bar.com`, `foo.com`},
		{`foo.com-foo`, `foo.com`},
		{`foo.company`, `foo.company`},
		{`foo.comrandom`, nil},
		{`foo.onion`, `foo.onion`},
		{`foo.i2p`, `foo.i2p`},
		{`中国.中国`, `中国.中国`},
		{`中国.中国/foo中国`, `中国.中国/foo中国`},
		{`foo.com/`, `foo.com/`},
		{`1.1.1.1`, `1.1.1.1`},
		{`10.50.23.250`, `10.50.23.250`},
		{`121.1.1.1`, `121.1.1.1`},
		{`255.1.1.1`, `255.1.1.1`},
		{`300.1.1.1`, nil},
		{`1.1.1.300`, nil},
		{`1080:0:0:0:8:800:200C:4171`, `1080:0:0:0:8:800:200C:4171`},
		{`3ffe:2a00:100:7031::1`, `3ffe:2a00:100:7031::1`},
		{`1080::8:800:200c:417a`, `1080::8:800:200c:417a`},
		{`foo.com:8080`, `foo.com:8080`},
		{`foo.com:8080/path`, `foo.com:8080/path`},
		{`test.foo.com`, `test.foo.com`},
		{`test.foo.com/path`, `test.foo.com/path`},
		{`test.foo.com/path/more/`, `test.foo.com/path/more/`},
		{`TEST.FOO.COM/PATH`, `TEST.FOO.COM/PATH`},
		{`TEST.FÓO.COM/PÁTH`, `TEST.FÓO.COM/PÁTH`},
		{`foo.com/path_(more)`, `foo.com/path_(more)`},
		{`foo.com/path_(even)_(more)`, `foo.com/path_(even)_(more)`},
		{`foo.com/path_(more)/more`, `foo.com/path_(more)/more`},
		{`foo.com/path_(more)/end)`, `foo.com/path_(more)/end`},
		{`www.foo.com`, `www.foo.com`},
		{` foo.com/bar `, `foo.com/bar`},
		{` foo.com/bar more`, `foo.com/bar`},
		{`<foo.com/bar>`, `foo.com/bar`},
		{`<foo.com/bar>more`, `foo.com/bar`},
		{`,foo.com/bar.`, `foo.com/bar`},
		{`,foo.com/bar.more`, `foo.com/bar.more`},
		{`,foo.com/bar,`, `foo.com/bar`},
		{`,foo.com/bar,more`, `foo.com/bar,more`},
		{`(foo.com/bar)`, `foo.com/bar`},
		{`"foo.com/bar'`, `foo.com/bar`},
		{`"foo.com/bar'more`, `foo.com/bar'more`},
		{`"foo.com/bar"`, `foo.com/bar`},
		{`what is foo.com?`, `foo.com`},
		{`the foo.com!`, `foo.com`},

		{`foo@bar`, nil},
		{`foo@bar.a`, nil},
		{`foo@bar.com`, `foo@bar.com`},
		{`foo@bar.com bar@bar.com`, `foo@bar.com`},
		{`foo@bar.onion`, `foo@bar.onion`},
		{`foo@中国.中国`, `foo@中国.中国`},
		{`foo@test.bar.com`, `foo@test.bar.com`},
		{`FOO@TEST.BAR.COM`, `FOO@TEST.BAR.COM`},
		{`foo@bar.com/path`, `foo@bar.com`},
		{`foo+test@bar.com`, `foo+test@bar.com`},
		{`foo+._%-@bar.com`, `foo+._%-@bar.com`},
	})
	doTest(t, "Strict", Strict, []testCase{
		{`http:// foo.com`, nil},
		{`http:// foo.com`, nil},
		{`foo.a`, nil},
		{`foo.com`, nil},
		{`foo.com/`, nil},
		{`1.1.1.1`, nil},
		{`3ffe:2a00:100:7031::1`, nil},
		{`test.foo.com:8080/path`, nil},
		{`foo@bar.com`, nil},
	})
}

func TestStrictMatchingError(t *testing.T) {
	for _, c := range []struct {
		exp     string
		wantErr bool
	}{
		{`http://`, false},
		{`https?://`, false},
		{`http://|mailto:`, false},
		{`http://(`, true},
	} {
		_, err := StrictMatching(c.exp)
		if c.wantErr && err == nil {
			t.Errorf(`StrictMatching("%s") did not error as expected`, c.exp)
		} else if !c.wantErr && err != nil {
			t.Errorf(`StrictMatching("%s") unexpectedly errored`, c.exp)
		}
	}
}

func TestStrictMatching(t *testing.T) {
	strictMatching, _ := StrictMatching("http://|ftps?://|mailto:")
	doTest(t, "StrictMatching", strictMatching, []testCase{
		{`foo.com`, nil},
		{`foo@bar.com`, nil},
		{`http://foo`, `http://foo`},
		{`https://foo`, nil},
		{`ftp://foo`, `ftp://foo`},
		{`ftps://foo`, `ftps://foo`},
		{`mailto:foo`, `mailto:foo`},
		{`sms:123`, nil},
	})
}

func bench(b *testing.B, re *regexp.Regexp, str string) {
	for i := 0; i < b.N; i++ {
		re.FindAllString(str, -1)
	}
}

func BenchmarkStrictEmpty(b *testing.B) {
	bench(b, Strict, "foo")
}

func BenchmarkStrictSingle(b *testing.B) {
	bench(b, Strict, "http://foo.foo foo.com")
}

func BenchmarkStrictMany(b *testing.B) {
	bench(b, Strict, ` foo bar http://foo.foo
	foo.com bitcoin:address ftp://
	xmpp:foo@bar.com`)
}

func BenchmarkRelaxedEmpty(b *testing.B) {
	bench(b, Relaxed, "foo")
}

func BenchmarkRelaxedSingle(b *testing.B) {
	bench(b, Relaxed, "http://foo.foo foo.com")
}

func BenchmarkRelaxedMany(b *testing.B) {
	bench(b, Relaxed, ` foo bar http://foo.foo
	foo.com bitcoin:address ftp://
	xmpp:foo@bar.com`)
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/gode"
	"github.com/dickeyxxx/golock"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

func init() {
	node.Registry = "https://d3nfsbmspisrno.cloudfront.net"
	node.NodeVersion = "2.0.2"
	node.NpmVersion = "2.9.0"
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	if !node.IsSetup() {
		LogIfError(golock.Lock(updateLockPath))
		defer golock.Unlock(updateLockPath)
		if node.IsSetup() {
			return
		}
		Debugln("setting up iojs", node.NodeVersion)
		ExitIfError(node.Setup())
		clearOldNodeInstalls()
		Debugln("done setting up iojs")
	}
}

func clearOldNodeInstalls() {
	files, _ := ioutil.ReadDir(AppDir)
	for _, f := range files {
		name := f.Name()
		if name != node.NodeBase() && strings.HasPrefix(name, "iojs-v") {
			LogIfError(os.RemoveAll(filepath.Join(AppDir, name)))
		}
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := node.InstallPackage(name)
		ExitIfError(err)
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(node.RemovePackage(name))
		}
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("symlinked", plugin.Name)
		Err("Updating plugin cache... ")
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := node.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		if err != nil {
			panic(err)
		}
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + module
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(module)
		ctx.Version = ctx.Version + " " + module + " iojs-v" + node.NodeVersion
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var topic = '%s';
		var command = '%s';
		var ctx = %s;
		var logPath = %s;
		process.chdir(ctx.cwd);
		function repair (name) {
			console.error('Attempting to repair ' + name + '...');
			require('child_process')
			.spawnSync('heroku', ['plugins:install', name],
			{stdio: [0,1,2]});
			console.error('Repair complete. Try running your command again.');
		}
		if (!ctx.dev) {
			process.on('uncaughtException', function (err) {
				console.error(' !   Error in ' + moduleName + ':')
				if (err.message) {
					console.error(' !   ' + err.message);
					if (err.message.indexOf('Cannot find module') != -1) {
						repair(moduleName);
						process.exit(1);
					}
				} else {
					console.error(' !   ' + err);
				}
				if (err.stack) {
					var fs = require('fs');
					var log = function (line) {
						var d = new Date().toISOString()
						.replace(/T/, ' ')
						.replace(/-/g, '/')
						.replace(/\..+/, '');
						fs.appendFileSync(logPath, d + ' ' + line + '\n');
					}
					log('Error during ' + topic + ':' + command);
					log(err.stack);
					console.error(' !   See ' + logPath + ' for more info.');
				}
				process.exit(1);
			});
		}
		if (command === '') { command = null }
		var module = require(moduleName);
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, module, topic, command, ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := node.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = node.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	plugin.name = require('` + name + `/package.json').name;
	console.log(JSON.stringify(plugin))`
	cmd := node.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := node.InstallPackage(name); err != nil {
				panic(errors.New(string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	cache := FetchPluginCache()
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := cache[name]
		if plugin == nil {
			plugin = getPlugin(name, true)
		}
		if plugin != nil {
			for _, command := range plugin.Commands {
				command.Plugin = name
				command.Run = runFn(name, command.Topic, command.Command)
				command.Help = strings.TrimSpace(command.Help)
			}
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		if !ignorePlugin(f.Name()) {
			names = append(names, f.Name())
		}
	}
	return names
}

func ignorePlugin(plugin string) bool {
	ignored := []string{".bin", "node-inspector"}
	for _, p := range ignored {
		if plugin == p {
			return true
		}
	}
	return false
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir, "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		panic(err)
	}
	return fi.Mode()&os.ModeSymlink != 0
}
<commit_msg>updated iojs to 2.1.0<commit_after>package main

import (
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"runtime"
	"strconv"
	"strings"
	"syscall"

	"github.com/dickeyxxx/gode"
	"github.com/dickeyxxx/golock"
)

// Plugin represents a javascript plugin
type Plugin struct {
	Name     string     `json:"name"`
	Topics   TopicSet   `json:"topics"`
	Topic    *Topic     `json:"topic"`
	Commands CommandSet `json:"commands"`
}

var node = gode.NewClient(AppDir)

func init() {
	node.Registry = "https://d3nfsbmspisrno.cloudfront.net"
	node.NodeVersion = "2.1.0"
	node.NpmVersion = "2.10.1"
}

// SetupNode sets up node and npm in ~/.heroku
func SetupNode() {
	if !node.IsSetup() {
		LogIfError(golock.Lock(updateLockPath))
		defer golock.Unlock(updateLockPath)
		if node.IsSetup() {
			return
		}
		Debugln("setting up iojs", node.NodeVersion)
		ExitIfError(node.Setup())
		clearOldNodeInstalls()
		Debugln("done setting up iojs")
	}
}

func clearOldNodeInstalls() {
	files, _ := ioutil.ReadDir(AppDir)
	for _, f := range files {
		name := f.Name()
		if name != node.NodeBase() && strings.HasPrefix(name, "iojs-v") {
			LogIfError(os.RemoveAll(filepath.Join(AppDir, name)))
		}
	}
}

// LoadPlugins loads the topics and commands from the JavaScript plugins into the CLI
func (cli *Cli) LoadPlugins(plugins []Plugin) {
	for _, plugin := range plugins {
		for _, topic := range plugin.Topics {
			cli.AddTopic(topic)
		}
		if plugin.Topic != nil {
			cli.AddTopic(plugin.Topic)
		}
		for _, command := range plugin.Commands {
			if !cli.AddCommand(command) {
				Errf("WARNING: command %s has already been defined\n", command)
			}
		}
	}
}

var pluginsTopic = &Topic{
	Name:        "plugins",
	Description: "manage plugins",
}

var pluginsInstallCmd = &Command{
	Topic:       "plugins",
	Command:     "install",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Installs a plugin into the CLI",
	Help: `Install a Heroku plugin

  Example:
  $ heroku plugins:install dickeyxxx/heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		if len(name) == 0 {
			Errln("Must specify a plugin name")
			return
		}
		Errf("Installing plugin %s... ", name)
		err := node.InstallPackage(name)
		ExitIfError(err)
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Err("\nThis does not appear to be a Heroku plugin, uninstalling... ")
			ExitIfError(node.RemovePackage(name))
		}
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	},
}

var pluginsLinkCmd = &Command{
	Topic:       "plugins",
	Command:     "link",
	Description: "Links a local plugin into CLI",
	Args:        []Arg{{Name: "path", Optional: true}},
	Help: `Links a local plugin into CLI.
	This is useful when developing plugins locally.
	It simply symlinks the specified path into ~/.heroku/node_modules

  Example:
	$ heroku plugins:link .`,

	Run: func(ctx *Context) {
		path := ctx.Args.(map[string]string)["path"]
		if path == "" {
			path = "."
		}
		path, err := filepath.Abs(path)
		if err != nil {
			panic(err)
		}
		if _, err := os.Stat(path); err != nil {
			panic(err)
		}
		name := filepath.Base(path)
		newPath := filepath.Join(ctx.HerokuDir, "node_modules", name)
		os.Remove(newPath)
		os.RemoveAll(newPath)
		err = os.Symlink(path, newPath)
		if err != nil {
			panic(err)
		}
		plugin := getPlugin(name, false)
		if plugin == nil || len(plugin.Commands) == 0 {
			Errln(name + " does not appear to be a Heroku plugin")
			if err := os.Remove(newPath); err != nil {
				panic(err)
			}
			return
		}
		if name != plugin.Name {
			path = newPath
			newPath = filepath.Join(ctx.HerokuDir, "node_modules", plugin.Name)
			os.Remove(newPath)
			os.RemoveAll(newPath)
			os.Rename(path, newPath)
		}
		Println("symlinked", plugin.Name)
		Err("Updating plugin cache... ")
		ClearPluginCache()
		WritePluginCache(GetPlugins())
		Errln("done")
	},
}

var pluginsUninstallCmd = &Command{
	Topic:       "plugins",
	Command:     "uninstall",
	Hidden:      true,
	Args:        []Arg{{Name: "name"}},
	Description: "Uninstalls a plugin from the CLI",
	Help: `Uninstalls a Heroku plugin

  Example:
  $ heroku plugins:uninstall heroku-production-status`,

	Run: func(ctx *Context) {
		name := ctx.Args.(map[string]string)["name"]
		Errf("Uninstalling plugin %s... ", name)
		err := node.RemovePackage(name)
		ExitIfError(err)
		Errln("done")
	},
}

var pluginsListCmd = &Command{
	Topic:       "plugins",
	Hidden:      true,
	Description: "Lists the installed plugins",
	Help: `Lists installed plugins

  Example:
  $ heroku plugins`,

	Run: func(ctx *Context) {
		packages, err := node.Packages()
		if err != nil {
			panic(err)
		}
		for _, pkg := range packages {
			Println(pkg.Name, pkg.Version)
		}
	},
}

func runFn(module, topic, command string) func(ctx *Context) {
	return func(ctx *Context) {
		lockfile := updateLockPath + "." + module
		if exists, _ := fileExists(lockfile); exists {
			golock.Lock(lockfile)
			golock.Unlock(lockfile)
		}
		ctx.Dev = isPluginSymlinked(module)
		ctx.Version = ctx.Version + " " + module + " iojs-v" + node.NodeVersion
		ctxJSON, err := json.Marshal(ctx)
		if err != nil {
			panic(err)
		}
		script := fmt.Sprintf(`
		'use strict';
		var moduleName = '%s';
		var topic = '%s';
		var command = '%s';
		var ctx = %s;
		var logPath = %s;
		process.chdir(ctx.cwd);
		function repair (name) {
			console.error('Attempting to repair ' + name + '...');
			require('child_process')
			.spawnSync('heroku', ['plugins:install', name],
			{stdio: [0,1,2]});
			console.error('Repair complete. Try running your command again.');
		}
		if (!ctx.dev) {
			process.on('uncaughtException', function (err) {
				console.error(' !   Error in ' + moduleName + ':')
				if (err.message) {
					console.error(' !   ' + err.message);
					if (err.message.indexOf('Cannot find module') != -1) {
						repair(moduleName);
						process.exit(1);
					}
				} else {
					console.error(' !   ' + err);
				}
				if (err.stack) {
					var fs = require('fs');
					var log = function (line) {
						var d = new Date().toISOString()
						.replace(/T/, ' ')
						.replace(/-/g, '/')
						.replace(/\..+/, '');
						fs.appendFileSync(logPath, d + ' ' + line + '\n');
					}
					log('Error during ' + topic + ':' + command);
					log(err.stack);
					console.error(' !   See ' + logPath + ' for more info.');
				}
				process.exit(1);
			});
		}
		if (command === '') { command = null }
		var module = require(moduleName);
		var cmd = module.commands.filter(function (c) {
			return c.topic === topic && c.command == command;
		})[0];
		cmd.run(ctx);`, module, topic, command, ctxJSON, strconv.Quote(ErrLogPath))

		// swallow sigint since the plugin will handle it
		swallowSignal(os.Interrupt)

		cmd := node.RunScript(script)
		if ctx.Flags["debugger"] == true {
			cmd = node.DebugScript(script)
		}
		os.Chdir(cmd.Dir)
		execBin(cmd.Path, cmd.Args)
	}
}

func execBin(bin string, args []string) {
	if runtime.GOOS == "windows" {
		cmd := exec.Command(bin, args[1:]...)
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr
		if err := cmd.Run(); err != nil {
			os.Exit(getExitCode(err))
		}
	} else {
		if err := syscall.Exec(bin, args, os.Environ()); err != nil {
			panic(err)
		}
	}
}

func swallowSignal(s os.Signal) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, s)
	go func() {
		<-c
	}()
}

func getExitCode(err error) int {
	switch e := err.(type) {
	case *exec.ExitError:
		status, ok := e.Sys().(syscall.WaitStatus)
		if !ok {
			panic(err)
		}
		return status.ExitStatus()
	default:
		panic(err)
	}
}

func getPlugin(name string, attemptReinstall bool) *Plugin {
	script := `
	var plugin = require('` + name + `');
	plugin.name = require('` + name + `/package.json').name;
	console.log(JSON.stringify(plugin))`
	cmd := node.RunScript(script)
	output, err := cmd.CombinedOutput()
	if err != nil {
		if attemptReinstall && strings.Contains(string(output), "Error: Cannot find module") {
			Errf("Error reading plugin %s. Reinstalling... ", name)
			if err := node.InstallPackage(name); err != nil {
				panic(errors.New(string(output)))
			}
			Errln("done")
			return getPlugin(name, false)
		}
		Errf("Error reading plugin: %s. See %s for more information.\n", name, ErrLogPath)
		Logln(err, "\n", string(output))
		return nil
	}
	var plugin Plugin
	json.Unmarshal([]byte(output), &plugin)
	return &plugin
}

// GetPlugins goes through all the node plugins and returns them in Go stucts
func GetPlugins() []Plugin {
	cache := FetchPluginCache()
	names := PluginNames()
	plugins := make([]Plugin, 0, len(names))
	for _, name := range names {
		plugin := cache[name]
		if plugin == nil {
			plugin = getPlugin(name, true)
		}
		if plugin != nil {
			for _, command := range plugin.Commands {
				command.Plugin = name
				command.Run = runFn(name, command.Topic, command.Command)
				command.Help = strings.TrimSpace(command.Help)
			}
			plugins = append(plugins, *plugin)
		}
	}
	return plugins
}

// PluginNames just lists the files in ~/.heroku/node_modules
func PluginNames() []string {
	files, _ := ioutil.ReadDir(filepath.Join(AppDir, "node_modules"))
	names := make([]string, 0, len(files))
	for _, f := range files {
		if !ignorePlugin(f.Name()) {
			names = append(names, f.Name())
		}
	}
	return names
}

func ignorePlugin(plugin string) bool {
	ignored := []string{".bin", "node-inspector"}
	for _, p := range ignored {
		if plugin == p {
			return true
		}
	}
	return false
}

func isPluginSymlinked(plugin string) bool {
	path := filepath.Join(AppDir, "node_modules", plugin)
	fi, err := os.Lstat(path)
	if err != nil {
		panic(err)
	}
	return fi.Mode()&os.ModeSymlink != 0
}
<|endoftext|>


<commit_before>package jsonpointer

import (
	"errors"
	"strconv"
	"strings"

	"github.com/dustin/gojson"
)

var unparsable = errors.New("I can't parse this")

func arreq(a, b []string) bool {
	if len(a) == len(b) {
		for i := range a {
			if a[i] != b[i] {
				return false
			}
		}
		return true
	}

	return false
}

func parsePointer(s string) []string {
	a := strings.Split(s[1:], "/")

	for i := range a {
		if strings.Contains(a[i], "~") {
			a[i] = strings.Replace(a[i], "~1", "/", -1)
			a[i] = strings.Replace(a[i], "~0", "~", -1)
		}
	}
	return a
}

func encodePointer(p []string) string {
	a := make([]string, 0, len(p))
	for _, s := range p {
		s = strings.Replace(s, "~", "~0", -1)
		s = strings.Replace(s, "/", "~1", -1)
		a = append(a, s)
	}
	return "/" + strings.Join(a, "/")
}

func grokLiteral(b []byte) string {
	s, ok := json.UnquoteBytes(b)
	if !ok {
		panic("could not grok literal " + string(b))
	}
	return string(s)
}

// Find a section of raw JSON by specifying a JSONPointer.
func Find(data []byte, path string) ([]byte, error) {
	if path == "" {
		return data, nil
	}

	needle := parsePointer(path)

	scanner := &json.Scanner{}
	scanner.Reset()

	offset := 0
	beganLiteral := 0
	current := []string{}
	for {
		var newOp int
		if offset >= len(data) {
			newOp = scanner.EOF()
			break
			offset = len(data) + 1 // mark processed EOF with len+1
		} else {
			c := int(data[offset])
			offset++
			newOp = scanner.Step(scanner, c)
		}

		switch newOp {
		case json.ScanBeginArray:
			current = append(current, "0")
		case json.ScanObjectKey:
			current = append(current, grokLiteral(data[beganLiteral-1:offset-1]))
		case json.ScanBeginLiteral:
			beganLiteral = offset
		case json.ScanArrayValue:
			n, err := strconv.Atoi(current[len(current)-1])
			if err != nil {
				return nil, err
			}
			current[len(current)-1] = strconv.Itoa(n + 1)
		case json.ScanObjectValue:
			current = current[:len(current)-1]
		case json.ScanEndArray:
			current = current[:len(current)-1]
		}

		if (newOp == json.ScanBeginArray || newOp == json.ScanArrayValue ||
			newOp == json.ScanObjectKey) && arreq(needle, current) {
			val, _, err := json.NextValue(data[offset:], scanner)
			return val, err
		}
	}

	return nil, nil
}

// Find a section of raw JSON by specifying a JSONPointer.
func FindMany(data []byte, paths []string) (map[string][]byte, error) {
	todo := map[string]bool{}
	m := map[string][]byte{}
	for _, p := range paths {
		if p == "" {
			m[p] = data
		} else {
			todo[p] = true
		}
	}

	scan := &json.Scanner{}
	scan.Reset()

	offset := 0
	beganLiteral := 0
	var current []string
	for {
		var newOp int
		if offset >= len(data) {
			newOp = scan.EOF()
			break
			offset = len(data) + 1 // mark processed EOF with len+1
		} else {
			c := int(data[offset])
			offset++
			newOp = scan.Step(scan, c)
		}

		switch newOp {
		case json.ScanBeginArray:
			current = append(current, "0")
		case json.ScanObjectKey:
			current = append(current, grokLiteral(data[beganLiteral-1:offset-1]))
		case json.ScanBeginLiteral:
			beganLiteral = offset
		case json.ScanArrayValue:
			n, err := strconv.Atoi(current[len(current)-1])
			if err != nil {
				return nil, err
			}
			current[len(current)-1] = strconv.Itoa(n + 1)
		case json.ScanObjectValue, json.ScanEndArray, json.ScanEndObject:
			current = current[:len(current)-1]
		}

		if newOp == json.ScanBeginArray || newOp == json.ScanArrayValue ||
			newOp == json.ScanObjectKey {
			currentStr := encodePointer(current)
			if !todo[currentStr] {
				continue
			}

			stmp := &json.Scanner{}
			val, _, err := json.NextValue(data[offset:], stmp)
			if err != nil {
				return m, err
			}
			m[currentStr] = val
			delete(todo, currentStr)
			if len(todo) == 0 {
				return m, nil
			}
		}
	}

	return m, nil
}
<commit_msg>Performance enhancements for worst case.<commit_after>package jsonpointer

import (
	"errors"
	"sort"
	"strconv"
	"strings"

	"github.com/dustin/gojson"
)

var unparsable = errors.New("I can't parse this")

func arreq(a, b []string) bool {
	if len(a) == len(b) {
		for i := range a {
			if a[i] != b[i] {
				return false
			}
		}
		return true
	}

	return false
}

func parsePointer(s string) []string {
	a := strings.Split(s[1:], "/")

	for i := range a {
		if strings.Contains(a[i], "~") {
			a[i] = strings.Replace(a[i], "~1", "/", -1)
			a[i] = strings.Replace(a[i], "~0", "~", -1)
		}
	}
	return a
}

func encodePointer(p []string) string {
	a := make([]string, 0, len(p))
	for _, s := range p {
		s = strings.Replace(s, "~", "~0", -1)
		s = strings.Replace(s, "/", "~1", -1)
		a = append(a, s)
	}
	return "/" + strings.Join(a, "/")
}

func grokLiteral(b []byte) string {
	s, ok := json.UnquoteBytes(b)
	if !ok {
		panic("could not grok literal " + string(b))
	}
	return string(s)
}

// Find a section of raw JSON by specifying a JSONPointer.
func Find(data []byte, path string) ([]byte, error) {
	if path == "" {
		return data, nil
	}

	needle := parsePointer(path)

	scanner := &json.Scanner{}
	scanner.Reset()

	offset := 0
	beganLiteral := 0
	current := []string{}
	for {
		var newOp int
		if offset >= len(data) {
			newOp = scanner.EOF()
			break
			offset = len(data) + 1 // mark processed EOF with len+1
		} else {
			c := int(data[offset])
			offset++
			newOp = scanner.Step(scanner, c)
		}

		switch newOp {
		case json.ScanBeginArray:
			current = append(current, "0")
		case json.ScanObjectKey:
			current = append(current, grokLiteral(data[beganLiteral-1:offset-1]))
		case json.ScanBeginLiteral:
			beganLiteral = offset
		case json.ScanArrayValue:
			n, err := strconv.Atoi(current[len(current)-1])
			if err != nil {
				return nil, err
			}
			current[len(current)-1] = strconv.Itoa(n + 1)
		case json.ScanObjectValue:
			current = current[:len(current)-1]
		case json.ScanEndArray:
			current = current[:len(current)-1]
		}

		if (newOp == json.ScanBeginArray || newOp == json.ScanArrayValue ||
			newOp == json.ScanObjectKey) && arreq(needle, current) {
			val, _, err := json.NextValue(data[offset:], scanner)
			return val, err
		}
	}

	return nil, nil
}

// Find a section of raw JSON by specifying a JSONPointer.
func FindMany(data []byte, paths []string) (map[string][]byte, error) {
	tpaths := make([]string, 0, len(paths))
	m := map[string][]byte{}
	for _, p := range paths {
		if p == "" {
			m[p] = data
		} else {
			tpaths = append(tpaths, p)
		}
	}
	sort.Strings(tpaths)

	scan := &json.Scanner{}
	scan.Reset()

	offset := 0
	todo := len(tpaths)
	beganLiteral := 0
	matchedAt := 0
	var current []string
	for {
		var newOp int
		if offset >= len(data) {
			newOp = scan.EOF()
			break
			offset = len(data) + 1 // mark processed EOF with len+1
		} else {
			c := int(data[offset])
			offset++
			newOp = scan.Step(scan, c)
		}

		switch newOp {
		case json.ScanBeginArray:
			current = append(current, "0")
		case json.ScanObjectKey:
			current = append(current, grokLiteral(data[beganLiteral-1:offset-1]))
		case json.ScanBeginLiteral:
			beganLiteral = offset
		case json.ScanArrayValue:
			n, err := strconv.Atoi(current[len(current)-1])
			if err != nil {
				return nil, err
			}
			current[len(current)-1] = strconv.Itoa(n + 1)
		case json.ScanObjectValue, json.ScanEndArray, json.ScanEndObject:
			current = current[:len(current)-1]
		}

		if newOp == json.ScanBeginArray || newOp == json.ScanArrayValue ||
			newOp == json.ScanObjectKey {

			if matchedAt < len(current)-1 {
				continue
			}
			if matchedAt > len(current) {
				matchedAt = len(current)
			}

			currentStr := encodePointer(current)
			off := sort.SearchStrings(tpaths, currentStr)
			if off < len(tpaths) {
				// Check to see if the path we're
				// going down could even lead to a
				// possible match.
				if strings.HasPrefix(tpaths[off], currentStr) {
					matchedAt++
				}
				// And if it's not an exact match, keep parsing.
				if tpaths[off] != currentStr {
					continue
				}
			} else {
				// Fell of the end of the list, no possible match
				continue
			}

			// At this point, we have an exact match, so grab it.
			stmp := &json.Scanner{}
			val, _, err := json.NextValue(data[offset:], stmp)
			if err != nil {
				return m, err
			}
			m[currentStr] = val
			todo--
			if todo == 0 {
				return m, nil
			}
		}
	}

	return m, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/microcosm-cc/bluemonday"
	"github.com/russross/blackfriday"
	"io/ioutil"
	"os"
	// "path/filepath"
	"encoding/json"
	"strings"
)

// For holding our post's configuration settings
type Config struct {
	Title string `json:"title`
	Date  string `json:"date`
}

func main() {

	// root := "./content"
	// err := filepath.Walk(root, visit)
	// fmt.Printf("filepath.Walk() returned %v\n", err)
	// os.Exit(1)

	args := os.Args[1:] // Reason: index 0 contains the program path
	if len(args) != 2 {
		fmt.Println("ERROR: Wrong number of arguments provided. We're expecting:")
		fmt.Println("1. Input file")
		fmt.Println("2. Output file")
		os.Exit(1)
	}
	inputFile := args[0]
	outputFile := args[1]

	title, date := getPostConfigsJson(inputFile)

	fmt.Println("postTitle: " + title)
	fmt.Println("postDate: " + date)
	fmt.Println("Leaving off here")
	// os.Exit(1)

	// func call here
	content := getContent(inputFile)

	// Get the shared markup pieces
	topHTML, err := ioutil.ReadFile("./shared_markup/top.html")
	check(err)
	bottomHTML, err := ioutil.ReadFile("./shared_markup/bottom.html")
	check(err)

	// Incrementally building a singular byte array via append()
	finalOutput := append(topHTML[:], content[:]...)
	finalOutput = append(finalOutput, bottomHTML[:]...)

	temp := string(finalOutput[:])
	temp = strings.Replace(temp, "{{title}}", title, -1)
	finalOutput = []byte(temp)

	// Write our output to an HTML file
	err = ioutil.WriteFile("./content/"+outputFile, finalOutput, 0644)
	check(err)

	fmt.Println("Program finished, check result.")
}

func getContent(inputFile string) []byte {
	// Get the Markdown input
	rawInput, err := ioutil.ReadFile("./content/" + inputFile)
	check(err)

	// Convert and sanitize our content
	unsafe := blackfriday.MarkdownCommon(rawInput)
	content := bluemonday.UGCPolicy().SanitizeBytes(unsafe)

	return content
}

func getPostConfigsJson(inputFile string) (string, string) {
	// Get the configs for this page:
	// First read the post's corresponding .json file
	configPath := "./content/" + strings.Replace(inputFile, ".md", "", 1) + ".json"
	configJson, err := ioutil.ReadFile(configPath)
	check(err)

	// Then parse out our relevant config values for later use
	var config Config
	err = json.Unmarshal(configJson, &config)
	check(err)

	return config.Title, config.Date
}

func visit(path string, f os.FileInfo, err error) error {
	fmt.Printf("Visited: %s\n", path)
	return nil
}

func check(e error) {
	if e != nil {
		panic(e)
	}
}
<commit_msg>Moved fetching shared markup pieces into a function<commit_after>package main

import (
	"fmt"
	"github.com/microcosm-cc/bluemonday"
	"github.com/russross/blackfriday"
	"io/ioutil"
	"os"
	// "path/filepath"
	"encoding/json"
	"strings"
)

// For holding our post's configuration settings
type Config struct {
	Title string `json:"title`
	Date  string `json:"date`
}

func main() {

	// root := "./content"
	// err := filepath.Walk(root, visit)
	// fmt.Printf("filepath.Walk() returned %v\n", err)
	// os.Exit(1)

	args := os.Args[1:] // Reason: index 0 contains the program path
	if len(args) != 2 {
		fmt.Println("ERROR: Wrong number of arguments provided. We're expecting:")
		fmt.Println("1. Input file")
		fmt.Println("2. Output file")
		os.Exit(1)
	}
	inputFile := args[0]
	outputFile := args[1]

	title, date := getPostConfigsJson(inputFile)

	fmt.Println("postTitle: " + title)
	fmt.Println("postDate: " + date)
	fmt.Println("Leaving off here")
	// os.Exit(1)

	content := getContent(inputFile)

	topHTML, bottomHTML := getSharedMarkup()

	// Incrementally building a singular byte array via append()
	finalOutput := append(topHTML[:], content[:]...)
	finalOutput = append(finalOutput, bottomHTML[:]...)

	temp := string(finalOutput[:])
	temp = strings.Replace(temp, "{{title}}", title, -1)
	finalOutput = []byte(temp)

	// Write our output to an HTML file
	err := ioutil.WriteFile("./content/"+outputFile, finalOutput, 0644)
	check(err)

	fmt.Println("Program finished, check result.")
}

func getSharedMarkup() ([]byte, []byte) {

	// Get the shared markup pieces
	topHTML, err := ioutil.ReadFile("./shared_markup/top.html")
	check(err)
	bottomHTML, err := ioutil.ReadFile("./shared_markup/bottom.html")
	check(err)

	return topHTML, bottomHTML
}

func getContent(inputFile string) []byte {

	// Get the Markdown input
	rawInput, err := ioutil.ReadFile("./content/" + inputFile)
	check(err)

	// Convert and sanitize our content
	unsafe := blackfriday.MarkdownCommon(rawInput)
	content := bluemonday.UGCPolicy().SanitizeBytes(unsafe)

	return content
}

func getPostConfigsJson(inputFile string) (string, string) {
	// Get the configs for this page:
	// First read the post's corresponding .json file
	configPath := "./content/" + strings.Replace(inputFile, ".md", "", 1) + ".json"
	configJson, err := ioutil.ReadFile(configPath)
	check(err)

	// Then parse out our relevant config values for later use
	var config Config
	err = json.Unmarshal(configJson, &config)
	check(err)

	return config.Title, config.Date
}

func visit(path string, f os.FileInfo, err error) error {
	fmt.Printf("Visited: %s\n", path)
	return nil
}

func check(e error) {
	if e != nil {
		panic(e)
	}
}
<|endoftext|>


<commit_before>package battery

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

var (
	deviceBusy = errors.New("battery updating; can't poll.")
)

type state struct {
	// true iff the battery is being charged.
	Charging bool
	// The percentage of the battery's maximum energy remaining.
	PercentRemaining int
	// Time until battery is exhausted. Only meaningful if Charging is false.
	Hours, Minutes, Seconds int
}

type battery struct {
	Path  string
	State *state
}

func Monitor(filename string) <-chan string {
	ch := make(chan string, 1)
	b := &battery{filename, nil}
	if b.exists() {
		go b.Monitor(ch)
		go b.NotifyDaemon()
	}
	return ch
}

func (s *state) String() string {
	if s.Charging {
		return fmt.Sprintf("Battery charging, %d%%", s.PercentRemaining)
	} else {
		return fmt.Sprintf("On battery, %d:%02d remaining (%d%%)",
			s.Hours, s.Minutes, s.PercentRemaining)
	}
}

func (b *battery) Poll() (err error) {

	// Read in each of the files we care about, and store their contents in a map.
	text := make(map[string]string)
	for _, f := range []string{"status", "capacity", "power_now", "energy_now"} {
		text[f], err = slurpFile(b.Path + f)
		if err != nil {
			// If we're unsuccessful reading any of the files, stop now.
			// We don't want to update the state until we can get a
			// meaningful reading.
			return
		}
	}

	// Most of the files contain numbers; convert them into actual
	// integers so we can work with them.
	nums := make(map[string]int)
	for _, f := range []string{"capacity", "power_now", "energy_now"} {
		// The call to TrimSpace is important; the files end with a
		// newline, so without this Atoi will fail.
		nums[f], err = strconv.Atoi(strings.TrimSpace(text[f]))
		if err != nil {
			// Again, stop now if anything fails.
			return
		}
	}

	// assigning these to variables will make the code below less verbose
	capacity, power, energy := nums["capacity"], nums["power_now"], nums["energy_now"]

	if power == 0 {
		// If power is zero, it is usually an indication that the
		// device has been recently plugged in or unplugged, and
		// hasn't settled down yet. Stop here; we want to wait until
		// we can get an accurate reading.
		return deviceBusy
	}

	result := &state{}

	// work out how much time we have left
	result.Hours = energy / power
	energy %= power
	result.Minutes = (60 * energy) / power
	energy = (60 * energy) % power
	result.Seconds = (60 * energy) / power

	result.PercentRemaining = capacity

	result.Charging = (text["status"][0] == 'C')
	b.State = result
	return nil
}

func (b *battery) Monitor(ch chan<- string) {
	for {
		if b.Poll() == nil {
			ch <-b.State.String()
		}
		time.Sleep(time.Second)
	}
}

func (b *battery) NotifyDaemon() {
	if !b.exists() {
		return
	}

	// These variables record whether or not the battery has reached low
	// or very low states since the last time it was unplugged. This lets
	// us avoid getting multiple messages about those events, which
	// happens because the estimated time remaining tends to fluctuate.
	wasLow := false
	wasVeryLow := false

	oldstate := b.State
	for {
		state := b.State

		// Check for low battery status
		switch {
		case state == nil:
			// We don't have any data on the battery right now.
		case state.Hours > 0 || state.Charging:
			// Either the battery is not low, or or it's charging.
			// No need to bother the user.
		case state.Minutes < 5 && !wasVeryLow:
			notifySend("critical", "Very low battery", state.String())
			wasVeryLow = true
		case state.Minutes < 20 && !wasLow:
			notifySend("normal", "Low battery", state.String())
			wasLow = true
		}

		// Check for AC adapter events.
		switch {
		case oldstate == nil || state == nil || oldstate.Charging == state.Charging:
			// The AC adapter hasn't changed state since we last measured it.
		case state.Charging:
			notifySend("normal", "AC adapter connected", state.String())
			wasVeryLow = false
			wasLow = false
		case !state.Charging:
			notifySend("normal", "AC adapter disconnected", state.String())
		}

		// Save the current state for later comparison.
		oldstate = state

		time.Sleep(time.Second)
	}
}

func notifySend(urgency, summary, body string) {
	cmd := exec.Command("notify-send", "-u", urgency, summary, body)
	cmd.Run()
}

// This is a utility function which grabs the entire contents of a file by
// name. We use this in the Poll method.
func slurpFile(filename string) (string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer file.Close()
	data, err := ioutil.ReadAll(file)
	return string(data), err
}

func (b *battery) exists() bool {
	_, err := os.Stat(b.Path)
	return err == nil
}
<commit_msg>Remove redundant check<commit_after>package battery

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

var (
	deviceBusy = errors.New("battery updating; can't poll.")
)

type state struct {
	// true iff the battery is being charged.
	Charging bool
	// The percentage of the battery's maximum energy remaining.
	PercentRemaining int
	// Time until battery is exhausted. Only meaningful if Charging is false.
	Hours, Minutes, Seconds int
}

type battery struct {
	Path  string
	State *state
}

func Monitor(filename string) <-chan string {
	ch := make(chan string, 1)
	b := &battery{filename, nil}
	if b.exists() {
		go b.Monitor(ch)
		go b.NotifyDaemon()
	}
	return ch
}

func (s *state) String() string {
	if s.Charging {
		return fmt.Sprintf("Battery charging, %d%%", s.PercentRemaining)
	} else {
		return fmt.Sprintf("On battery, %d:%02d remaining (%d%%)",
			s.Hours, s.Minutes, s.PercentRemaining)
	}
}

func (b *battery) Poll() (err error) {

	// Read in each of the files we care about, and store their contents in a map.
	text := make(map[string]string)
	for _, f := range []string{"status", "capacity", "power_now", "energy_now"} {
		text[f], err = slurpFile(b.Path + f)
		if err != nil {
			// If we're unsuccessful reading any of the files, stop now.
			// We don't want to update the state until we can get a
			// meaningful reading.
			return
		}
	}

	// Most of the files contain numbers; convert them into actual
	// integers so we can work with them.
	nums := make(map[string]int)
	for _, f := range []string{"capacity", "power_now", "energy_now"} {
		// The call to TrimSpace is important; the files end with a
		// newline, so without this Atoi will fail.
		nums[f], err = strconv.Atoi(strings.TrimSpace(text[f]))
		if err != nil {
			// Again, stop now if anything fails.
			return
		}
	}

	// assigning these to variables will make the code below less verbose
	capacity, power, energy := nums["capacity"], nums["power_now"], nums["energy_now"]

	if power == 0 {
		// If power is zero, it is usually an indication that the
		// device has been recently plugged in or unplugged, and
		// hasn't settled down yet. Stop here; we want to wait until
		// we can get an accurate reading.
		return deviceBusy
	}

	result := &state{}

	// work out how much time we have left
	result.Hours = energy / power
	energy %= power
	result.Minutes = (60 * energy) / power
	energy = (60 * energy) % power
	result.Seconds = (60 * energy) / power

	result.PercentRemaining = capacity

	result.Charging = (text["status"][0] == 'C')
	b.State = result
	return nil
}

func (b *battery) Monitor(ch chan<- string) {
	for {
		if b.Poll() == nil {
			ch <-b.State.String()
		}
		time.Sleep(time.Second)
	}
}

func (b *battery) NotifyDaemon() {
	// These variables record whether or not the battery has reached low
	// or very low states since the last time it was unplugged. This lets
	// us avoid getting multiple messages about those events, which
	// happens because the estimated time remaining tends to fluctuate.
	wasLow := false
	wasVeryLow := false

	oldstate := b.State
	for {
		state := b.State

		// Check for low battery status
		switch {
		case state == nil:
			// We don't have any data on the battery right now.
		case state.Hours > 0 || state.Charging:
			// Either the battery is not low, or or it's charging.
			// No need to bother the user.
		case state.Minutes < 5 && !wasVeryLow:
			notifySend("critical", "Very low battery", state.String())
			wasVeryLow = true
		case state.Minutes < 20 && !wasLow:
			notifySend("normal", "Low battery", state.String())
			wasLow = true
		}

		// Check for AC adapter events.
		switch {
		case oldstate == nil || state == nil || oldstate.Charging == state.Charging:
			// The AC adapter hasn't changed state since we last measured it.
		case state.Charging:
			notifySend("normal", "AC adapter connected", state.String())
			wasVeryLow = false
			wasLow = false
		case !state.Charging:
			notifySend("normal", "AC adapter disconnected", state.String())
		}

		// Save the current state for later comparison.
		oldstate = state

		time.Sleep(time.Second)
	}
}

func notifySend(urgency, summary, body string) {
	cmd := exec.Command("notify-send", "-u", urgency, summary, body)
	cmd.Run()
}

// This is a utility function which grabs the entire contents of a file by
// name. We use this in the Poll method.
func slurpFile(filename string) (string, error) {
	file, err := os.Open(filename)
	if err != nil {
		return "", err
	}
	defer file.Close()
	data, err := ioutil.ReadAll(file)
	return string(data), err
}

func (b *battery) exists() bool {
	_, err := os.Stat(b.Path)
	return err == nil
}
<|endoftext|>


<commit_before>// Copyright 2016 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package sublime

import (
	"io/ioutil"
	"os"
	"path"
	"path/filepath"

	"github.com/limetext/lime-backend/lib"
	"github.com/limetext/lime-backend/lib/keys"
	"github.com/limetext/lime-backend/lib/log"
	"github.com/limetext/lime-backend/lib/packages"
	"github.com/limetext/text"
)

// A sublime package
type pkg struct {
	dir string
	text.HasSettings
	keys.HasKeyBindings
	platformSet *text.HasSettings
	defaultSet  *text.HasSettings
	defaultKB   *keys.HasKeyBindings
	plugins     map[string]*plugin
	// TODO: themes, snippets, etc more info on iss#71
}

func newPKG(dir string) packages.Package {
	return &pkg{
		dir:         dir,
		platformSet: new(text.HasSettings),
		defaultSet:  new(text.HasSettings),
		defaultKB:   new(keys.HasKeyBindings),
		plugins:     make(map[string]*plugin),
	}
}

func (p *pkg) Load() {
	log.Debug("Loading package %s", p.Name())
	p.loadKeyBindings()
	p.loadSettings()
	p.loadPlugins()
}

func (p *pkg) Name() string {
	return p.dir
}

// TODO: how we should watch the package and the files containing?
func (p *pkg) FileCreated(name string) {
	p.loadPlugin(name)
}

func (p *pkg) loadPlugins() {
	log.Fine("Loading %s plugins", p.Name())
	fis, err := ioutil.ReadDir(p.Name())
	if err != nil {
		log.Warn("Error on reading directory %s, %s", p.Name(), err)
		return
	}
	for _, fi := range fis {
		if isPlugin(fi.Name()) {
			p.loadPlugin(path.Join(p.Name(), fi.Name()))
		}
	}
}

func (p *pkg) loadPlugin(fn string) {
	if _, exist := p.plugins[fn]; exist {
		return
	}

	pl := newPlugin(fn)
	pl.Load()

	p.plugins[fn] = pl.(*plugin)
}

func (p *pkg) loadKeyBindings() {
	log.Fine("Loading %s keybindings", p.Name())
	ed := backend.GetEditor()
	tmp := ed.KeyBindings().Parent()
	dir := filepath.Dir(p.Name())

	ed.KeyBindings().SetParent(p)
	p.KeyBindings().SetParent(p.defaultKB)
	p.defaultKB.KeyBindings().SetParent(tmp)

	pt := path.Join(dir, "Default.sublime-keymap")
	packages.NewJSONL(pt, p.defaultKB.KeyBindings())

	pt = path.Join(dir, "Default ("+ed.Plat()+").sublime-keymap")
	packages.NewJSONL(pt, p.KeyBindings())
}

func (p *pkg) loadSettings() {
	log.Fine("Loading %s settings", p.Name())
	ed := backend.GetEditor()
	tmp := ed.Settings().Parent()
	dir := filepath.Dir(p.Name())

	ed.Settings().SetParent(p)
	p.Settings().SetParent(p.platformSet)
	p.platformSet.Settings().SetParent(p.defaultSet)
	p.defaultSet.Settings().SetParent(tmp)

	pt := path.Join(dir, "Preferences.sublime-settings")
	packages.NewJSONL(pt, p.defaultSet.Settings())

	pt = path.Join(dir, "Preferences ("+ed.Plat()+").sublime-settings")
	packages.NewJSONL(pt, p.platformSet.Settings())

	pt = path.Join(ed.PackagesPath("user"), "Preferences.sublime-settings")
	packages.NewJSONL(pt, p.Settings())
}

// Any directory in sublime is a package
func isPKG(dir string) bool {
	fi, err := os.Stat(dir)
	if err != nil {
		return false
	}
	return fi.IsDir()
}

var packageRecord *packages.Record = &packages.Record{isPKG, newPKG}

func init() {
	packages.Register(packageRecord)
}
<commit_msg>Forgot to add File*Callbacks for packages.JSON<commit_after>// Copyright 2016 The lime Authors.
// Use of this source code is governed by a 2-clause
// BSD-style license that can be found in the LICENSE file.

package sublime

import (
	"io/ioutil"
	"os"
	"path"

	"github.com/limetext/lime-backend/lib"
	"github.com/limetext/lime-backend/lib/keys"
	"github.com/limetext/lime-backend/lib/log"
	"github.com/limetext/lime-backend/lib/packages"
	"github.com/limetext/text"
)

// A sublime package
type pkg struct {
	dir string
	text.HasSettings
	keys.HasKeyBindings
	platformSet *text.HasSettings
	defaultSet  *text.HasSettings
	defaultKB   *keys.HasKeyBindings
	plugins     map[string]*plugin
	// TODO: themes, snippets, etc more info on iss#71
}

func newPKG(dir string) packages.Package {
	return &pkg{
		dir:         dir,
		platformSet: new(text.HasSettings),
		defaultSet:  new(text.HasSettings),
		defaultKB:   new(keys.HasKeyBindings),
		plugins:     make(map[string]*plugin),
	}
}

func (p *pkg) Load() {
	log.Debug("Loading package %s", p.Name())
	p.loadKeyBindings()
	p.loadSettings()
	p.loadPlugins()
}

func (p *pkg) Name() string {
	return p.dir
}

// TODO: how we should watch the package and the files containing?
func (p *pkg) FileCreated(name string) {
	p.loadPlugin(name)
}

func (p *pkg) loadPlugins() {
	log.Fine("Loading %s plugins", p.Name())
	fis, err := ioutil.ReadDir(p.Name())
	if err != nil {
		log.Warn("Error on reading directory %s, %s", p.Name(), err)
		return
	}
	for _, fi := range fis {
		if isPlugin(fi.Name()) {
			p.loadPlugin(path.Join(p.Name(), fi.Name()))
		}
	}
}

func (p *pkg) loadPlugin(fn string) {
	if _, exist := p.plugins[fn]; exist {
		return
	}

	pl := newPlugin(fn)
	pl.Load()

	p.plugins[fn] = pl.(*plugin)
}

func (p *pkg) loadKeyBindings() {
	log.Fine("Loading %s keybindings", p.Name())
	ed := backend.GetEditor()
	tmp := ed.KeyBindings().Parent()

	ed.KeyBindings().SetParent(p)
	p.KeyBindings().SetParent(p.defaultKB)
	p.defaultKB.KeyBindings().SetParent(tmp)

	pt := path.Join(p.dir, "Default.sublime-keymap")
	packages.NewJSONL(pt, p.defaultKB.KeyBindings())

	pt = path.Join(p.dir, "Default ("+ed.Plat()+").sublime-keymap")
	packages.NewJSONL(pt, p.KeyBindings())
}

func (p *pkg) loadSettings() {
	log.Fine("Loading %s settings", p.Name())
	ed := backend.GetEditor()
	tmp := ed.Settings().Parent()

	ed.Settings().SetParent(p)
	p.Settings().SetParent(p.platformSet)
	p.platformSet.Settings().SetParent(p.defaultSet)
	p.defaultSet.Settings().SetParent(tmp)

	pt := path.Join(p.dir, "Preferences.sublime-settings")
	packages.NewJSONL(pt, p.defaultSet.Settings())

	pt = path.Join(p.dir, "Preferences ("+ed.Plat()+").sublime-settings")
	packages.NewJSONL(pt, p.platformSet.Settings())

	pt = path.Join(ed.PackagesPath("user"), "Preferences.sublime-settings")
	packages.NewJSONL(pt, p.Settings())
}

// Any directory in sublime is a package
func isPKG(dir string) bool {
	fi, err := os.Stat(dir)
	if err != nil {
		return false
	}
	return fi.IsDir()
}

var packageRecord *packages.Record = &packages.Record{isPKG, newPKG}

func init() {
	packages.Register(packageRecord)
}
<|endoftext|>


<commit_before>package semaphore

import (
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

type testSemaphore struct {
	s     *Semaphore
	value int64 // value contains an upper bound on the current semaphore's value
	err   chan string
}

func (s *testSemaphore) setError(e string) {
	select {
	case s.err <- e:
	default:
	}
}

func (s *testSemaphore) Error() string {
	select {
	case e := <-s.err:
		return e
	default:
		return ""
	}
}

func (s *testSemaphore) Acquire(n int) {
	s.s.Acquire(n)
	v := atomic.AddInt64(&s.value, int64(-n))
	if v < 0 {
		s.setError("Acquire lowered the semaphore to a negative value")
	}
}

func (s *testSemaphore) Drain() int {
	n := s.s.Drain()
	v := atomic.AddInt64(&s.value, int64(-n))
	if v < 0 {
		s.setError("Drain lowered the semaphore to a negative value")
	}
	return n
}

func (s *testSemaphore) Release(n int) {
	atomic.AddInt64(&s.value, int64(n))
	s.s.Release(n)
}

func newTestSemaphore(value int) *testSemaphore {
	return &testSemaphore{
		s:     New(value),
		value: int64(value),
		err:   make(chan string, 1),
	}
}

func TestSemaphore(t *testing.T) {
	const P = 5
	const N = 1000
	s := newTestSemaphore(P)
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		for j := 0; j < N; j++ {
			v := s.Drain()
			s.Release(v)
		}
		wg.Done()
	}()
	for i := 0; i < P; i++ {
		wg.Add(1)
		go func() {
			for j := 0; j < N; j++ {
				s.Acquire(1)
				s.Release(1)
				s.Acquire(2)
				s.Release(2)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	if err := s.Error(); err != "" {
		t.Error(err)
	}
}

func TestDrain(t *testing.T) {
	const P = 5
	const N = 1000
	s := newTestSemaphore(P)
	var wg sync.WaitGroup
	for i := 0; i < P; i++ {
		wg.Add(1)
		go func() {
			for j := 0; j < N; j++ {
				v := s.Drain()
				s.Release(v)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	if err := s.Error(); err != "" {
		t.Error(err)
	}
}

func TestCancel(t *testing.T) {
	ch := make(chan struct{})
	s := New(1)
	if s.AcquireCancellable(1, ch) != true {
		t.Fatal("AcquireCancellable spuriously cancelled.")
	}
	s.Release(1)
	go func() {
		time.Sleep(time.Duration(100) * time.Millisecond)
		close(ch)
	}()
	if s.AcquireCancellable(2, ch) != false {
		t.Fatal("AcquireCancellable incorrectly succeeded")
	}
	if v := s.Drain(); v != 1 {
		t.Errorf("Drain drained %d units when %d should have been available", v, 1)
	}
}

func TestCancelConcurrent(t *testing.T) {
	const M = 5
	const N = 5
	ch := make(chan struct{}, M)
	for i := 0; i < M; i++ {
		ch <- struct{}{}
	}

	results := make(chan bool, M+N)

	s := New(2*N + 1)
	for i := 0; i < M+N; i++ {
		go func() {
			results <- s.AcquireCancellable(2, ch)
		}()
	}

	successes := 0
	for i := 0; i < M+N; i++ {
		if <-results {
			successes++
		}
	}
	close(results)

	if successes != N {
		t.Fatalf("Expected %d AcquireCancellable calls to succeed; instead %d succeeded.", N, successes)
	}
	if v := s.Drain(); v != 1 {
		t.Errorf("Drain drained %d units when %d should have been available", v, 1)
	}
}

func TestPromptCancel(t *testing.T) {
	s := New(0)
	result := make(chan bool)
	go func() {
		result <- s.AcquireCancellable(1, nil)
	}()
	time.Sleep(time.Duration(100) * time.Millisecond)
	cancel := make(chan struct{}, 1)
	cancel <- struct{}{}
	if s.AcquireCancellable(1, cancel) == true {
		t.Fatal("AcquireCancellable succeeded on a zero-valued semaphore.")
	}
	s.Release(1)
	if <-result != true {
		t.Fatal("AcquireCancellable spuriously failed.")
	}
}

func BenchmarkSemaphore(b *testing.B) {
	const P = 10
	const UnitSize = 1000
	N := int64(b.N / UnitSize / 4)
	s := newTestSemaphore(P)
	done := make(chan struct{}, P)
	for i := 0; i < P; i++ {
		go func() {
			for atomic.AddInt64(&N, int64(-1)) > 0 {
				for j := 0; j < UnitSize; j++ {
					s.Acquire(1)
					s.Release(1)
					s.Acquire(2)
					s.Release(2)
				}
			}
			done <- struct{}{}
		}()
	}
	for i := 0; i < P; i++ {
		<-done
	}
	if err := s.Error(); err != "" {
		b.Error(err)
	}
}
<commit_msg>Added a test and a benchmark for TimedAcquire.<commit_after>package semaphore

import (
	"sync"
	"sync/atomic"
	"testing"
	"time"
)

type testSemaphore struct {
	s     *Semaphore
	value int64 // value contains an upper bound on the current semaphore's value
	err   chan string
}

func (s *testSemaphore) setError(e string) {
	select {
	case s.err <- e:
	default:
	}
}

func (s *testSemaphore) Error() string {
	select {
	case e := <-s.err:
		return e
	default:
		return ""
	}
}

func (s *testSemaphore) Acquire(n int) {
	s.s.Acquire(n)
	v := atomic.AddInt64(&s.value, int64(-n))
	if v < 0 {
		s.setError("Acquire lowered the semaphore to a negative value")
	}
}

func (s *testSemaphore) TimedAcquire(n int, delay time.Duration) bool {
	result := s.s.TimedAcquire(n, delay)
	if result {
		v := atomic.AddInt64(&s.value, int64(-n))
		if v < 0 {
			s.setError("TimedAcquire lowered the semaphore to a negative value")
		}
	}
	return result
}

func (s *testSemaphore) Drain() int {
	n := s.s.Drain()
	v := atomic.AddInt64(&s.value, int64(-n))
	if v < 0 {
		s.setError("Drain lowered the semaphore to a negative value")
	}
	return n
}

func (s *testSemaphore) Release(n int) {
	atomic.AddInt64(&s.value, int64(n))
	s.s.Release(n)
}

func newTestSemaphore(value int) *testSemaphore {
	return &testSemaphore{
		s:     New(value),
		value: int64(value),
		err:   make(chan string, 1),
	}
}

func TestSemaphore(t *testing.T) {
	const P = 5
	const N = 1000
	s := newTestSemaphore(P)
	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		for j := 0; j < N; j++ {
			v := s.Drain()
			s.Release(v)
		}
		wg.Done()
	}()
	for i := 0; i < P; i++ {
		wg.Add(1)
		go func() {
			for j := 0; j < N; j++ {
				s.Acquire(1)
				s.Release(1)
				s.Acquire(2)
				s.Release(2)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	if err := s.Error(); err != "" {
		t.Error(err)
	}
}

func TestDrain(t *testing.T) {
	const P = 5
	const N = 1000
	s := newTestSemaphore(P)
	var wg sync.WaitGroup
	for i := 0; i < P; i++ {
		wg.Add(1)
		go func() {
			for j := 0; j < N; j++ {
				v := s.Drain()
				s.Release(v)
			}
			wg.Done()
		}()
	}
	wg.Wait()
	if err := s.Error(); err != "" {
		t.Error(err)
	}
}

func TestCancel(t *testing.T) {
	ch := make(chan struct{})
	s := New(1)
	if s.AcquireCancellable(1, ch) != true {
		t.Fatal("AcquireCancellable spuriously cancelled.")
	}
	s.Release(1)
	go func() {
		time.Sleep(time.Duration(100) * time.Millisecond)
		close(ch)
	}()
	if s.AcquireCancellable(2, ch) != false {
		t.Fatal("AcquireCancellable incorrectly succeeded")
	}
	if v := s.Drain(); v != 1 {
		t.Errorf("Drain drained %d units when %d should have been available", v, 1)
	}
}

func TestTimedAcquire(t *testing.T) {
	s := New(1)
	if s.TimedAcquire(1, time.Duration(100)*time.Millisecond) != true {
		t.Fatal("TimedAcquire spuriously failed")
	}
	s.Release(1)
	if s.TimedAcquire(2, time.Duration(100)*time.Millisecond) != false {
		t.Fatal("TimedAcquire incorrectly succeeded")
	}
	if v := s.Drain(); v != 1 {
		t.Errorf("Drain drained %d units when %d should have been available", v, 1)
	}
}

func TestCancelConcurrent(t *testing.T) {
	const M = 5
	const N = 5
	ch := make(chan struct{}, M)
	for i := 0; i < M; i++ {
		ch <- struct{}{}
	}

	results := make(chan bool, M+N)

	s := New(2*N + 1)
	for i := 0; i < M+N; i++ {
		go func() {
			results <- s.AcquireCancellable(2, ch)
		}()
	}

	successes := 0
	for i := 0; i < M+N; i++ {
		if <-results {
			successes++
		}
	}
	close(results)

	if successes != N {
		t.Fatalf("Expected %d AcquireCancellable calls to succeed; instead %d succeeded.", N, successes)
	}
	if v := s.Drain(); v != 1 {
		t.Errorf("Drain drained %d units when %d should have been available", v, 1)
	}
}

func TestPromptCancel(t *testing.T) {
	s := New(0)
	result := make(chan bool)
	go func() {
		result <- s.AcquireCancellable(1, nil)
	}()
	time.Sleep(time.Duration(100) * time.Millisecond)
	cancel := make(chan struct{}, 1)
	cancel <- struct{}{}
	if s.AcquireCancellable(1, cancel) == true {
		t.Fatal("AcquireCancellable succeeded on a zero-valued semaphore.")
	}
	s.Release(1)
	if <-result != true {
		t.Fatal("AcquireCancellable spuriously failed.")
	}
}

func BenchmarkSemaphore(b *testing.B) {
	const P = 10
	const UnitSize = 1000
	N := int64(b.N / UnitSize / 4)
	s := newTestSemaphore(P)
	done := make(chan struct{}, P)
	for i := 0; i < P; i++ {
		go func() {
			for atomic.AddInt64(&N, int64(-1)) > 0 {
				for j := 0; j < UnitSize; j++ {
					s.Acquire(1)
					s.Release(1)
					s.Acquire(2)
					s.Release(2)
				}
			}
			done <- struct{}{}
		}()
	}
	for i := 0; i < P; i++ {
		<-done
	}
	if err := s.Error(); err != "" {
		b.Error(err)
	}
}

func BenchmarkTimedAcquire(b *testing.B) {
	const P = 10
	const UnitSize = 1000
	N := int64(b.N / UnitSize / 4)
	s := newTestSemaphore(P)
	done := make(chan struct{}, P)
	for i := 0; i < P; i++ {
		go func() {
			for atomic.AddInt64(&N, int64(-1)) > 0 {
				for j := 0; j < UnitSize; j++ {
					if s.TimedAcquire(1, time.Duration(100)*time.Millisecond) {
						s.Release(1)
					}
					if s.TimedAcquire(2, time.Duration(100)*time.Millisecond) {
						s.Release(2)
					}
				}
			}
			done <- struct{}{}
		}()
	}
	for i := 0; i < P; i++ {
		<-done
	}
	if err := s.Error(); err != "" {
		b.Error(err)
	}
}
<|endoftext|>


<commit_before>package send

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/tychoish/grip/level"
	"github.com/tychoish/grip/message"
)

type callSiteLogger struct {
	depth  int
	logger *log.Logger
	*base
}

func NewCallSiteConsoleLogger(name string, depth int, l LevelInfo) (Sender, error) {
	s := MakeCallSiteConsoleLogger(depth)

	if err := s.SetLevel(l); err != nil {
		return nil, err
	}

	s.SetName(name)

	return s, nil
}

func MakeCallSiteConsoleLogger(depth int) Sender {
	s := &callSiteLogger{
		depth: depth,
		base:  newBase(""),
	}

	s.level = LevelInfo{level.Trace, level.Trace}

	s.reset = func() {
		s.logger = log.New(os.Stdout, strings.Join([]string{"[", s.Name(), "] "}, ""), log.LstdFlags)
	}

	return s
}

func NewCallSiteFileLogger(name, fileName string, depth int, l LevelInfo) (Sender, error) {
	s, err := MakeCallSiteFileLogger(fileName, depth)
	if err != nil {
		return nil, err
	}

	if err := s.SetLevel(l); err != nil {
		return nil, err
	}

	s.SetName(name)

	return s, nil
}

func MakeCallSiteFileLogger(fileName string, depth int) (Sender, error) {
	s := &callSiteLogger{
		depth: depth,
		base:  newBase(""),
	}

	s.level = LevelInfo{level.Trace, level.Trace}

	f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, fmt.Errorf("error opening logging file, %s", err.Error())
	}

	s.reset = func() {
		s.logger = log.New(f, strings.Join([]string{"[", s.Name(), "] "}, ""), log.LstdFlags)
	}

	s.closer = func() error {
		return f.Close()
	}

	return s, nil
}

func (s *callSiteLogger) Type() SenderType { return CallSite }
func (s *callSiteLogger) Send(m message.Composer) {
	if s.level.ShouldLog(m) {
		file, line := callerInfo(s.depth)
		s.logger.Printf("[p=%s] [%s:%d]: %s", m.Priority(), file, line, m.Resolve())
	}
}

func callerInfo(depth int) (string, int) {
	// increase depth to account for callerInfo itself.
	depth++

	// get caller info.
	_, file, line, _ := runtime.Caller(depth)

	// get the directory and filename
	dir, fileName := filepath.Split(file)
	file = filepath.Join(filepath.Base(dir), fileName)

	return file, line
}
<commit_msg>add docs for call site sender<commit_after>/*
Call Site Sender

Call site loggers provide a way to record the line number and file
name where the logging call was made, which is particularly useful in
tracing down log messages.

This sender does *not* attach this data to the Message object, and the
call site information is only logged when formatting the message
itself. Additionally the call site includes the file name and its
enclosing directory.

When constructing the Sender you must specifiy a "depth"
argument This sets the offset for the call site relative to the
Sender's Send() method. Grip's default logger (e.g. the grip.Info()
methods and friends) requires a depth of 2, while in *most* other
cases you will want to use a depth of 1. The LogMany, and
Emergency[Panic,Fatal] methods also include an extra level of
indirection.

Create a call site logger with one of the following constructors:

   NewCallSiteConsoleLogger(<name>, <depth>, <LevelInfo>)
   MakeCallSiteConsoleLogger(<depth>)
   NewCallSiteFileLogger(<name>, <fileName>, <depth>, <LevelInfo>)
   MakeCallSiteFileLogger(<fileName>, <depth>)
*/
package send

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/tychoish/grip/level"
	"github.com/tychoish/grip/message"
)

type callSiteLogger struct {
	depth  int
	logger *log.Logger
	*base
}

// NewCallSiteConsoleLogger returns a fully configured Sender
// implementation that writes log messages to standard output in a
// format that includes the filename and line number of the call site
// of the logger.
func NewCallSiteConsoleLogger(name string, depth int, l LevelInfo) (Sender, error) {
	s := MakeCallSiteConsoleLogger(depth)

	if err := s.SetLevel(l); err != nil {
		return nil, err
	}

	s.SetName(name)

	return s, nil
}

// MakeCallSiteConsoleLogger constructs an unconfigured call site
// logger that writes output to standard output. You must set the name
// of the logger using SetName or your Journaler's SetSender method
// before using this logger.
func MakeCallSiteConsoleLogger(depth int) Sender {
	s := &callSiteLogger{
		depth: depth,
		base:  newBase(""),
	}

	s.level = LevelInfo{level.Trace, level.Trace}

	s.reset = func() {
		s.logger = log.New(os.Stdout, strings.Join([]string{"[", s.Name(), "] "}, ""), log.LstdFlags)
	}

	return s
}

// NewCallSiteFileLogger returns a fully configured Sender
// implementation that writes log messages to a specified file in a
// format that includes the filename and line number of the call site
// of the logger.
func NewCallSiteFileLogger(name, fileName string, depth int, l LevelInfo) (Sender, error) {
	s, err := MakeCallSiteFileLogger(fileName, depth)
	if err != nil {
		return nil, err
	}

	if err := s.SetLevel(l); err != nil {
		return nil, err
	}

	s.SetName(name)

	return s, nil
}

// MakeCallSiteFileLogger constructs an unconfigured call site logger
// that writes output to the specified hours. You must set the name of
// the logger using SetName or your Journaler's SetSender method
// before using this logger.
func MakeCallSiteFileLogger(fileName string, depth int) (Sender, error) {
	s := &callSiteLogger{
		depth: depth,
		base:  newBase(""),
	}

	s.level = LevelInfo{level.Trace, level.Trace}

	f, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		return nil, fmt.Errorf("error opening logging file, %s", err.Error())
	}

	s.reset = func() {
		s.logger = log.New(f, strings.Join([]string{"[", s.Name(), "] "}, ""), log.LstdFlags)
	}

	s.closer = func() error {
		return f.Close()
	}

	return s, nil
}

func (s *callSiteLogger) Type() SenderType { return CallSite }
func (s *callSiteLogger) Send(m message.Composer) {
	if s.level.ShouldLog(m) {
		file, line := callerInfo(s.depth)
		s.logger.Printf("[p=%s] [%s:%d]: %s", m.Priority(), file, line, m.Resolve())
	}
}

func callerInfo(depth int) (string, int) {
	// increase depth to account for callerInfo itself.
	depth++

	// get caller info.
	_, file, line, _ := runtime.Caller(depth)

	// get the directory and filename
	dir, fileName := filepath.Split(file)
	file = filepath.Join(filepath.Base(dir), fileName)

	return file, line
}
<|endoftext|>