<commit_before>package mint

import (
	"bytes"
	"crypto/cipher"
	"fmt"
	"io"
	"sync"
)

const (
	sequenceNumberLen = 8       // sequence number length
	recordHeaderLen   = 5       // record header length
	maxFragmentLen    = 1 << 14 // max number of bytes in a record
)

// struct {
//     ContentType type;
//     ProtocolVersion record_version = { 3, 1 };    /* TLS v1.x */
//     uint16 length;
//     opaque fragment[TLSPlaintext.length];
// } TLSPlaintext;
type tlsPlaintext struct {
	// Omitted: record_version (static)
	// Omitted: length         (computed from fragment)
	contentType recordType
	fragment    []byte
}

type recordLayer struct {
	sync.Mutex

	conn     io.ReadWriter // The underlying connection
	buffer   []byte        // The next record to send
	nextData []byte        // The next record to send

	ivLength int         // Length of the seq and nonce fields
	seq      []byte      // Zero-padded sequence number
	nonce    []byte      // Buffer for per-record nonces
	cipher   cipher.AEAD // AEAD cipher
}

func newRecordLayer(conn io.ReadWriter) *recordLayer {
	r := recordLayer{}
	r.conn = conn
	r.ivLength = 0
	return &r
}

func (r *recordLayer) Rekey(cipher aeadFactory, key []byte, iv []byte) error {
	var err error
	r.cipher, err = cipher(key)
	if err != nil {
		return err
	}

	r.ivLength = len(iv)
	r.seq = bytes.Repeat([]byte{0}, r.ivLength)
	r.nonce = make([]byte, r.ivLength)
	copy(r.nonce, iv)
	return nil
}

func (r *recordLayer) incrementSequenceNumber() {
	if r.ivLength == 0 {
		return
	}

	for i := r.ivLength - 1; i > r.ivLength-sequenceNumberLen; i-- {
		r.seq[i]++
		r.nonce[i] ^= (r.seq[i] - 1) ^ r.seq[i]
		if r.seq[i] != 0 {
			return
		}
	}

	// Not allowed to let sequence number wrap.
	// Instead, must renegotiate before it does.
	// Not likely enough to bother.
	panic("TLS: sequence number wraparound")
}

func (r *recordLayer) encrypt(pt *tlsPlaintext, padLen int) *tlsPlaintext {
	// Expand the fragment to hold contentType, padding, and overhead
	originalLen := len(pt.fragment)
	plaintextLen := originalLen + 1 + padLen
	ciphertextLen := plaintextLen + r.cipher.Overhead()

	// Assemble the revised plaintext
	out := &tlsPlaintext{
		contentType: recordTypeApplicationData,
		fragment:    make([]byte, ciphertextLen),
	}
	copy(out.fragment, pt.fragment)
	out.fragment[originalLen] = byte(pt.contentType)
	for i := 1; i <= padLen; i++ {
		out.fragment[originalLen+i] = 0
	}

	// Encrypt the fragment
	payload := out.fragment[:plaintextLen]
	r.cipher.Seal(payload[:0], r.nonce, payload, nil)
	return out
}

func (r *recordLayer) decrypt(pt *tlsPlaintext) (*tlsPlaintext, int, error) {
	decryptLen := len(pt.fragment) - r.cipher.Overhead()
	out := &tlsPlaintext{
		contentType: pt.contentType,
		fragment:    make([]byte, decryptLen),
	}

	// Decrypt
	_, err := r.cipher.Open(out.fragment[:0], r.nonce, pt.fragment, nil)
	if err != nil {
		return nil, 0, fmt.Errorf("tls.record.decrypt: AEAD decrypt failed")
	}

	// Find the padding boundary
	padLen := 0
	for ; padLen < decryptLen+1 && out.fragment[decryptLen-padLen-1] == 0; padLen++ {
	}

	// Transfer the content type
	newLen := decryptLen - padLen - 1
	out.contentType = recordType(out.fragment[newLen])

	// Truncate the message to remove contentType, padding, overhead
	out.fragment = out.fragment[:newLen]
	return out, padLen, nil
}

func (r *recordLayer) readFullBuffer(data []byte) error {
	buffer := make([]byte, cap(data)+recordHeaderLen)

	var index int
	copy(buffer, r.nextData)
	index = len(r.nextData)

	for {
		m, err := r.conn.Read(buffer[index:])
		if m+index >= cap(data) {
			// TODO(bradfitz,agl): slightly suspicious
			// that we're throwing away r.Read's err here.
			copy(data[:cap(data)], buffer)
			r.nextData = buffer[cap(data) : m+index]
			return nil
		}
		if err != nil {
			return err
		}
		index = index + m
	}
}

func (r *recordLayer) ReadRecord() (*tlsPlaintext, error) {
	pt := &tlsPlaintext{}
	header := make([]byte, recordHeaderLen)
	err := r.readFullBuffer(header)
	if err != nil {
		return nil, err
	}

	// Validate content type
	switch recordType(header[0]) {
	default:
		return nil, fmt.Errorf("tls.record: Unknown content type %02x", header[0])
	case recordTypeAlert, recordTypeHandshake, recordTypeApplicationData:
		pt.contentType = recordType(header[0])
	}

	// Validate version
	if !allowWrongVersionNumber && (header[1] != 0x03 || header[2] != 0x01) {
		return nil, fmt.Errorf("tls.record: Invalid version %02x%02x", header[1], header[2])
	}

	// Validate size < max
	size := (int(header[3]) << 8) + int(header[4])
	if size > maxFragmentLen+256 {
		return nil, fmt.Errorf("tls.record: Ciphertext size too big")
	}

	// Attempt to read fragment
	pt.fragment = make([]byte, size)
	err = r.readFullBuffer(pt.fragment[:0])
	if err != nil {
		return nil, err
	}

	// Attempt to decrypt fragment
	if r.cipher != nil {
		pt, _, err = r.decrypt(pt)
		if err != nil {
			return nil, err
		}
	}

	// Check that plaintext length is not too long
	if len(pt.fragment) > maxFragmentLen {
		return nil, fmt.Errorf("tls.record: Plaintext size too big")
	}

	logf(logTypeIO, "recordLayer.ReadRecord [%d] [%x]", pt.contentType, pt.fragment)

	r.incrementSequenceNumber()
	return pt, nil
}

func (r *recordLayer) WriteRecord(pt *tlsPlaintext) error {
	return r.WriteRecordWithPadding(pt, 0)
}

func (r *recordLayer) WriteRecordWithPadding(pt *tlsPlaintext, padLen int) error {
	if r.cipher != nil {
		pt = r.encrypt(pt, padLen)
	} else if padLen > 0 {
		return fmt.Errorf("tls.record: Padding can only be done on encrypted records")
	}

	if len(pt.fragment) > maxFragmentLen {
		return fmt.Errorf("tls.record: Record size too big")
	}

	length := len(pt.fragment)
	header := []byte{byte(pt.contentType), 0x03, 0x01, byte(length >> 8), byte(length)}
	record := append(header, pt.fragment...)

	logf(logTypeIO, "recordLayer.WriteRecord [%d] [%x]", pt.contentType, pt.fragment)

	r.incrementSequenceNumber()
	_, err := r.conn.Write(record)
	return err
}
<commit_msg>Avoid panics when the remote side sends a record that is too short<commit_after>package mint

import (
	"bytes"
	"crypto/cipher"
	"fmt"
	"io"
	"sync"
)

const (
	sequenceNumberLen = 8       // sequence number length
	recordHeaderLen   = 5       // record header length
	maxFragmentLen    = 1 << 14 // max number of bytes in a record
)

// struct {
//     ContentType type;
//     ProtocolVersion record_version = { 3, 1 };    /* TLS v1.x */
//     uint16 length;
//     opaque fragment[TLSPlaintext.length];
// } TLSPlaintext;
type tlsPlaintext struct {
	// Omitted: record_version (static)
	// Omitted: length         (computed from fragment)
	contentType recordType
	fragment    []byte
}

type recordLayer struct {
	sync.Mutex

	conn     io.ReadWriter // The underlying connection
	buffer   []byte        // The next record to send
	nextData []byte        // The next record to send

	ivLength int         // Length of the seq and nonce fields
	seq      []byte      // Zero-padded sequence number
	nonce    []byte      // Buffer for per-record nonces
	cipher   cipher.AEAD // AEAD cipher
}

func newRecordLayer(conn io.ReadWriter) *recordLayer {
	r := recordLayer{}
	r.conn = conn
	r.ivLength = 0
	return &r
}

func (r *recordLayer) Rekey(cipher aeadFactory, key []byte, iv []byte) error {
	var err error
	r.cipher, err = cipher(key)
	if err != nil {
		return err
	}

	r.ivLength = len(iv)
	r.seq = bytes.Repeat([]byte{0}, r.ivLength)
	r.nonce = make([]byte, r.ivLength)
	copy(r.nonce, iv)
	return nil
}

func (r *recordLayer) incrementSequenceNumber() {
	if r.ivLength == 0 {
		return
	}

	for i := r.ivLength - 1; i > r.ivLength-sequenceNumberLen; i-- {
		r.seq[i]++
		r.nonce[i] ^= (r.seq[i] - 1) ^ r.seq[i]
		if r.seq[i] != 0 {
			return
		}
	}

	// Not allowed to let sequence number wrap.
	// Instead, must renegotiate before it does.
	// Not likely enough to bother.
	panic("TLS: sequence number wraparound")
}

func (r *recordLayer) encrypt(pt *tlsPlaintext, padLen int) *tlsPlaintext {
	// Expand the fragment to hold contentType, padding, and overhead
	originalLen := len(pt.fragment)
	plaintextLen := originalLen + 1 + padLen
	ciphertextLen := plaintextLen + r.cipher.Overhead()

	// Assemble the revised plaintext
	out := &tlsPlaintext{
		contentType: recordTypeApplicationData,
		fragment:    make([]byte, ciphertextLen),
	}
	copy(out.fragment, pt.fragment)
	out.fragment[originalLen] = byte(pt.contentType)
	for i := 1; i <= padLen; i++ {
		out.fragment[originalLen+i] = 0
	}

	// Encrypt the fragment
	payload := out.fragment[:plaintextLen]
	r.cipher.Seal(payload[:0], r.nonce, payload, nil)
	return out
}

func (r *recordLayer) decrypt(pt *tlsPlaintext) (*tlsPlaintext, int, error) {
	if len(pt.Fragment) < r.cipher.Overhead() {
		return nil, 0, fmt.Errorf("tls.record.decrypt: Record too short")
	}

	decryptLen := len(pt.fragment) - r.cipher.Overhead()
	out := &tlsPlaintext{
		contentType: pt.contentType,
		fragment:    make([]byte, decryptLen),
	}

	// Decrypt
	_, err := r.cipher.Open(out.fragment[:0], r.nonce, pt.fragment, nil)
	if err != nil {
		return nil, 0, fmt.Errorf("tls.record.decrypt: AEAD decrypt failed")
	}

	// Find the padding boundary
	padLen := 0
	for ; padLen < decryptLen+1 && out.fragment[decryptLen-padLen-1] == 0; padLen++ {
	}

	// Transfer the content type
	newLen := decryptLen - padLen - 1
	out.contentType = recordType(out.fragment[newLen])

	// Truncate the message to remove contentType, padding, overhead
	out.fragment = out.fragment[:newLen]
	return out, padLen, nil
}

func (r *recordLayer) readFullBuffer(data []byte) error {
	buffer := make([]byte, cap(data)+recordHeaderLen)

	var index int
	copy(buffer, r.nextData)
	index = len(r.nextData)

	for {
		m, err := r.conn.Read(buffer[index:])
		if m+index >= cap(data) {
			// TODO(bradfitz,agl): slightly suspicious
			// that we're throwing away r.Read's err here.
			copy(data[:cap(data)], buffer)
			r.nextData = buffer[cap(data) : m+index]
			return nil
		}
		if err != nil {
			return err
		}
		index = index + m
	}
}

func (r *recordLayer) ReadRecord() (*tlsPlaintext, error) {
	pt := &tlsPlaintext{}
	header := make([]byte, recordHeaderLen)
	err := r.readFullBuffer(header)
	if err != nil {
		return nil, err
	}

	// Validate content type
	switch recordType(header[0]) {
	default:
		return nil, fmt.Errorf("tls.record: Unknown content type %02x", header[0])
	case recordTypeAlert, recordTypeHandshake, recordTypeApplicationData:
		pt.contentType = recordType(header[0])
	}

	// Validate version
	if !allowWrongVersionNumber && (header[1] != 0x03 || header[2] != 0x01) {
		return nil, fmt.Errorf("tls.record: Invalid version %02x%02x", header[1], header[2])
	}

	// Validate size < max
	size := (int(header[3]) << 8) + int(header[4])
	if size > maxFragmentLen+256 {
		return nil, fmt.Errorf("tls.record: Ciphertext size too big")
	}

	// Attempt to read fragment
	pt.fragment = make([]byte, size)
	err = r.readFullBuffer(pt.fragment[:0])
	if err != nil {
		return nil, err
	}

	// Attempt to decrypt fragment
	if r.cipher != nil {
		pt, _, err = r.decrypt(pt)
		if err != nil {
			return nil, err
		}
	}

	// Check that plaintext length is not too long
	if len(pt.fragment) > maxFragmentLen {
		return nil, fmt.Errorf("tls.record: Plaintext size too big")
	}

	logf(logTypeIO, "recordLayer.ReadRecord [%d] [%x]", pt.contentType, pt.fragment)

	r.incrementSequenceNumber()
	return pt, nil
}

func (r *recordLayer) WriteRecord(pt *tlsPlaintext) error {
	return r.WriteRecordWithPadding(pt, 0)
}

func (r *recordLayer) WriteRecordWithPadding(pt *tlsPlaintext, padLen int) error {
	if r.cipher != nil {
		pt = r.encrypt(pt, padLen)
	} else if padLen > 0 {
		return fmt.Errorf("tls.record: Padding can only be done on encrypted records")
	}

	if len(pt.fragment) > maxFragmentLen {
		return fmt.Errorf("tls.record: Record size too big")
	}

	length := len(pt.fragment)
	header := []byte{byte(pt.contentType), 0x03, 0x01, byte(length >> 8), byte(length)}
	record := append(header, pt.fragment...)

	logf(logTypeIO, "recordLayer.WriteRecord [%d] [%x]", pt.contentType, pt.fragment)

	r.incrementSequenceNumber()
	_, err := r.conn.Write(record)
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	"github.com/UniversityofTromso/mrclean"
)

var (
	//IP:PORT of thr rpc server
	rpcserver string
	//Display component RPC server address
	displayrpc string
	//the display component rpc client
	client *rpc.Client
	//config file name
	configfile string
	//config is map of configuration options
	config map[string]string
	//netconn is the transport protocol for the connection
	netconn string
)

func init() {
	flag.StringVar(&displayrpc,
		"displayrpc", mrclean.DisplayAddr,
		"IP:PORT of the Display RPC server")
	flag.StringVar(&rpcserver,
		"rpcserver", mrclean.CoreAddr, "IP:PORT of the rpc server, defaults to localhost:32123")
	flag.StringVar(&configfile,
		"configfile", "config.json", "Configuration file for Mr. Clean")
	flag.StringVar(&netconn, "net", "tcp", "Specifies the connection protocol: tcp, udp, unix etc..")
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func main() {
	flag.Parse()
	var err error
	config, err = ReadConfig(configfile)
	if err != nil {
		log.Fatal(err)
	}
	core := &Core{Visuals: make(map[string]*mrclean.Visual)}
	go srunService(core)
	client, err = jsonrpc.Dial(netconn, displayrpc)
	if err != nil {
		log.Fatal("dialing:", err)
	}
	var (
		farg  int
		reply [2]float64
	)
	err = client.Call("Display.Size", farg, &reply)
	if err != nil {
		log.Fatal("Display error:", err)
	}
	core.DispW = reply[0]
	core.DispH = reply[1]
	log.Printf("core.DispW: %f, core.DispH: %f\n", core.DispW, core.DispH)

	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, syscall.SIGINT)
	<-sigc
}

//All method have rpc signature
type Core struct {
	Visuals      map[string]*mrclean.Visual
	DispW, DispH float64
	Lock         sync.Mutex
	// max width and height of the visuals
	mx, my float64
}

//AddVisual adds a visual received from the chronicle
func (c *Core) AddVisual(vis *mrclean.Visual, reply *int) error {
	c.Lock.Lock()
	defer c.Lock.Unlock()
	//c.Visuals[vis.Name] = vis
	*reply = 0
	log.Printf("Received visual %+v\n", vis)
	//adding visual to the display
	rvis := mrclean.Visual{
	//Origin: make([]float64, 2),
	//Size:   make([]float64, 2),
	}
	err := client.Call("Display.AddVisual", vis, &rvis)
	if err != nil {
		log.Println(err)
		//maye just return nil? chronicle cannot do much a this point
		*reply = -1
		return err
	}
	//the rpc result has the missing data so we update the visual and put it in the map
	vis.Origin = rvis.Origin
	vis.Size = rvis.Size
	vis.ID = rvis.ID
	c.Visuals[vis.Name] = vis
	log.Printf("Added visual %+v\n", vis)
	log.Println("len(Visuals) ", len(c.Visuals))
	c.mx = math.Max(c.mx, vis.Size[0])
	c.my = math.Max(c.my, rvis.Size[1])
	return nil
}

//Sort handle the gestures received from ths users to sort the visuals
func (c *Core) Sort(layersorder string, reply *int) error {
	c.Lock.Lock()
	defer c.Lock.Unlock()
	//get the the metadata
	layersconf := config["layers"]
	//split metdata in to layers
	layers := strings.Split(layersconf, string(os.PathSeparator))
	//split requested layer order
	order := strings.Split(layersorder, string(os.PathSeparator))
	//chek we are doing things correctly
	if len(layers) != len(order) {
		log.Printf("order layer and configured layer mismatch:\n%+v\n%+v\n", order, layers)
		*reply = -1
		return fmt.Errorf("sorting layers number differs from configuraion")
	}
	// we get the map of the position of each layer in
	// the sorting order
	ordermap := make(map[string]int, len(layers))
	for i, s := range order {
		ordermap[s] = i
	}
	//now check we don't have wrong layers
	for _, s := range layers {
		_, ok := ordermap[s]
		if !ok {
			log.Printf("order layer and configured layer mismatch:\n%+v\n%+v\n", order, layers)
			*reply = -1
			return fmt.Errorf("sorting layers elements differ from configuraion")
		}
	}
	//maps layer position to order position
	swap := make(map[int]int, len(layers))
	for i, l := range layers {
		swap[i] = ordermap[l]
	}

	//	var (
	//		// maximum sixe of the images, basically the imeges will be in a grid
	//		// the grid size is the size of teh biggest image plus 5cm, see later
	//		dx, dy float64
	//	)

	//get the visuals in an array with the correct meta-data
	visuals := make([]mrclean.Visual, 0, len(c.Visuals))
	for _, v := range c.Visuals {
		//strings for metadata
		metastrings := make([]string, len(layers))
		// layers in the name
		//log.Printf("splitting for metadata: %s", v.Name)
		sn := strings.Split(v.Name, string(os.PathSeparator))
		if len(sn) != len(metastrings) {
			log.Printf("WARNING: metadata and path of images are of different length, path is %d and shuld be %d\n",
				len(sn), len(metastrings))
		}
		//name layer map
		//nlm := make(map[string]int, len(layers))
		//for i, s := range sn {
		//	nlm[s] = i
		//}
		log.Printf(" len(sn) = %d len(metastrings) = %d", len(sn), len(metastrings))
		//assemble meta-data swapping position
		//according to the swap map
		for l, o := range swap {
			log.Printf("swapping metastrings[%v] = sn[%v]\n", o, l)
			metastrings[o] = sn[l]
		}
		//v.Meta = strings.Join(metastrings, "/")
		v.Meta = metastrings
		visuals = append(visuals, *v)
		//get the bigger visual to use as placeholder
		//for the displaying
		//dx = math.Max(dx, v.Size[0])
		//dy = math.Max(dy, v.Size[1])
	}
	log.Printf("sorting: len(visuals) = %v\n", len(visuals))
	//SORT
	By(metaf).Sort(visuals)
	//loop to put the visuals on screen
	// evenly spaced and sorted
	dx += 0.05 //5 cm
	dy += 0.05 //5 cm
	var (
		row     float64                = 1.0
		lastpx                         = -c.DispW*0.5 + dx*0.5
		lastpy                         = c.DispH*0.5 - dy*0.5*row
		origins *mrclean.VisualOrigins = mrclean.NewVisualOrigins()
	)
	//log.Printf("dx: %f, dy: %f\n", dx, dy)
	//log.Printf(" lastpx: %f lastpy: %f \n", lastpx, lastpy)
	for i := range visuals {
		visuals[i].Origin[0], visuals[i].Origin[1] = lastpx, lastpy
		//log.Println("Origin: ", visuals[i].Origin)
		//fmt.Println("before: ", v.rect, v.rect.Center())
		//fmt.Println("after: ", v.rect, v.rect.Center())
		origins.Vids = append(origins.Vids, visuals[i].ID)
		origins.Origins = append(origins.Origins, visuals[i].Origin)
		lastpx += dx
		//log.Printf(" lastpx+dx: %f c.DispW*0.5: %f \n", lastpx+dx, c.DispW*0.5)
		if lastpx+dx > c.DispW*0.5 {
			lastpx = -c.DispW*0.5 + dx*0.5
			row += 1
			lastpy -= dy //c.DispH*0.5 - dy*row //c.DispH - row*dy
			//log.Printf("New ROW: %f ", row)
		}
		//log.Printf(" lastpx: %f lastpy: %f \n", lastpx, lastpy)
	}
	//CALL
	var repl int = 0
	//log.Printf("calling Display.SetVisualsOrigin %v\n", origins)
	err := client.Call("Display.SetVisualsOrigin", origins, &repl)
	if err != nil {
		*reply = -1
		//log.Println("Display error setting Visuals orgin: ", err)
		return err
	}
	if repl == 0 {
		reply = &repl
	} else {
		log.Println("Something happened during Display.SetVisualsOrigin ", repl)
		*reply = -1
	}

	return nil
}

func (c *Core) Group(layer string, reply *int) error {
	return nil
}

func (c *Core) updatemetadata(layers string) error {
	//get the the metadata
	layersconf := config["layers"]
	//split metdata in to layers
	layers := strings.Split(layersconf, string(os.PathSeparator))
	//split requested layer order
	order := strings.Split(layersorder, string(os.PathSeparator))
	//chek we are doing things correctly
	if len(layers) != len(order) {
		log.Printf("order layer and configured layer mismatch:\n%+v\n%+v\n", order, layers)
		*reply = -1
		return fmt.Errorf("sorting layers number differs from configuraion")
	}
	// we get the map of the position of each layer in
	// the sorting order
	ordermap := make(map[string]int, len(layers))
	for i, s := range order {
		ordermap[s] = i
	}
	//now check we don't have wrong layers
	for _, s := range layers {
		_, ok := ordermap[s]
		if !ok {
			log.Printf("order layer and configured layer mismatch:\n%+v\n%+v\n", order, layers)
			*reply = -1
			return fmt.Errorf("sorting layers elements differ from configuraion")
		}
	}
	//maps layer position to order position
	swap := make(map[int]int, len(layers))
	for i, l := range layers {
		swap[i] = ordermap[l]
	}

	//	var (
	//		// maximum sixe of the images, basically the imeges will be in a grid
	//		// the grid size is the size of teh biggest image plus 5cm, see later
	//		dx, dy float64
	//	)

	//get the visuals in an array with the correct meta-data
	visuals := make([]mrclean.Visual, 0, len(c.Visuals))
	for _, v := range c.Visuals {
		//strings for metadata
		metastrings := make([]string, len(layers))
		// layers in the name
		//log.Printf("splitting for metadata: %s", v.Name)
		sn := strings.Split(v.Name, string(os.PathSeparator))
		if len(sn) != len(metastrings) {
			log.Printf("WARNING: metadata and path of images are of different length, path is %d and shuld be %d\n",
				len(sn), len(metastrings))
		}
		//name layer map
		//nlm := make(map[string]int, len(layers))
		//for i, s := range sn {
		//	nlm[s] = i
		//}
		log.Printf(" len(sn) = %d len(metastrings) = %d", len(sn), len(metastrings))
		//assemble meta-data swapping position
		//according to the swap map
		for l, o := range swap {
			log.Printf("swapping metastrings[%v] = sn[%v]\n", o, l)
			metastrings[o] = sn[l]
		}
		//v.Meta = strings.Join(metastrings, "/")
		v.Meta = metastrings
		visuals = append(visuals, *v)
		//get the bigger visual to use as placeholder
		//for the displaying
		//dx = math.Max(dx, v.Size[0])
		//dy = math.Max(dy, v.Size[1])
	}
}

//runs the given struct as an RPC service using JSON as encoding
func srunService(core *Core) {
	rpc.Register(core)
	l, e := net.Listen("tcp", rpcserver)
	if e != nil {
		log.Fatal("listen error:", e)
	}

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Fatal(err)
		}

		go rpc.DefaultServer.ServeCodec(jsonrpc.NewServerCodec(conn))
	}
}

func ReadConfig(fname string) (map[string]string, error) {
	file, err := os.Open(fname)
	if err != nil {
		return nil, err
	}
	buff, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, err
	}
	var config map[string]string
	err = json.Unmarshal(buff, &config)
	if err != nil {
		return nil, err
	}
	log.Printf("Got configuration: %+v\n", config)
	return config, nil
}
<commit_msg>added grouping<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"math"
	"net"
	"net/rpc"
	"net/rpc/jsonrpc"
	"os"
	"os/signal"
	"strings"
	"sync"
	"syscall"

	"github.com/UniversityofTromso/mrclean"
)

var (
	//IP:PORT of thr rpc server
	rpcserver string
	//Display component RPC server address
	displayrpc string
	//the display component rpc client
	client *rpc.Client
	//config file name
	configfile string
	//config is map of configuration options
	config map[string]string
	//netconn is the transport protocol for the connection
	netconn string
)

func init() {
	flag.StringVar(&displayrpc,
		"displayrpc", mrclean.DisplayAddr,
		"IP:PORT of the Display RPC server")
	flag.StringVar(&rpcserver,
		"rpcserver", mrclean.CoreAddr, "IP:PORT of the rpc server, defaults to localhost:32123")
	flag.StringVar(&configfile,
		"configfile", "config.json", "Configuration file for Mr. Clean")
	flag.StringVar(&netconn, "net", "tcp", "Specifies the connection protocol: tcp, udp, unix etc..")
	log.SetFlags(log.LstdFlags | log.Lshortfile)
}

func main() {
	flag.Parse()
	var err error
	config, err = ReadConfig(configfile)
	if err != nil {
		log.Fatal(err)
	}
	core := &Core{Visuals: make(map[string]*mrclean.Visual)}
	go srunService(core)
	client, err = jsonrpc.Dial(netconn, displayrpc)
	if err != nil {
		log.Fatal("dialing:", err)
	}
	var (
		farg  int
		reply [2]float64
	)
	err = client.Call("Display.Size", farg, &reply)
	if err != nil {
		log.Fatal("Display error:", err)
	}
	core.DispW = reply[0]
	core.DispH = reply[1]
	log.Printf("core.DispW: %f, core.DispH: %f\n", core.DispW, core.DispH)

	sigc := make(chan os.Signal, 1)
	signal.Notify(sigc, syscall.SIGINT)
	<-sigc
}

//All method have rpc signature
type Core struct {
	Visuals      map[string]*mrclean.Visual
	DispW, DispH float64
	Lock         sync.Mutex
	// max width and height of the visuals
	mx, my float64
}

//AddVisual adds a visual received from the chronicle
func (c *Core) AddVisual(vis *mrclean.Visual, reply *int) error {
	c.Lock.Lock()
	defer c.Lock.Unlock()
	//c.Visuals[vis.Name] = vis
	*reply = 0
	log.Printf("Received visual %+v\n", vis)
	//adding visual to the display
	rvis := mrclean.Visual{
	//Origin: make([]float64, 2),
	//Size:   make([]float64, 2),
	}
	err := client.Call("Display.AddVisual", vis, &rvis)
	if err != nil {
		log.Println(err)
		//maye just return nil? chronicle cannot do much a this point
		*reply = -1
		return err
	}
	//the rpc result has the missing data so we update the visual and put it in the map
	vis.Origin = rvis.Origin
	vis.Size = rvis.Size
	vis.ID = rvis.ID
	c.Visuals[vis.Name] = vis
	log.Printf("Added visual %+v\n", vis)
	log.Println("len(Visuals) ", len(c.Visuals))
	c.mx = math.Max(c.mx, vis.Size[0])
	c.my = math.Max(c.my, rvis.Size[1])
	return nil
}

//Sort handle the gestures received from ths users to sort the visuals
func (c *Core) Sort(layersorder string, reply *int) error {
	c.Lock.Lock()
	defer c.Lock.Unlock()
	err := c.updatemetadata(layersorder)
	if err != nil {
		return err
	}
	//need to copy the map in a slice to sort it
	//maybe this should change
	visuals := make([]mrclean.Visual, 0, len(c.Visuals))
	for _, v := range c.Visuals {
		visuals = append(visuals, *v)
	}
	log.Printf("sorting: len(visuals) = %v\n", len(visuals))
	//SORT
	By(metaf).Sort(visuals)
	//loop to put the visuals on screen
	// evenly spaced and sorted
	dx := c.mx + 0.05 //5 cm
	dy := c.my + 0.05 //5 cm
	var (
		row     float64                = 1.0
		lastpx                         = -c.DispW*0.5 + dx*0.5
		lastpy                         = c.DispH*0.5 - dy*0.5*row
		origins *mrclean.VisualOrigins = mrclean.NewVisualOrigins()
	)
	//log.Printf("dx: %f, dy: %f\n", dx, dy)
	//log.Printf(" lastpx: %f lastpy: %f \n", lastpx, lastpy)
	for i := range visuals {
		visuals[i].Origin[0], visuals[i].Origin[1] = lastpx, lastpy
		//log.Println("Origin: ", visuals[i].Origin)
		//fmt.Println("before: ", v.rect, v.rect.Center())
		//fmt.Println("after: ", v.rect, v.rect.Center())
		origins.Vids = append(origins.Vids, visuals[i].ID)
		origins.Origins = append(origins.Origins, visuals[i].Origin)
		lastpx += dx
		//log.Printf(" lastpx+dx: %f c.DispW*0.5: %f \n", lastpx+dx, c.DispW*0.5)
		if lastpx+dx > c.DispW*0.5 {
			lastpx = -c.DispW*0.5 + dx*0.5
			row += 1
			lastpy -= dy //c.DispH*0.5 - dy*row //c.DispH - row*dy
			//log.Printf("New ROW: %f ", row)
		}
		//log.Printf(" lastpx: %f lastpy: %f \n", lastpx, lastpy)
	}
	//CALL
	var repl int = 0
	//log.Printf("calling Display.SetVisualsOrigin %v\n", origins)
	err = client.Call("Display.SetVisualsOrigin", origins, &repl)
	if err != nil {
		*reply = -1
		//log.Println("Display error setting Visuals orgin: ", err)
		return err
	}
	if repl == 0 {
		reply = &repl
	} else {
		log.Println("Something happened during Display.SetVisualsOrigin ", repl)
		*reply = -1
	}

	return nil
}

func (c *Core) Group(layer string, reply *int) error {
	c.Lock.Lock()
	defer c.Lock.Unlock()
	err := c.updatemetadata(layer)
	if err != nil {
		return err
	}
	//map af array of visuals, the key is the metadata/layer
	groups := make(map[string][]*mrclean.Visual, len(c.Visuals))
	for _, v := range c.Visuals {
		groups[v.Meta[0]] = append(groups[v.Meta[0]], v)
	}
	dx := c.mx + 0.05 //5 cm
	dy := c.my + 0.05 //5 cm
	var (
		//row     float64                = 1.0
		lastpx                         = -c.DispW*0.5 + dx*0.5
		lastpy                         = c.DispH*0.5 - dy*0.5
		origins *mrclean.VisualOrigins = mrclean.NewVisualOrigins()
	)
	for _, row := range groups {
		//put row by row on screen here
		for _, v := range row {
			v.Origin[0], v.Origin[1] = lastpx, lastpy
			origins.Vids = append(origins.Vids, v.ID)
			origins.Origins = append(origins.Origins, v.Origin)
			lastpx += dx
		}
		// back to the left
		lastpx = -c.DispW*0.5 + dx*0.5
		// next row
		lastpy -= dy
	}
	//CALL
	var repl int = 0
	//log.Printf("calling Display.SetVisualsOrigin %v\n", origins)
	err = client.Call("Display.SetVisualsOrigin", origins, &repl)
	if err != nil {
		*reply = -1
		//log.Println("Display error setting Visuals orgin: ", err)
		return err
	}
	if repl == 0 {
		reply = &repl
	} else {
		log.Println("Something happened during Display.SetVisualsOrigin ", repl)
		*reply = -1
	}

	return nil
}

//update the metadata in the visuals to the recived order
func (c *Core) updatemetadata(layersorder string) error {
	//TODO add check in case we are already up to date

	//get the the metadata
	layersconf := config["layers"]
	//split metdata in to layers
	layers := strings.Split(layersconf, string(os.PathSeparator))
	//split requested layer order
	order := strings.Split(layersorder, string(os.PathSeparator))
	//chek we are doing things correctly
	if len(layers) != len(order) {
		log.Printf("order layer and configured layer mismatch:\n%+v\n%+v\n", order, layers)
		//*reply = -1
		return fmt.Errorf("sorting layers number differs from configuraion")
	}
	// we get the map of the position of each layer in
	// the sorting order
	ordermap := make(map[string]int, len(layers))
	for i, s := range order {
		ordermap[s] = i
	}
	//now check we don't have wrong layers
	for _, s := range layers {
		_, ok := ordermap[s]
		if !ok {
			log.Printf("order layer and configured layer mismatch:\n%+v\n%+v\n", order, layers)
			//*reply = -1
			return fmt.Errorf("sorting layers elements differ from configuraion")
		}
	}
	//maps layer position to order position
	swap := make(map[int]int, len(layers))
	for i, l := range layers {
		swap[i] = ordermap[l]
	}

	//	var (
	//		// maximum sixe of the images, basically the imeges will be in a grid
	//		// the grid size is the size of teh biggest image plus 5cm, see later
	//		dx, dy float64
	//	)

	//get the visuals in an array with the correct meta-data
	//visuals := make([]mrclean.Visual, 0, len(c.Visuals))
	for _, v := range c.Visuals {
		//strings for metadata
		metastrings := make([]string, len(layers))
		// layers in the name
		//log.Printf("splitting for metadata: %s", v.Name)
		sn := strings.Split(v.Name, string(os.PathSeparator))
		if len(sn) != len(metastrings) {
			log.Printf("WARNING: metadata and path of images are of different length, path is %d and shuld be %d\n",
				len(sn), len(metastrings))
		}
		//name layer map
		//nlm := make(map[string]int, len(layers))
		//for i, s := range sn {
		//	nlm[s] = i
		//}
		log.Printf(" len(sn) = %d len(metastrings) = %d", len(sn), len(metastrings))
		//assemble meta-data swapping position
		//according to the swap map
		for l, o := range swap {
			log.Printf("swapping metastrings[%v] = sn[%v]\n", o, l)
			metastrings[o] = sn[l]
		}
		//v.Meta = strings.Join(metastrings, "/")
		v.Meta = metastrings
		//visuals = append(visuals, *v)
		//get the bigger visual to use as placeholder
		//for the displaying
		//dx = math.Max(dx, v.Size[0])
		//dy = math.Max(dy, v.Size[1])
	}
	return nil
}

//runs the given struct as an RPC service using JSON as encoding
func srunService(core *Core) {
	rpc.Register(core)
	l, e := net.Listen("tcp", rpcserver)
	if e != nil {
		log.Fatal("listen error:", e)
	}

	for {
		conn, err := l.Accept()
		if err != nil {
			log.Fatal(err)
		}

		go rpc.DefaultServer.ServeCodec(jsonrpc.NewServerCodec(conn))
	}
}

func ReadConfig(fname string) (map[string]string, error) {
	file, err := os.Open(fname)
	if err != nil {
		return nil, err
	}
	buff, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, err
	}
	var config map[string]string
	err = json.Unmarshal(buff, &config)
	if err != nil {
		return nil, err
	}
	log.Printf("Got configuration: %+v\n", config)
	return config, nil
}
<|endoftext|>


<commit_before>package sarama

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io/ioutil"

	"github.com/eapache/go-xerial-snappy"
	"github.com/pierrec/lz4"
)

const recordBatchOverhead = 49

type recordsArray []*Record

func (e recordsArray) encode(pe packetEncoder) error {
	for _, r := range e {
		if err := r.encode(pe); err != nil {
			return err
		}
	}
	return nil
}

func (e recordsArray) decode(pd packetDecoder) error {
	for i := range e {
		rec := &Record{}
		if err := rec.decode(pd); err != nil {
			return err
		}
		e[i] = rec
	}
	return nil
}

type RecordBatch struct {
	FirstOffset           int64
	PartitionLeaderEpoch  int32
	Version               int8
	Codec                 CompressionCodec
	Control               bool
	LastOffsetDelta       int32
	FirstTimestamp        int64
	MaxTimestamp          int64
	ProducerID            int64
	ProducerEpoch         int16
	FirstSequence         int32
	Records               []*Record
	PartialTrailingRecord bool

	compressedRecords []byte
	recordsLen        int // uncompressed records size
}

func (b *RecordBatch) encode(pe packetEncoder) error {
	if b.Version != 2 {
		return PacketEncodingError{fmt.Sprintf("unsupported compression codec (%d)", b.Codec)}
	}
	pe.putInt64(b.FirstOffset)
	pe.push(&lengthField{})
	pe.putInt32(b.PartitionLeaderEpoch)
	pe.putInt8(b.Version)
	pe.push(newCRC32Field(crcCastagnoli))
	pe.putInt16(b.computeAttributes())
	pe.putInt32(b.LastOffsetDelta)
	pe.putInt64(b.FirstTimestamp)
	pe.putInt64(b.MaxTimestamp)
	pe.putInt64(b.ProducerID)
	pe.putInt16(b.ProducerEpoch)
	pe.putInt32(b.FirstSequence)

	if err := pe.putArrayLength(len(b.Records)); err != nil {
		return err
	}

	if b.compressedRecords != nil {
		if err := pe.putRawBytes(b.compressedRecords); err != nil {
			return err
		}
		if err := pe.pop(); err != nil {
			return err
		}
		return pe.pop()
	}

	var raw []byte
	if b.Codec != CompressionNone {
		var err error
		if raw, err = encode(recordsArray(b.Records), nil); err != nil {
			return err
		}
		b.recordsLen = len(raw)
	}
	switch b.Codec {
	case CompressionNone:
		offset := pe.offset()
		if err := recordsArray(b.Records).encode(pe); err != nil {
			return err
		}
		b.recordsLen = pe.offset() - offset
	case CompressionGZIP:
		var buf bytes.Buffer
		writer := gzip.NewWriter(&buf)
		if _, err := writer.Write(raw); err != nil {
			return err
		}
		if err := writer.Close(); err != nil {
			return err
		}
		b.compressedRecords = buf.Bytes()
	case CompressionSnappy:
		b.compressedRecords = snappy.Encode(raw)
	case CompressionLZ4:
		var buf bytes.Buffer
		writer := lz4.NewWriter(&buf)
		if _, err := writer.Write(raw); err != nil {
			return err
		}
		if err := writer.Close(); err != nil {
			return err
		}
		b.compressedRecords = buf.Bytes()
	default:
		return PacketEncodingError{fmt.Sprintf("unsupported compression codec (%d)", b.Codec)}
	}
	if err := pe.putRawBytes(b.compressedRecords); err != nil {
		return err
	}

	if err := pe.pop(); err != nil {
		return err
	}
	return pe.pop()
}

func (b *RecordBatch) decode(pd packetDecoder) (err error) {
	if b.FirstOffset, err = pd.getInt64(); err != nil {
		return err
	}

	var batchLen int32
	if batchLen, err = pd.getInt32(); err != nil {
		return err
	}

	if b.PartitionLeaderEpoch, err = pd.getInt32(); err != nil {
		return err
	}

	if b.Version, err = pd.getInt8(); err != nil {
		return err
	}

	if err = pd.push(&crc32Field{polynomial: crcCastagnoli}); err != nil {
		return err
	}

	var attributes int16
	if attributes, err = pd.getInt16(); err != nil {
		return err
	}
	b.Codec = CompressionCodec(int8(attributes) & compressionCodecMask)
	b.Control = attributes&controlMask == controlMask

	if b.LastOffsetDelta, err = pd.getInt32(); err != nil {
		return err
	}

	if b.FirstTimestamp, err = pd.getInt64(); err != nil {
		return err
	}

	if b.MaxTimestamp, err = pd.getInt64(); err != nil {
		return err
	}

	if b.ProducerID, err = pd.getInt64(); err != nil {
		return err
	}

	if b.ProducerEpoch, err = pd.getInt16(); err != nil {
		return err
	}

	if b.FirstSequence, err = pd.getInt32(); err != nil {
		return err
	}

	numRecs, err := pd.getArrayLength()
	if err != nil {
		return err
	}
	if numRecs >= 0 {
		b.Records = make([]*Record, numRecs)
	}

	bufSize := int(batchLen) - recordBatchOverhead
	recBuffer, err := pd.getRawBytes(bufSize)
	if err != nil {
		return err
	}

	if err = pd.pop(); err != nil {
		return err
	}

	switch b.Codec {
	case CompressionNone:
	case CompressionGZIP:
		reader, err := gzip.NewReader(bytes.NewReader(recBuffer))
		if err != nil {
			return err
		}
		if recBuffer, err = ioutil.ReadAll(reader); err != nil {
			return err
		}
	case CompressionSnappy:
		if recBuffer, err = snappy.Decode(recBuffer); err != nil {
			return err
		}
	case CompressionLZ4:
		reader := lz4.NewReader(bytes.NewReader(recBuffer))
		if recBuffer, err = ioutil.ReadAll(reader); err != nil {
			return err
		}
	default:
		return PacketDecodingError{fmt.Sprintf("invalid compression specified (%d)", b.Codec)}
	}

	err = decode(recBuffer, recordsArray(b.Records))
	if err == ErrInsufficientData {
		b.PartialTrailingRecord = true
		b.Records = nil
		return nil
	}
	return err
}

func (b *RecordBatch) computeAttributes() int16 {
	attr := int16(b.Codec) & int16(compressionCodecMask)
	if b.Control {
		attr |= controlMask
	}
	return attr
}

func (b *RecordBatch) addRecord(r *Record) {
	b.Records = append(b.Records, r)
}
<commit_msg>Refactor record batch encoding<commit_after>package sarama

import (
	"bytes"
	"compress/gzip"
	"fmt"
	"io/ioutil"

	"github.com/eapache/go-xerial-snappy"
	"github.com/pierrec/lz4"
)

const recordBatchOverhead = 49

type recordsArray []*Record

func (e recordsArray) encode(pe packetEncoder) error {
	for _, r := range e {
		if err := r.encode(pe); err != nil {
			return err
		}
	}
	return nil
}

func (e recordsArray) decode(pd packetDecoder) error {
	for i := range e {
		rec := &Record{}
		if err := rec.decode(pd); err != nil {
			return err
		}
		e[i] = rec
	}
	return nil
}

type RecordBatch struct {
	FirstOffset           int64
	PartitionLeaderEpoch  int32
	Version               int8
	Codec                 CompressionCodec
	Control               bool
	LastOffsetDelta       int32
	FirstTimestamp        int64
	MaxTimestamp          int64
	ProducerID            int64
	ProducerEpoch         int16
	FirstSequence         int32
	Records               []*Record
	PartialTrailingRecord bool

	compressedRecords []byte
	recordsLen        int // uncompressed records size
}

func (b *RecordBatch) encode(pe packetEncoder) error {
	if b.Version != 2 {
		return PacketEncodingError{fmt.Sprintf("unsupported compression codec (%d)", b.Codec)}
	}
	pe.putInt64(b.FirstOffset)
	pe.push(&lengthField{})
	pe.putInt32(b.PartitionLeaderEpoch)
	pe.putInt8(b.Version)
	pe.push(newCRC32Field(crcCastagnoli))
	pe.putInt16(b.computeAttributes())
	pe.putInt32(b.LastOffsetDelta)
	pe.putInt64(b.FirstTimestamp)
	pe.putInt64(b.MaxTimestamp)
	pe.putInt64(b.ProducerID)
	pe.putInt16(b.ProducerEpoch)
	pe.putInt32(b.FirstSequence)

	if err := pe.putArrayLength(len(b.Records)); err != nil {
		return err
	}

	if b.compressedRecords == nil {
		b.encodeRecords(pe)
	}
	if err := pe.putRawBytes(b.compressedRecords); err != nil {
		return err
	}

	if err := pe.pop(); err != nil {
		return err
	}
	return pe.pop()
}

func (b *RecordBatch) decode(pd packetDecoder) (err error) {
	if b.FirstOffset, err = pd.getInt64(); err != nil {
		return err
	}

	batchLen, err := pd.getInt32()
	if err != nil {
		return err
	}

	if b.PartitionLeaderEpoch, err = pd.getInt32(); err != nil {
		return err
	}

	if b.Version, err = pd.getInt8(); err != nil {
		return err
	}

	if err = pd.push(&crc32Field{polynomial: crcCastagnoli}); err != nil {
		return err
	}

	attributes, err := pd.getInt16()
	if err != nil {
		return err
	}
	b.Codec = CompressionCodec(int8(attributes) & compressionCodecMask)
	b.Control = attributes&controlMask == controlMask

	if b.LastOffsetDelta, err = pd.getInt32(); err != nil {
		return err
	}

	if b.FirstTimestamp, err = pd.getInt64(); err != nil {
		return err
	}

	if b.MaxTimestamp, err = pd.getInt64(); err != nil {
		return err
	}

	if b.ProducerID, err = pd.getInt64(); err != nil {
		return err
	}

	if b.ProducerEpoch, err = pd.getInt16(); err != nil {
		return err
	}

	if b.FirstSequence, err = pd.getInt32(); err != nil {
		return err
	}

	numRecs, err := pd.getArrayLength()
	if err != nil {
		return err
	}
	if numRecs >= 0 {
		b.Records = make([]*Record, numRecs)
	}

	bufSize := int(batchLen) - recordBatchOverhead
	recBuffer, err := pd.getRawBytes(bufSize)
	if err != nil {
		return err
	}

	if err = pd.pop(); err != nil {
		return err
	}

	switch b.Codec {
	case CompressionNone:
	case CompressionGZIP:
		reader, err := gzip.NewReader(bytes.NewReader(recBuffer))
		if err != nil {
			return err
		}
		if recBuffer, err = ioutil.ReadAll(reader); err != nil {
			return err
		}
	case CompressionSnappy:
		if recBuffer, err = snappy.Decode(recBuffer); err != nil {
			return err
		}
	case CompressionLZ4:
		reader := lz4.NewReader(bytes.NewReader(recBuffer))
		if recBuffer, err = ioutil.ReadAll(reader); err != nil {
			return err
		}
	default:
		return PacketDecodingError{fmt.Sprintf("invalid compression specified (%d)", b.Codec)}
	}

	err = decode(recBuffer, recordsArray(b.Records))
	if err == ErrInsufficientData {
		b.PartialTrailingRecord = true
		b.Records = nil
		return nil
	}
	return err
}

func (b *RecordBatch) encodeRecords(pe packetEncoder) error {
	var raw []byte
	if b.Codec != CompressionNone {
		var err error
		if raw, err = encode(recordsArray(b.Records), nil); err != nil {
			return err
		}
		b.recordsLen = len(raw)
	}

	switch b.Codec {
	case CompressionNone:
		offset := pe.offset()
		if err := recordsArray(b.Records).encode(pe); err != nil {
			return err
		}
		b.recordsLen = pe.offset() - offset
	case CompressionGZIP:
		var buf bytes.Buffer
		writer := gzip.NewWriter(&buf)
		if _, err := writer.Write(raw); err != nil {
			return err
		}
		if err := writer.Close(); err != nil {
			return err
		}
		b.compressedRecords = buf.Bytes()
	case CompressionSnappy:
		b.compressedRecords = snappy.Encode(raw)
	case CompressionLZ4:
		var buf bytes.Buffer
		writer := lz4.NewWriter(&buf)
		if _, err := writer.Write(raw); err != nil {
			return err
		}
		if err := writer.Close(); err != nil {
			return err
		}
		b.compressedRecords = buf.Bytes()
	default:
		return PacketEncodingError{fmt.Sprintf("unsupported compression codec (%d)", b.Codec)}
	}

	return nil
}

func (b *RecordBatch) computeAttributes() int16 {
	attr := int16(b.Codec) & int16(compressionCodecMask)
	if b.Control {
		attr |= controlMask
	}
	return attr
}

func (b *RecordBatch) addRecord(r *Record) {
	b.Records = append(b.Records, r)
}
<|endoftext|>


<commit_before>/*
Copyright The Helm Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"
	"io"
	"os"
	"strconv"

	"github.com/gosuri/uitable"
	"github.com/spf13/cobra"

	"helm.sh/helm/v3/cmd/helm/require"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/cli/output"
	"helm.sh/helm/v3/pkg/release"
	helmtime "helm.sh/helm/v3/pkg/time"
)

var listHelp = `
This command lists all of the releases for a specified namespace (uses current namespace context if namespace not specified).

By default, it lists only releases that are deployed or failed. Flags like
'--uninstalled' and '--all' will alter this behavior. Such flags can be combined:
'--uninstalled --failed'.

By default, items are sorted alphabetically. Use the '-d' flag to sort by
release date.

If the --filter flag is provided, it will be treated as a filter. Filters are
regular expressions (Perl compatible) that are applied to the list of releases.
Only items that match the filter will be returned.

    $ helm list --filter 'ara[a-z]+'
    NAME                UPDATED                         CHART
    maudlin-arachnid    1977-09-02 22:04:05 +0000 UTC   alpine-0.1.0

If no results are found, 'helm list' will exit 0, but with no output (or in
the case of no '-q' flag, only headers).

By default, up to 256 items may be returned. To limit this, use the '--max' flag.
Setting '--max' to 0 will not return all results. Rather, it will return the
server's default, which may be much higher than 256. Pairing the '--max'
flag with the '--offset' flag allows you to page through results.
`

func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
	client := action.NewList(cfg)
	var outfmt output.Format

	cmd := &cobra.Command{
		Use:               "list",
		Short:             "list releases",
		Long:              listHelp,
		Aliases:           []string{"ls"},
		Args:              require.NoArgs,
		ValidArgsFunction: noCompletions,
		RunE: func(cmd *cobra.Command, args []string) error {
			if client.AllNamespaces {
				if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil {
					return err
				}
			}
			client.SetStateMask()

			results, err := client.Run()
			if err != nil {
				return err
			}

			if client.Short {

				names := make([]string, 0)
				for _, res := range results {
					names = append(names, res.Name)
				}

				outputFlag := cmd.Flag("output")

				switch outputFlag.Value.String() {
				case "json":
					output.EncodeJSON(out, names)
					return nil
				case "yaml":
					output.EncodeYAML(out, names)
					return nil
				case "table":
					for _, res := range results {
						fmt.Fprintln(out, res.Name)
					}
					return nil
				default:
					return outfmt.Write(out, newReleaseListWriter(results, client.FormatTime))
				}
			}

			return outfmt.Write(out, newReleaseListWriter(results, client.FormatTime))
		},
	}

	f := cmd.Flags()
	f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
	f.BoolVar(&client.FormatTime, "format-time", false, "format time")
	f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
	f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order")
	f.BoolVarP(&client.All, "all", "a", false, "show all releases without any filter applied")
	f.BoolVar(&client.Uninstalled, "uninstalled", false, "show uninstalled releases (if 'helm uninstall --keep-history' was used)")
	f.BoolVar(&client.Superseded, "superseded", false, "show superseded releases")
	f.BoolVar(&client.Uninstalling, "uninstalling", false, "show releases that are currently being uninstalled")
	f.BoolVar(&client.Deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled")
	f.BoolVar(&client.Failed, "failed", false, "show failed releases")
	f.BoolVar(&client.Pending, "pending", false, "show pending releases")
	f.BoolVarP(&client.AllNamespaces, "all-namespaces", "A", false, "list releases across all namespaces")
	f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch")
	f.IntVar(&client.Offset, "offset", 0, "next release name in the list, used to offset from start value")
	f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results")
	f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.")
	bindOutputFlag(cmd, &outfmt)

	return cmd
}

type releaseElement struct {
	Name       string `json:"name"`
	Namespace  string `json:"namespace"`
	Revision   string `json:"revision"`
	Updated    string `json:"updated"`
	Status     string `json:"status"`
	Chart      string `json:"chart"`
	AppVersion string `json:"app_version"`
}

type releaseListWriter struct {
	releases []releaseElement
}

func newReleaseListWriter(releases []*release.Release, formatTime bool) *releaseListWriter {
	// Initialize the array so no results returns an empty array instead of null
	elements := make([]releaseElement, 0, len(releases))
	for _, r := range releases {
		var element releaseElement

		if formatTime {
			element = timeFormattedElement(r)
		} else {
			element = releaseElement{
				Name:       r.Name,
				Namespace:  r.Namespace,
				Revision:   strconv.Itoa(r.Version),
				Status:     r.Info.Status.String(),
				Chart:      fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version),
				AppVersion: r.Chart.Metadata.AppVersion,
			}
			t := "-"
			if tspb := r.Info.LastDeployed; !tspb.IsZero() {
				t = tspb.String()
			}
			element.Updated = t
		}

		elements = append(elements, element)
	}
	return &releaseListWriter{elements}
}

func timeFormattedElement(r *release.Release) releaseElement {
	t := "-"
	if tspb := r.Info.LastDeployed; !tspb.IsZero() {
		t = helmtime.Format(tspb)
	}
	return releaseElement{
		Name:       r.Name,
		Namespace:  r.Namespace,
		Revision:   strconv.Itoa(r.Version),
		Updated:    t,
		Status:     r.Info.Status.String(),
		Chart:      fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version),
		AppVersion: r.Chart.Metadata.AppVersion,
	}
}

func (r *releaseListWriter) WriteTable(out io.Writer) error {
	table := uitable.New()
	table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION")
	for _, r := range r.releases {
		table.AddRow(r.Name, r.Namespace, r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion)
	}
	return output.EncodeTable(out, table)
}

func (r *releaseListWriter) WriteJSON(out io.Writer) error {
	return output.EncodeJSON(out, r.releases)
}

func (r *releaseListWriter) WriteYAML(out io.Writer) error {
	return output.EncodeYAML(out, r.releases)
}

// Provide dynamic auto-completion for release names
func compListReleases(toComplete string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) {
	cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug)

	client := action.NewList(cfg)
	client.All = true
	client.Limit = 0
	client.Filter = fmt.Sprintf("^%s", toComplete)

	client.SetStateMask()
	results, err := client.Run()
	if err != nil {
		return nil, cobra.ShellCompDirectiveDefault
	}

	var choices []string
	for _, res := range results {
		choices = append(choices, res.Name)
	}

	return choices, cobra.ShellCompDirectiveNoFileComp
}
<commit_msg>fix ls command example<commit_after>/*
Copyright The Helm Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"
	"io"
	"os"
	"strconv"

	"github.com/gosuri/uitable"
	"github.com/spf13/cobra"

	"helm.sh/helm/v3/cmd/helm/require"
	"helm.sh/helm/v3/pkg/action"
	"helm.sh/helm/v3/pkg/cli/output"
	"helm.sh/helm/v3/pkg/release"
	helmtime "helm.sh/helm/v3/pkg/time"
)

var listHelp = `
This command lists all of the releases for a specified namespace (uses current namespace context if namespace not specified).

By default, it lists only releases that are deployed or failed. Flags like
'--uninstalled' and '--all' will alter this behavior. Such flags can be combined:
'--uninstalled --failed'.

By default, items are sorted alphabetically. Use the '-d' flag to sort by
release date.

If the --filter flag is provided, it will be treated as a filter. Filters are
regular expressions (Perl compatible) that are applied to the list of releases.
Only items that match the filter will be returned.

    $ helm list --filter 'ara[a-z]+'
    NAME                UPDATED                               CHART
    maudlin-arachnid    1977-09-02 22:04:05.46104 +0000 UTC   alpine-0.1.0

If no results are found, 'helm list' will exit 0, but with no output (or in
the case of no '-q' flag, only headers).

By default, up to 256 items may be returned. To limit this, use the '--max' flag.
Setting '--max' to 0 will not return all results. Rather, it will return the
server's default, which may be much higher than 256. Pairing the '--max'
flag with the '--offset' flag allows you to page through results.
`

func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
	client := action.NewList(cfg)
	var outfmt output.Format

	cmd := &cobra.Command{
		Use:               "list",
		Short:             "list releases",
		Long:              listHelp,
		Aliases:           []string{"ls"},
		Args:              require.NoArgs,
		ValidArgsFunction: noCompletions,
		RunE: func(cmd *cobra.Command, args []string) error {
			if client.AllNamespaces {
				if err := cfg.Init(settings.RESTClientGetter(), "", os.Getenv("HELM_DRIVER"), debug); err != nil {
					return err
				}
			}
			client.SetStateMask()

			results, err := client.Run()
			if err != nil {
				return err
			}

			if client.Short {

				names := make([]string, 0)
				for _, res := range results {
					names = append(names, res.Name)
				}

				outputFlag := cmd.Flag("output")

				switch outputFlag.Value.String() {
				case "json":
					output.EncodeJSON(out, names)
					return nil
				case "yaml":
					output.EncodeYAML(out, names)
					return nil
				case "table":
					for _, res := range results {
						fmt.Fprintln(out, res.Name)
					}
					return nil
				default:
					return outfmt.Write(out, newReleaseListWriter(results, client.FormatTime))
				}
			}

			return outfmt.Write(out, newReleaseListWriter(results, client.FormatTime))
		},
	}

	f := cmd.Flags()
	f.BoolVarP(&client.Short, "short", "q", false, "output short (quiet) listing format")
	f.BoolVar(&client.FormatTime, "format-time", false, "format time")
	f.BoolVarP(&client.ByDate, "date", "d", false, "sort by release date")
	f.BoolVarP(&client.SortReverse, "reverse", "r", false, "reverse the sort order")
	f.BoolVarP(&client.All, "all", "a", false, "show all releases without any filter applied")
	f.BoolVar(&client.Uninstalled, "uninstalled", false, "show uninstalled releases (if 'helm uninstall --keep-history' was used)")
	f.BoolVar(&client.Superseded, "superseded", false, "show superseded releases")
	f.BoolVar(&client.Uninstalling, "uninstalling", false, "show releases that are currently being uninstalled")
	f.BoolVar(&client.Deployed, "deployed", false, "show deployed releases. If no other is specified, this will be automatically enabled")
	f.BoolVar(&client.Failed, "failed", false, "show failed releases")
	f.BoolVar(&client.Pending, "pending", false, "show pending releases")
	f.BoolVarP(&client.AllNamespaces, "all-namespaces", "A", false, "list releases across all namespaces")
	f.IntVarP(&client.Limit, "max", "m", 256, "maximum number of releases to fetch")
	f.IntVar(&client.Offset, "offset", 0, "next release name in the list, used to offset from start value")
	f.StringVarP(&client.Filter, "filter", "f", "", "a regular expression (Perl compatible). Any releases that match the expression will be included in the results")
	f.StringVarP(&client.Selector, "selector", "l", "", "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2). Works only for secret(default) and configmap storage backends.")
	bindOutputFlag(cmd, &outfmt)

	return cmd
}

type releaseElement struct {
	Name       string `json:"name"`
	Namespace  string `json:"namespace"`
	Revision   string `json:"revision"`
	Updated    string `json:"updated"`
	Status     string `json:"status"`
	Chart      string `json:"chart"`
	AppVersion string `json:"app_version"`
}

type releaseListWriter struct {
	releases []releaseElement
}

func newReleaseListWriter(releases []*release.Release, formatTime bool) *releaseListWriter {
	// Initialize the array so no results returns an empty array instead of null
	elements := make([]releaseElement, 0, len(releases))
	for _, r := range releases {
		var element releaseElement

		if formatTime {
			element = timeFormattedElement(r)
		} else {
			element = releaseElement{
				Name:       r.Name,
				Namespace:  r.Namespace,
				Revision:   strconv.Itoa(r.Version),
				Status:     r.Info.Status.String(),
				Chart:      fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version),
				AppVersion: r.Chart.Metadata.AppVersion,
			}
			t := "-"
			if tspb := r.Info.LastDeployed; !tspb.IsZero() {
				t = tspb.String()
			}
			element.Updated = t
		}

		elements = append(elements, element)
	}
	return &releaseListWriter{elements}
}

func timeFormattedElement(r *release.Release) releaseElement {
	t := "-"
	if tspb := r.Info.LastDeployed; !tspb.IsZero() {
		t = helmtime.Format(tspb)
	}
	return releaseElement{
		Name:       r.Name,
		Namespace:  r.Namespace,
		Revision:   strconv.Itoa(r.Version),
		Updated:    t,
		Status:     r.Info.Status.String(),
		Chart:      fmt.Sprintf("%s-%s", r.Chart.Metadata.Name, r.Chart.Metadata.Version),
		AppVersion: r.Chart.Metadata.AppVersion,
	}
}

func (r *releaseListWriter) WriteTable(out io.Writer) error {
	table := uitable.New()
	table.AddRow("NAME", "NAMESPACE", "REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION")
	for _, r := range r.releases {
		table.AddRow(r.Name, r.Namespace, r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion)
	}
	return output.EncodeTable(out, table)
}

func (r *releaseListWriter) WriteJSON(out io.Writer) error {
	return output.EncodeJSON(out, r.releases)
}

func (r *releaseListWriter) WriteYAML(out io.Writer) error {
	return output.EncodeYAML(out, r.releases)
}

// Provide dynamic auto-completion for release names
func compListReleases(toComplete string, cfg *action.Configuration) ([]string, cobra.ShellCompDirective) {
	cobra.CompDebugln(fmt.Sprintf("compListReleases with toComplete %s", toComplete), settings.Debug)

	client := action.NewList(cfg)
	client.All = true
	client.Limit = 0
	client.Filter = fmt.Sprintf("^%s", toComplete)

	client.SetStateMask()
	results, err := client.Run()
	if err != nil {
		return nil, cobra.ShellCompDirectiveDefault
	}

	var choices []string
	for _, res := range results {
		choices = append(choices, res.Name)
	}

	return choices, cobra.ShellCompDirectiveNoFileComp
}
<|endoftext|>


<commit_before>/*
Package redis adds built-in Redis support with our common configuration.
This will ensure that Go services using "github.com/skuid/spec" are in compliance with our infrastructure expectations.

The package provides two utilities for using Redis.
NewStandardRedisClient generates a full-featured Redis client, with preconfigured connection settings from our spec.
NewStandardRedisCache generates a more limited Redis cache interface whcih will automatically marshal
	and unmarshal into msgpack, also preconfigured with connection settings from our spec.
*/
package redis

import (
	"os"

	"github.com/go-redis/cache"
	"github.com/go-redis/redis"
	"gopkg.in/vmihailenco/msgpack.v2"
)

var redisHost string

func init() {
	redisHost = os.Getenv("REDIS_HOST")
}

// NewStandardRedisClient generates a preconfigured redis client according to our spec.
// Accepts an *redis.Options object, and overrides the Addr field to use `$REDIS_HOST:6379` instead
func NewStandardRedisClient(options *redis.Options) *redis.Client {
	options.Addr = redisHost + ":6379"
	return redis.NewClient(options)
}

// NewStandardRedisCache generates a preconfigured redis cache according to our spec, using msgpack for serialization format.
// Accepts an *redis.Options object, and overrides the Addr field to use `$REDIS_HOST:6379` instead
func NewStandardRedisCache(options *redis.Options) *cache.Codec {
	return &cache.Codec{
		Redis: NewStandardRedisClient(options),
		Marshal: func(v interface{}) ([]byte, error) {
			return msgpack.Marshal(v)
		},
		Unmarshal: func(b []byte, v interface{}) error {
			return msgpack.Unmarshal(b, v)
		},
	}
}
<commit_msg>fixed docstring indentation<commit_after>/*
Package redis adds built-in Redis support with our common configuration.
This will ensure that Go services using "github.com/skuid/spec" are in compliance with our infrastructure expectations.

The package provides two utilities for using Redis.
NewStandardRedisClient generates a full-featured Redis client, with preconfigured connection settings from our spec.
NewStandardRedisCache generates a more limited Redis cache interface whcih will automatically marshal
and unmarshal into msgpack, also preconfigured with connection settings from our spec.
*/
package redis

import (
	"os"

	"github.com/go-redis/cache"
	"github.com/go-redis/redis"
	"gopkg.in/vmihailenco/msgpack.v2"
)

var redisHost string

func init() {
	redisHost = os.Getenv("REDIS_HOST")
}

// NewStandardRedisClient generates a preconfigured redis client according to our spec.
// Accepts an *redis.Options object, and overrides the Addr field to use `$REDIS_HOST:6379` instead
func NewStandardRedisClient(options *redis.Options) *redis.Client {
	options.Addr = redisHost + ":6379"
	return redis.NewClient(options)
}

// NewStandardRedisCache generates a preconfigured redis cache according to our spec, using msgpack for serialization format.
// Accepts an *redis.Options object, and overrides the Addr field to use `$REDIS_HOST:6379` instead
func NewStandardRedisCache(options *redis.Options) *cache.Codec {
	return &cache.Codec{
		Redis: NewStandardRedisClient(options),
		Marshal: func(v interface{}) ([]byte, error) {
			return msgpack.Marshal(v)
		},
		Unmarshal: func(b []byte, v interface{}) error {
			return msgpack.Unmarshal(b, v)
		},
	}
}
<|endoftext|>


<commit_before>package cmd

import (
	"log"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/acarl005/stripansi"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	gitlab "github.com/xanzy/go-gitlab"
	"github.com/zaquestion/lab/internal/copy"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

var labBinaryPath string

func TestMain(m *testing.M) {
	rand.Seed(time.Now().UnixNano())
	// Build a lab binary with test symbols. If the parent test binary was run
	// with coverage enabled, enable coverage on the child binary, too.
	var err error
	labBinaryPath, err = filepath.Abs(os.ExpandEnv("$GOPATH/src/github.com/zaquestion/lab/testdata/" + labBinary))
	if err != nil {
		log.Fatal(err)
	}
	testCmd := []string{"test", "-c", "-o", labBinaryPath, "github.com/zaquestion/lab"}
	if coverMode := testing.CoverMode(); coverMode != "" {
		testCmd = append(testCmd, "-covermode", coverMode, "-coverpkg", "./...")
	}
	if out, err := exec.Command("go", testCmd...).CombinedOutput(); err != nil {
		log.Fatalf("Error building lab test binary: %s (%s)", string(out), err)
	}

	originalWd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	// Make a copy of the testdata Git test project and chdir to it.
	repo := copyTestRepo(log.New(os.Stderr, "", log.LstdFlags))
	if err := os.Chdir(repo); err != nil {
		log.Fatalf("Error chdir to testdata: %s", err)
	}
	// Load config for non-testbinary based tests
	viper.SetConfigName("lab")
	viper.SetConfigType("toml")
	viper.AddConfigPath(".")
	err = viper.ReadInConfig()
	if err != nil {
		log.Fatal(err)
	}
	host := viper.GetString("core.host")
	token := viper.GetString("core.token")

	client, _ := gitlab.NewClient(token, gitlab.WithBaseURL(host+"/api/v4"))
	u, _, err := client.Users.CurrentUser()
	if err != nil {
		log.Fatal(err)
	}
	lab.Init(host, u.Username, token, false)

	// Make "origin" the default remote for test cases calling
	// cmd.Run() directly, instead of launching the labBinaryPath
	// for getting these vars correctly set through Execute().
	forkedFromRemote = "origin"
	forkRemote = "origin"
	code := m.Run()

	if err := os.Chdir(originalWd); err != nil {
		log.Fatalf("Error chdir to original working dir: %s", err)
	}
	os.Remove(labBinaryPath)
	testdirs, err := filepath.Glob(os.ExpandEnv("$GOPATH/src/github.com/zaquestion/lab/testdata-*"))
	if err != nil {
		log.Printf("Error listing glob testdata-*: %s", err)
	}
	for _, dir := range testdirs {
		err := os.RemoveAll(dir)
		if err != nil {
			log.Printf("Error removing dir %s: %s", dir, err)
		}
	}

	os.Exit(code)
}

func TestRootCloneNoArg(t *testing.T) {
	cmd := exec.Command(labBinaryPath, "clone")
	b, _ := cmd.CombinedOutput()
	require.Contains(t, string(b), "You must specify a repository to clone.")
}

func TestRootGitCmd(t *testing.T) {
	cmd := exec.Command(labBinaryPath, "log", "-n", "1")
	b, _ := cmd.CombinedOutput()
	require.Contains(t, string(b), `commit 09b519cba018b707c98fc56e37df15806d89d866
Author: Zaq? Wiedmann <zaquestion@gmail.com>
Date:   Sun Apr 1 19:40:47 2018 -0700

    (ci) jobs with interleaved sleeps and prints`)
}

func TestRootNoArg(t *testing.T) {
	cmd := exec.Command(labBinaryPath)
	b, _ := cmd.CombinedOutput()
	assert.Contains(t, string(b), "usage: git [--version] [--help] [-C <path>]")
	assert.Contains(t, string(b), `These GitLab commands are provided by lab:

  ci            Work with GitLab CI pipelines and jobs`)
}

func TestGitHelp(t *testing.T) {
	cmd := exec.Command(labBinaryPath)
	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatal(err)
	}
	expected := string(b)
	expected = expected[:strings.LastIndex(strings.TrimSpace(expected), "\n")]

	tests := []struct {
		desc string
		Cmds []string
	}{
		{
			desc: "help arg",
			Cmds: []string{"help"},
		},
		{
			desc: "no arg",
			Cmds: []string{},
		},
	}

	for _, test := range tests {
		t.Run(test.desc, func(t *testing.T) {
			cmd := exec.Command(labBinaryPath)
			if len(test.Cmds) >= 1 {
				cmd = exec.Command(labBinaryPath, test.Cmds...)
			}
			b, _ := cmd.CombinedOutput()
			res := string(b)
			res = res[:strings.LastIndex(strings.TrimSpace(res), "\n")]
			t.Log(expected)
			t.Log(res)
			assert.Equal(t, expected, res)
			assert.Contains(t, res, "usage: git [--version] [--help] [-C <path>]")
			assert.Contains(t, res, `These GitLab commands are provided by lab:

  ci            Work with GitLab CI pipelines and jobs`)
		})
	}
}

type fatalLogger interface {
	Fatal(...interface{})
}

// copyTestRepo creates a copy of the testdata directory (contains a Git repo) in
// the project root with a random dir name. It returns the absolute path of the
// new testdata dir.
// Note: testdata-* must be in the .gitignore or the copies will create write
// errors as Git attempts to add the Git repo to the the project repo's index.
func copyTestRepo(log fatalLogger) string {
	dst, err := filepath.Abs(os.ExpandEnv("$GOPATH/src/github.com/zaquestion/lab/testdata-" + strconv.Itoa(int(rand.Uint64()))))
	if err != nil {
		log.Fatal(err)
	}
	src, err := filepath.Abs(os.ExpandEnv("$GOPATH/src/github.com/zaquestion/lab/testdata"))
	if err != nil {
		log.Fatal(err)
	}
	if err := copy.Copy(src, dst); err != nil {
		log.Fatal(err)
	}
	// Move the test.git dir into the expected path at .git
	if err := os.Rename(dst+"/test.git", dst+"/.git"); err != nil {
		log.Fatal(err)
	}
	return dst
}

// getAppOutput splits and truncates the list of strings returned from the "lab"
// test binary to remove the test-specific output. It use "PASS" as a marker for
// the end of the app output and the beginning of the test output.
func getAppOutput(output []byte) []string {
	lines := strings.Split(string(output), "\n")
	for i, line := range lines {
		if line == "PASS" {
			return lines[:i]
		}
	}
	return lines
}

func setConfigValues(repo string, configVal string, gitVal string) error {
	err := os.Rename(repo+"/lab.toml", "/home/travis/.config/lab/lab.toml")
	if err != nil {
		log.Println(err)
		return err
	}

	configfile, err := os.OpenFile("/home/travis/.config/lab/lab.toml", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatal(err)
	}

	if _, err := configfile.WriteString("\n[mr_show]\n  comments = " + configVal + "\n"); err != nil {
		log.Fatal(err)
	}
	configfile.Close()

	err = os.Mkdir(repo+"/.git/lab/", 0700)
	if err != nil {
		log.Fatal(err)
	}
	gitfile, err := os.OpenFile(repo+"/.git/lab/lab.toml", os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatal(err)
	}

	if _, err = gitfile.WriteString("\n[mr_show]\n  comments = " + gitVal + "\n"); err != nil {
		log.Fatal(err)
	}
	gitfile.Close()

	return nil
}

// There isn't a really good way to test the config override
// infrastruture, so just call 'mr show' and set 'mr_show.comments'
func Test_config_gitConfig_FF(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "false", "false")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// both configs set to false, comments should not be output
	require.NotContains(t, string(b), `commented at`)
}

func Test_config_gitConfig_FT(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "false", "true")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// .config set to false and .git set to true, comments should be
	// output
	require.Contains(t, string(b), `commented at`)
}

func Test_config_gitConfig_TF(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "true", "false")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// .config set to true and .git set to false, comments should not be
	// output
	require.NotContains(t, string(b), `commented at`)
}

func Test_config_gitConfig_TT(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "true", "true")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// both configs set to true, comments should be output
	require.Contains(t, string(b), `commented at`)
}

// Some flag and config tests do not have to be run.
// flag not set, config true == comments
//   This case is handled by Test_config_gitConfig_TT
// flag not set, config false == no comments
//   This case is handled by Test_config_gitConfig_FF
// flag not set, config not set == no comments
// flag set, config not set == comments
//   These case are handled in cmd/mr_show_test.go

// flag set, config true == comments
func Test_flag_config_TT(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "true", "true")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1", "--comments")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// both configs set to true, comments should be output
	require.Contains(t, string(b), `commented at`)
}

// flag set, config false == comments
func Test_flag_config_TF(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "false", "false")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1", "--comments")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// both configs set to true, comments should be output
	require.Contains(t, string(b), `commented at`)
}
<commit_msg>root: fix test code to follow new help behavior<commit_after>package cmd

import (
	"log"
	"math/rand"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/acarl005/stripansi"
	"github.com/spf13/viper"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	gitlab "github.com/xanzy/go-gitlab"
	"github.com/zaquestion/lab/internal/copy"
	lab "github.com/zaquestion/lab/internal/gitlab"
)

var labBinaryPath string

func TestMain(m *testing.M) {
	rand.Seed(time.Now().UnixNano())
	// Build a lab binary with test symbols. If the parent test binary was run
	// with coverage enabled, enable coverage on the child binary, too.
	var err error
	labBinaryPath, err = filepath.Abs(os.ExpandEnv("$GOPATH/src/github.com/zaquestion/lab/testdata/" + labBinary))
	if err != nil {
		log.Fatal(err)
	}
	testCmd := []string{"test", "-c", "-o", labBinaryPath, "github.com/zaquestion/lab"}
	if coverMode := testing.CoverMode(); coverMode != "" {
		testCmd = append(testCmd, "-covermode", coverMode, "-coverpkg", "./...")
	}
	if out, err := exec.Command("go", testCmd...).CombinedOutput(); err != nil {
		log.Fatalf("Error building lab test binary: %s (%s)", string(out), err)
	}

	originalWd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	// Make a copy of the testdata Git test project and chdir to it.
	repo := copyTestRepo(log.New(os.Stderr, "", log.LstdFlags))
	if err := os.Chdir(repo); err != nil {
		log.Fatalf("Error chdir to testdata: %s", err)
	}
	// Load config for non-testbinary based tests
	viper.SetConfigName("lab")
	viper.SetConfigType("toml")
	viper.AddConfigPath(".")
	err = viper.ReadInConfig()
	if err != nil {
		log.Fatal(err)
	}
	host := viper.GetString("core.host")
	token := viper.GetString("core.token")

	client, _ := gitlab.NewClient(token, gitlab.WithBaseURL(host+"/api/v4"))
	u, _, err := client.Users.CurrentUser()
	if err != nil {
		log.Fatal(err)
	}
	lab.Init(host, u.Username, token, false)

	// Make "origin" the default remote for test cases calling
	// cmd.Run() directly, instead of launching the labBinaryPath
	// for getting these vars correctly set through Execute().
	forkedFromRemote = "origin"
	forkRemote = "origin"
	code := m.Run()

	if err := os.Chdir(originalWd); err != nil {
		log.Fatalf("Error chdir to original working dir: %s", err)
	}
	os.Remove(labBinaryPath)
	testdirs, err := filepath.Glob(os.ExpandEnv("$GOPATH/src/github.com/zaquestion/lab/testdata-*"))
	if err != nil {
		log.Printf("Error listing glob testdata-*: %s", err)
	}
	for _, dir := range testdirs {
		err := os.RemoveAll(dir)
		if err != nil {
			log.Printf("Error removing dir %s: %s", dir, err)
		}
	}

	os.Exit(code)
}

func TestRootCloneNoArg(t *testing.T) {
	cmd := exec.Command(labBinaryPath, "clone")
	b, _ := cmd.CombinedOutput()
	require.Contains(t, string(b), "You must specify a repository to clone.")
}

func TestRootGitCmd(t *testing.T) {
	cmd := exec.Command(labBinaryPath, "log", "-n", "1")
	b, _ := cmd.CombinedOutput()
	require.Contains(t, string(b), `commit 09b519cba018b707c98fc56e37df15806d89d866
Author: Zaq? Wiedmann <zaquestion@gmail.com>
Date:   Sun Apr 1 19:40:47 2018 -0700

    (ci) jobs with interleaved sleeps and prints`)
}

func TestRootNoArg(t *testing.T) {
	cmd := exec.Command(labBinaryPath)
	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Fatal(err)
	}
	assert.Contains(t, string(b), `A Git Wrapper for GitLab

Usage:
  lab [flags]
  lab [command]`)
}

func TestRootHelp(t *testing.T) {
	cmd := exec.Command(labBinaryPath, "help")
	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Fatal(err)
	}
	res := string(b)
	assert.Contains(t, res, `Show the help for lab

Usage:
  lab help [command [subcommand...]] [flags]`)
}

type fatalLogger interface {
	Fatal(...interface{})
}

// copyTestRepo creates a copy of the testdata directory (contains a Git repo) in
// the project root with a random dir name. It returns the absolute path of the
// new testdata dir.
// Note: testdata-* must be in the .gitignore or the copies will create write
// errors as Git attempts to add the Git repo to the the project repo's index.
func copyTestRepo(log fatalLogger) string {
	dst, err := filepath.Abs(os.ExpandEnv("$GOPATH/src/github.com/zaquestion/lab/testdata-" + strconv.Itoa(int(rand.Uint64()))))
	if err != nil {
		log.Fatal(err)
	}
	src, err := filepath.Abs(os.ExpandEnv("$GOPATH/src/github.com/zaquestion/lab/testdata"))
	if err != nil {
		log.Fatal(err)
	}
	if err := copy.Copy(src, dst); err != nil {
		log.Fatal(err)
	}
	// Move the test.git dir into the expected path at .git
	if err := os.Rename(dst+"/test.git", dst+"/.git"); err != nil {
		log.Fatal(err)
	}
	return dst
}

// getAppOutput splits and truncates the list of strings returned from the "lab"
// test binary to remove the test-specific output. It use "PASS" as a marker for
// the end of the app output and the beginning of the test output.
func getAppOutput(output []byte) []string {
	lines := strings.Split(string(output), "\n")
	for i, line := range lines {
		if line == "PASS" {
			return lines[:i]
		}
	}
	return lines
}

func setConfigValues(repo string, configVal string, gitVal string) error {
	err := os.Rename(repo+"/lab.toml", "/home/travis/.config/lab/lab.toml")
	if err != nil {
		log.Println(err)
		return err
	}

	configfile, err := os.OpenFile("/home/travis/.config/lab/lab.toml", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatal(err)
	}

	if _, err := configfile.WriteString("\n[mr_show]\n  comments = " + configVal + "\n"); err != nil {
		log.Fatal(err)
	}
	configfile.Close()

	err = os.Mkdir(repo+"/.git/lab/", 0700)
	if err != nil {
		log.Fatal(err)
	}
	gitfile, err := os.OpenFile(repo+"/.git/lab/lab.toml", os.O_CREATE|os.O_WRONLY, 0644)
	if err != nil {
		log.Fatal(err)
	}

	if _, err = gitfile.WriteString("\n[mr_show]\n  comments = " + gitVal + "\n"); err != nil {
		log.Fatal(err)
	}
	gitfile.Close()

	return nil
}

// There isn't a really good way to test the config override
// infrastruture, so just call 'mr show' and set 'mr_show.comments'
func Test_config_gitConfig_FF(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "false", "false")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// both configs set to false, comments should not be output
	require.NotContains(t, string(b), `commented at`)
}

func Test_config_gitConfig_FT(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "false", "true")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// .config set to false and .git set to true, comments should be
	// output
	require.Contains(t, string(b), `commented at`)
}

func Test_config_gitConfig_TF(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "true", "false")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// .config set to true and .git set to false, comments should not be
	// output
	require.NotContains(t, string(b), `commented at`)
}

func Test_config_gitConfig_TT(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "true", "true")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// both configs set to true, comments should be output
	require.Contains(t, string(b), `commented at`)
}

// Some flag and config tests do not have to be run.
// flag not set, config true == comments
//   This case is handled by Test_config_gitConfig_TT
// flag not set, config false == no comments
//   This case is handled by Test_config_gitConfig_FF
// flag not set, config not set == no comments
// flag set, config not set == comments
//   These case are handled in cmd/mr_show_test.go

// flag set, config true == comments
func Test_flag_config_TT(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "true", "true")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1", "--comments")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// both configs set to true, comments should be output
	require.Contains(t, string(b), `commented at`)
}

// flag set, config false == comments
func Test_flag_config_TF(t *testing.T) {
	repo := copyTestRepo(t)

	err := setConfigValues(repo, "false", "false")
	if err != nil {
		t.Skip(err)
	}
	os.Remove(repo + "/lab.toml")

	cmd := exec.Command(labBinaryPath, "mr", "show", "1", "--comments")
	cmd.Dir = repo

	b, err := cmd.CombinedOutput()
	if err != nil {
		t.Log(string(b))
		t.Error(err)
	}

	out := string(b)
	out = stripansi.Strip(out)

	os.Remove("/home/travis/.config/lab/lab.toml")
	// both configs set to true, comments should be output
	require.Contains(t, string(b), `commented at`)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strconv"
)

// --- Day 10: Elves Look, Elves Say ---

// Today, the Elves are playing a game called look-and-say.
// They take turns making sequences by reading aloud the previous sequence and using that reading as
// the next sequence. For example, 211 is read as "one two, two ones",
// which becomes 1221 (1 2, 2 1s).

// Look-and-say sequences are generated iteratively,
// using the previous value as input for the next step.
// For each step, take the previous value, and replace each run of digits (like 111) with the
// number of digits (3) followed by the digit itself (1).

// For example:

// 1 becomes 11 (1 copy of digit 1).
// 11 becomes 21 (2 copies of digit 1).
// 21 becomes 1211 (one 2 followed by one 1).
// 1211 becomes 111221 (one 1, one 2, and two 1s).
// 111221 becomes 312211 (three 1s, two 2s, and one 1).
// Starting with the digits in your puzzle input, apply this process 40 times.
// What is the length of the result?

// Your puzzle input is 1113222113.

var input string = "1113222113"

func main() {
	// Part one has 40 iterations, part two has 50
	//iterations := 40
	iterations := 50
	part_one_iterations := 40
	next_sequence := input
	for i := 0; i < iterations; i++ {
		// fmt.Println(next_sequence)
		fmt.Println("iteration", i)
		current_sequence := next_sequence
		next_sequence = ""
		// iterate over all sequences
		current_pointer := 0
		for current_pointer < len(current_sequence) {
			// time.Sleep(100 * time.Millisecond)
			// transform each sequence into string
			end_of_this_sequence := endOfCurrentSequence(current_sequence, current_pointer)
			// fmt.Println(end_of_this_sequence)
			new_subsequence := transformSequence(current_sequence[current_pointer:end_of_this_sequence])
			// fmt.Println(new_subsequence)
			// concatenate strings and add to next_sequence
			next_sequence += new_subsequence
			current_pointer = end_of_this_sequence
		}
		if i == part_one_iterations || i == iterations-1 {
			fmt.Println("Sequence has length ", len(next_sequence))
		}
	}
}

// Returns the index of the field right of the end of the current sequence. If, e.g., the current sequence
// has length 1, it returns the current pointer+1.
func endOfCurrentSequence(current_sequence string, current_pointer int) int {
	if current_pointer == len(current_sequence)-1 {
		return current_pointer + 1
	}
	sequence_char := current_sequence[current_pointer]
	for i := current_pointer + 1; i < len(current_sequence); i++ {
		if sequence_char != current_sequence[i] {
			return i
		}
	}
	return len(current_sequence) // same sequence char until end of string
}

// a sequence consists of a number of the same number literals
func transformSequence(subsequence string) string {
	character := string(subsequence[0])
	amount := len(subsequence)
	return strconv.Itoa(amount) + character
}
<commit_msg>changed copying of strings to pointer passing, preventing copying of large strings. added solution hints.<commit_after>package main

import (
	"fmt"
	"strconv"
)

// --- Day 10: Elves Look, Elves Say ---

// Today, the Elves are playing a game called look-and-say.
// They take turns making sequences by reading aloud the previous sequence and using that reading as
// the next sequence. For example, 211 is read as "one two, two ones",
// which becomes 1221 (1 2, 2 1s).

// Look-and-say sequences are generated iteratively,
// using the previous value as input for the next step.
// For each step, take the previous value, and replace each run of digits (like 111) with the
// number of digits (3) followed by the digit itself (1).

// For example:

// 1 becomes 11 (1 copy of digit 1).
// 11 becomes 21 (2 copies of digit 1).
// 21 becomes 1211 (one 2 followed by one 1).
// 1211 becomes 111221 (one 1, one 2, and two 1s).
// 111221 becomes 312211 (three 1s, two 2s, and one 1).
// Starting with the digits in your puzzle input, apply this process 40 times.
// What is the length of the result?

// Your puzzle input is 1113222113.
// Correct answers are 252594 (40 iterations) and
// 3579328 (50 iterations)

var input string = "1113222113"

func main() {
	// Part one has 40 iterations, part two has 50
	// iterations := 40
	iterations := 50
	part_one_iterations := 40
	next_sequence := input
	for i := 0; i < iterations; i++ {
		// fmt.Println(next_sequence)
		fmt.Println("iteration", i+1)
		current_sequence := next_sequence
		next_sequence = ""
		// iterate over all sequences
		current_pointer := 0
		for current_pointer < len(current_sequence) {
			// time.Sleep(100 * time.Millisecond)
			// transform each sequence into string
			end_of_this_sequence := endOfCurrentSequence(&current_sequence, current_pointer)
			// fmt.Println(end_of_this_sequence)
			new_subsequence := transformSequence(&current_sequence, current_pointer, end_of_this_sequence)
			// fmt.Println(new_subsequence)
			// concatenate strings and add to next_sequence
			next_sequence += new_subsequence
			current_pointer = end_of_this_sequence
		}
		if i == part_one_iterations-1 || i == iterations-1 {
			fmt.Println("Sequence has length ", len(next_sequence))
		}
	}
}

// Returns the index of the field right of the end of the current sequence. If, e.g., the current sequence
// has length 1, it returns the current pointer+1.
func endOfCurrentSequence(current_sequence *string, current_pointer int) int {
	if current_pointer == len(*current_sequence)-1 {
		return current_pointer + 1
	}
	sequence_char := (*current_sequence)[current_pointer]
	for i := current_pointer + 1; i < len(*current_sequence); i++ {
		if sequence_char != (*current_sequence)[i] {
			return i
		}
	}
	return len(*current_sequence) // same sequence char until end of string
}

// a sequence consists of a number of the same number literals
func transformSequence(sequence *string, begin int, end int) string {
	character := string((*sequence)[begin])
	amount := len((*sequence)[begin:end])
	return strconv.Itoa(amount) + character
}
<|endoftext|>


<commit_before>// Package main provides ...
package main

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

	"github.com/mstruebing/tldr"
	"github.com/mstruebing/tldr/cache"
)

// Help message constants
const (
	listAllUsage  = "list all available commands for the current platform"
	platformUsage = "select platform; supported are: linux, osx, sunos, common"
	renderUsage   = "render a local page for testing purposes"
	updateUsage   = "update local database"
	versionUsage  = "print version and exit"
)

const (
	remoteURL = "http://tldr-pages.github.io/assets/tldr.zip"
	ttl       = time.Hour * 24 * 7
)

func printVersion() {
	fmt.Println("tldr v 1.0.5")
	fmt.Println("Copyright (C) 2017 Max Strübing")
	fmt.Println("Source available at https://github.com")
}

func main() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	version := flag.Bool("version", false, versionUsage)
	flag.BoolVar(version, "v", false, versionUsage)

	update := flag.Bool("update", false, updateUsage)
	flag.BoolVar(update, "u", false, updateUsage)

	render := flag.String("render", "", renderUsage)
	flag.StringVar(render, "r", "", renderUsage)

	listAll := flag.Bool("list-all", false, listAllUsage)
	flag.BoolVar(listAll, "a", false, listAllUsage)

	platform := flag.String("platform", "", platformUsage)
	flag.StringVar(platform, "p", "", platformUsage)

	flag.Parse()

	if *version {
		printVersion()
	} else if *update {
		err = repository.Reload()
		if err != nil {
			log.Fatalf("ERROR: updating cache: %s", err)
		}
	} else if *render != "" {
		if _, err := os.Stat(*render); os.IsNotExist(err) {
			log.Fatal("ERROR: page doesn't exist")
		}

		page, err := os.Open(*render)
		if err != nil {
			log.Fatal("ERROR: opening the page")
		}
		defer page.Close()

		err = tldr.Write(page, os.Stdout)
		if err != nil {
			log.Fatalf("ERROR: rendering the page: %s", err)
		}
	} else if *listAll {
		pages, err := repository.Pages(tldr.CurrentPlatform())
		if err != nil {
			log.Fatalf("ERROR: getting pages: %s", err)
		}

		for _, page := range pages {
			fmt.Println(page)
		}
	} else if *platform != "" {
		page := flag.Arg(0)
		if page == "" {
			log.Fatal("ERROR: no page provided")
		}

		markdown, err := repository.Markdown(*platform, page)
		if err != nil {
			log.Fatalf("ERROR: getting markdown for '%s/%s': %s", *platform, page, err)
		}
		defer markdown.Close()

		err = tldr.Write(markdown, os.Stdout)
		if err != nil {
			log.Fatalf("ERROR: writing markdown: %s", err)
		}
	} else {
		page := flag.Arg(0)
		if page == "" {
			flag.PrintDefaults()
			os.Exit(0)
		}

		platform := tldr.CurrentPlatform()
		markdown, err := repository.Markdown(platform, page)
		if err != nil {
			var platforms []string
			platforms, err = tldr.AvailablePlatforms(repository)
			if err != nil {
				log.Fatalf("ERROR: getting available platforms: %s", err)
			}

			for _, platform = range platforms {
				markdown, err = repository.Markdown(platform, page)
				if err == nil {
					break
				}
			}
			if err != nil {
				log.Fatalf("ERROR: no page found for '%s' in any available platform", page)
			}
		}
		defer markdown.Close()

		err = tldr.Write(markdown, os.Stdout)
		if err != nil {
			log.Fatalf("ERROR: writing markdown: %s", err)
		}
	}
}
<commit_msg>refactor(remoteURL): use tldr domain to prevent redirect<commit_after>// Package main provides ...
package main

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

	"github.com/mstruebing/tldr"
	"github.com/mstruebing/tldr/cache"
)

// Help message constants
const (
	listAllUsage  = "list all available commands for the current platform"
	platformUsage = "select platform; supported are: linux, osx, sunos, common"
	renderUsage   = "render a local page for testing purposes"
	updateUsage   = "update local database"
	versionUsage  = "print version and exit"
)

const (
	remoteURL = "https://tldr.sh/assets/tldr.zip"
	ttl       = time.Hour * 24 * 7
)

func printVersion() {
	fmt.Println("tldr v 1.0.5")
	fmt.Println("Copyright (C) 2017 Max Strübing")
	fmt.Println("Source available at https://github.com")
}

func main() {
	repository, err := cache.NewRepository(remoteURL, ttl)
	if err != nil {
		log.Fatalf("ERROR: creating cache repository: %s", err)
	}

	version := flag.Bool("version", false, versionUsage)
	flag.BoolVar(version, "v", false, versionUsage)

	update := flag.Bool("update", false, updateUsage)
	flag.BoolVar(update, "u", false, updateUsage)

	render := flag.String("render", "", renderUsage)
	flag.StringVar(render, "r", "", renderUsage)

	listAll := flag.Bool("list-all", false, listAllUsage)
	flag.BoolVar(listAll, "a", false, listAllUsage)

	platform := flag.String("platform", "", platformUsage)
	flag.StringVar(platform, "p", "", platformUsage)

	flag.Parse()

	if *version {
		printVersion()
	} else if *update {
		err = repository.Reload()
		if err != nil {
			log.Fatalf("ERROR: updating cache: %s", err)
		}
	} else if *render != "" {
		if _, err := os.Stat(*render); os.IsNotExist(err) {
			log.Fatal("ERROR: page doesn't exist")
		}

		page, err := os.Open(*render)
		if err != nil {
			log.Fatal("ERROR: opening the page")
		}
		defer page.Close()

		err = tldr.Write(page, os.Stdout)
		if err != nil {
			log.Fatalf("ERROR: rendering the page: %s", err)
		}
	} else if *listAll {
		pages, err := repository.Pages(tldr.CurrentPlatform())
		if err != nil {
			log.Fatalf("ERROR: getting pages: %s", err)
		}

		for _, page := range pages {
			fmt.Println(page)
		}
	} else if *platform != "" {
		page := flag.Arg(0)
		if page == "" {
			log.Fatal("ERROR: no page provided")
		}

		markdown, err := repository.Markdown(*platform, page)
		if err != nil {
			log.Fatalf("ERROR: getting markdown for '%s/%s': %s", *platform, page, err)
		}
		defer markdown.Close()

		err = tldr.Write(markdown, os.Stdout)
		if err != nil {
			log.Fatalf("ERROR: writing markdown: %s", err)
		}
	} else {
		page := flag.Arg(0)
		if page == "" {
			flag.PrintDefaults()
			os.Exit(0)
		}

		platform := tldr.CurrentPlatform()
		markdown, err := repository.Markdown(platform, page)
		if err != nil {
			var platforms []string
			platforms, err = tldr.AvailablePlatforms(repository)
			if err != nil {
				log.Fatalf("ERROR: getting available platforms: %s", err)
			}

			for _, platform = range platforms {
				markdown, err = repository.Markdown(platform, page)
				if err == nil {
					break
				}
			}
			if err != nil {
				log.Fatalf("ERROR: no page found for '%s' in any available platform", page)
			}
		}
		defer markdown.Close()

		err = tldr.Write(markdown, os.Stdout)
		if err != nil {
			log.Fatalf("ERROR: writing markdown: %s", err)
		}
	}
}
<|endoftext|>