<commit_before>package main

import (
	"bufio"
	"bytes"
	"compress/lzw"
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/gif"
	"io"
	"math"
	"os"
)

var (
	errDelay           = errors.New("gif: number of images and delays doesn't match")
	errNoImage         = errors.New("gif: no images given (needs at least 1)")
	errNegativeLoop    = errors.New("gif: loop count can't be negative (use 0 for infinite)")
)

func writeHeader(w *bufio.Writer, image *gif.GIF) {
	w.Write([]uint8("GIF89a"))

	b := image.Image[0].Bounds()
	w.WriteByte(uint8(b.Max.X % 255)) // Paletted width, LSB.
	w.WriteByte(uint8(b.Max.X / 255)) // Paletted width, MSB.
	w.WriteByte(uint8(b.Max.Y % 255)) // Paletted height, LSB.
	w.WriteByte(uint8(b.Max.Y / 255)) // Paletted height, MSB.

	colorTableSize := int(math.Log2(float64(len(image.Image[0].Palette)))) - 1
	// The bits in this in this field mean:
	// 1: The globl color table is present.
	// 1 \
	// 1  |-> The resolution is 8 bits per pixel
	// 1 /
	// 0: The values are not sorted
	// x \
	// x  |-> log2(color table size) - 1
	// x /
	w.WriteByte(uint8(0xF0 | colorTableSize)) // Color table information.
	w.WriteByte(uint8(0x00))                  // Background color.
	w.WriteByte(uint8(0x00))                  // Default pixel aspect ratio.

	// Global Color Table.
	palette := image.Image[0].Palette
	for _, c := range palette {
		r, g, b, _ := c.RGBA()
		w.WriteByte(uint8(r))
		w.WriteByte(uint8(g))
		w.WriteByte(uint8(b))
	}

	w.WriteByte(uint8(0x21))                   // Application Extension block.
	w.WriteByte(uint8(0xFF))                   // Application Extension block (cont).
	w.WriteByte(uint8(0x0B))                   // Next 11 bytes are Application Extension.
	w.Write([]uint8("NETSCAPE2.0"))            // 8 Character application name.
	w.WriteByte(uint8(0x03))                   // 3 more bytes of Application Extension.
	w.WriteByte(uint8(0x01))                   // Data sub-block index (always 1).
	w.WriteByte(uint8(image.LoopCount % 0xFF)) // Number of repetitions, LSB.
	w.WriteByte(uint8(image.LoopCount / 0xFF)) // Number of repetitions, MSB.
	w.WriteByte(uint8(0x00))                   // End of Application Extension block.
}

func writeFrameHeader(w *bufio.Writer, m *image.Paletted, delay int) {
	w.WriteByte(uint8(0x21)) // Start of Graphic Control Extension.
	w.WriteByte(uint8(0xF9)) // Start of Graphic Control Extension (cont).
	w.WriteByte(uint8(0x04)) // 4 more bytes of GCE.

	w.WriteByte(uint8(0x08))         // There is no transparent pixel.
	w.WriteByte(uint8(delay % 0xFF)) // Animation delay, in centiseconds, LSB.
	w.WriteByte(uint8(delay / 0xFF)) // Animation delay, in centiseconds, MSB.
	w.WriteByte(uint8(0x00))         // Transparent color #, if we were using.
	w.WriteByte(uint8(0x00))         // End of Application Extension data.

	w.WriteByte(uint8(0x2C)) // Start of Paletted Descriptor.

	b := m.Bounds()
	w.WriteByte(uint8(b.Min.X % 255)) // Minimum x (can be > 0), LSB.
	w.WriteByte(uint8(b.Min.X / 255)) // Minimum x (can be > 0), MSB.
	w.WriteByte(uint8(b.Min.Y % 255)) // Minimum y (can be > 0), LSB.
	w.WriteByte(uint8(b.Min.Y / 255)) // Minimum y (can be > 0), MSB.

	w.WriteByte(uint8(b.Max.X % 255)) // Frame width, LSB.
	w.WriteByte(uint8(b.Max.X / 255)) // Frame width, MSB.
	w.WriteByte(uint8(b.Max.Y % 255)) // Frame height, LSB.
	w.WriteByte(uint8(b.Max.Y / 255)) // Frame height, MSB.

	w.WriteByte(uint8(0x00)) // No local color table.
}

func compressImage(m *image.Paletted) *bytes.Buffer {
	compressedImageBuffer := bytes.NewBuffer(make([]uint8, 0, 255))
	lzww := lzw.NewWriter(compressedImageBuffer, lzw.LSB, int(8))
	lzww.Write(m.Pix)
	lzww.Close()

	return compressedImageBuffer
}

func writeFrame(w *bufio.Writer, m *image.Paletted, delay int) {
	writeFrameHeader(w, m, delay)

	w.WriteByte(uint8(0x08)) // Start of LZW with minimum code size 8.

	compressedImage := compressImage(m)

	const maxBlockSize = 255
	bytesSoFar := 0
	bytesRemaining := compressedImage.Len()
	for bytesRemaining > 0 {
		if bytesSoFar == 0 {
			blockSize := math.Min(maxBlockSize, float64(bytesRemaining))
			w.WriteByte(uint8(blockSize))
		}

		b, _ := compressedImage.ReadByte()
		w.WriteByte(b)

		bytesSoFar = (bytesSoFar + 1) % maxBlockSize
		bytesRemaining--
	}

	w.WriteByte(uint8(0x00)) // End of LZW data.
}

func Encode(w io.Writer, m *image.Paletted) error {
	animation := gif.GIF{[]*image.Paletted{m}, []int{0}, 0}
	return EncodeAll(w, &animation)
}

func EncodeAll(w io.Writer, animation *gif.GIF) error {
	if len(animation.Image) != len(animation.Delay) {
		return errDelay
	}

	if len(animation.Image) == 0 {
		return errNoImage
	}

	if animation.LoopCount < 0 {
		return errNegativeLoop
	}

	buffer := bufio.NewWriter(w)

	writeHeader(buffer, animation)
	for i, _ := range animation.Image {
		image := animation.Image[i]
		delay := animation.Delay[i]
		writeFrame(buffer, image, delay)
	}
	buffer.WriteByte(';')
	buffer.Flush()

	return nil
}

func main() {
	p := make([]color.Color, 256)

	for i := 0; i < 256; i++ {
		//c := uint8((i / 16) ^ (i % 16))
		c := uint8(i)
		p[i] = color.RGBA{c, c, c, 0xFF}
	}

	const nImages = 20
	const imageSize = 10

	images := make([]*image.Paletted, nImages)
	delays := make([]int, nImages)

	for i := 0; i < nImages; i++ {
		m := image.NewPaletted(image.Rect(0, 0, imageSize, imageSize), p)
		for x := 0; x < imageSize; x++ {
			for y := 0; y < imageSize; y++ {
				//m.SetColorIndex(x, y, uint8(x*y/(i+1)))
				m.SetColorIndex(x, y, uint8(x*i))
			}
		}

		images[i] = m
		delays[i] = 30
	}

	file, _ := os.Create("new_image.gif")
	animation := &gif.GIF{images, delays, 0}
	fmt.Println(EncodeAll(file, animation))

	/*file, _ := os.Open("image_test.gif")
	    animation, _ := gif.DecodeAll(file)
	    fmt.Println(animation)

		file, _ = os.Create("new_image.gif")
		fmt.Println(EncodeAll(file, animation))*/
}
<commit_msg>Add more comment to image packet byte<commit_after>package main

import (
	"bufio"
	"bytes"
	"compress/lzw"
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/gif"
	"io"
	"math"
	"os"
)

var (
	errDelay           = errors.New("gif: number of images and delays doesn't match")
	errNoImage         = errors.New("gif: no images given (needs at least 1)")
	errNegativeLoop    = errors.New("gif: loop count can't be negative (use 0 for infinite)")
)

func writeHeader(w *bufio.Writer, image *gif.GIF) {
	w.Write([]uint8("GIF89a"))

	b := image.Image[0].Bounds()
	w.WriteByte(uint8(b.Max.X % 255)) // Paletted width, LSB.
	w.WriteByte(uint8(b.Max.X / 255)) // Paletted width, MSB.
	w.WriteByte(uint8(b.Max.Y % 255)) // Paletted height, LSB.
	w.WriteByte(uint8(b.Max.Y / 255)) // Paletted height, MSB.

	colorTableSize := int(math.Log2(float64(len(image.Image[0].Palette)))) - 1
	// The bits in this in this field mean:
	// 1: The globl color table is present.
	// 1 \
	// 1  |-> The resolution is 8 bits per pixel
	// 1 /
	// 0: The values are not sorted
	// x \
	// x  |-> log2(color table size) - 1
	// x /
	w.WriteByte(uint8(0xF0 | colorTableSize)) // Color table information.
	w.WriteByte(uint8(0x00))                  // Background color.
	w.WriteByte(uint8(0x00))                  // Default pixel aspect ratio.

	// Global Color Table.
	palette := image.Image[0].Palette
	for _, c := range palette {
		r, g, b, _ := c.RGBA()
		w.WriteByte(uint8(r))
		w.WriteByte(uint8(g))
		w.WriteByte(uint8(b))
	}

	w.WriteByte(uint8(0x21))                   // Application Extension block.
	w.WriteByte(uint8(0xFF))                   // Application Extension block (cont).
	w.WriteByte(uint8(0x0B))                   // Next 11 bytes are Application Extension.
	w.Write([]uint8("NETSCAPE2.0"))            // 8 Character application name.
	w.WriteByte(uint8(0x03))                   // 3 more bytes of Application Extension.
	w.WriteByte(uint8(0x01))                   // Data sub-block index (always 1).
	w.WriteByte(uint8(image.LoopCount % 0xFF)) // Number of repetitions, LSB.
	w.WriteByte(uint8(image.LoopCount / 0xFF)) // Number of repetitions, MSB.
	w.WriteByte(uint8(0x00))                   // End of Application Extension block.
}

func writeFrameHeader(w *bufio.Writer, m *image.Paletted, delay int) {
	w.WriteByte(uint8(0x21)) // Start of Graphic Control Extension.
	w.WriteByte(uint8(0xF9)) // Start of Graphic Control Extension (cont).
	w.WriteByte(uint8(0x04)) // 4 more bytes of GCE.

	w.WriteByte(uint8(0x08))         // There is no transparent pixel.
	w.WriteByte(uint8(delay % 0xFF)) // Animation delay, in centiseconds, LSB.
	w.WriteByte(uint8(delay / 0xFF)) // Animation delay, in centiseconds, MSB.
	w.WriteByte(uint8(0x00))         // Transparent color #, if we were using.
	w.WriteByte(uint8(0x00))         // End of Application Extension data.

	w.WriteByte(uint8(0x2C)) // Start of Paletted Descriptor.

	b := m.Bounds()
	w.WriteByte(uint8(b.Min.X % 255)) // Minimum x (can be > 0), LSB.
	w.WriteByte(uint8(b.Min.X / 255)) // Minimum x (can be > 0), MSB.
	w.WriteByte(uint8(b.Min.Y % 255)) // Minimum y (can be > 0), LSB.
	w.WriteByte(uint8(b.Min.Y / 255)) // Minimum y (can be > 0), MSB.

	w.WriteByte(uint8(b.Max.X % 255)) // Frame width, LSB.
	w.WriteByte(uint8(b.Max.X / 255)) // Frame width, MSB.
	w.WriteByte(uint8(b.Max.Y % 255)) // Frame height, LSB.
	w.WriteByte(uint8(b.Max.Y / 255)) // Frame height, MSB.

	w.WriteByte(uint8(0x00)) // No local color table, interlace or sorting.
}

func compressImage(m *image.Paletted) *bytes.Buffer {
	compressedImageBuffer := bytes.NewBuffer(make([]uint8, 0, 255))
	lzww := lzw.NewWriter(compressedImageBuffer, lzw.LSB, int(8))
	lzww.Write(m.Pix)
	lzww.Close()

	return compressedImageBuffer
}

func writeFrame(w *bufio.Writer, m *image.Paletted, delay int) {
	writeFrameHeader(w, m, delay)

	w.WriteByte(uint8(0x08)) // Start of LZW with minimum code size 8.

	compressedImage := compressImage(m)

	const maxBlockSize = 255
	bytesSoFar := 0
	bytesRemaining := compressedImage.Len()
	for bytesRemaining > 0 {
		if bytesSoFar == 0 {
			blockSize := math.Min(maxBlockSize, float64(bytesRemaining))
			w.WriteByte(uint8(blockSize))
		}

		b, _ := compressedImage.ReadByte()
		w.WriteByte(b)

		bytesSoFar = (bytesSoFar + 1) % maxBlockSize
		bytesRemaining--
	}

	w.WriteByte(uint8(0x00)) // End of LZW data.
}

func Encode(w io.Writer, m *image.Paletted) error {
	animation := gif.GIF{[]*image.Paletted{m}, []int{0}, 0}
	return EncodeAll(w, &animation)
}

func EncodeAll(w io.Writer, animation *gif.GIF) error {
	if len(animation.Image) != len(animation.Delay) {
		return errDelay
	}

	if len(animation.Image) == 0 {
		return errNoImage
	}

	if animation.LoopCount < 0 {
		return errNegativeLoop
	}

	buffer := bufio.NewWriter(w)

	writeHeader(buffer, animation)
	for i, _ := range animation.Image {
		image := animation.Image[i]
		delay := animation.Delay[i]
		writeFrame(buffer, image, delay)
	}
	buffer.WriteByte(';')
	buffer.Flush()

	return nil
}

func main() {
	p := make([]color.Color, 256)

	for i := 0; i < 256; i++ {
		//c := uint8((i / 16) ^ (i % 16))
		c := uint8(i)
		p[i] = color.RGBA{c, c, c, 0xFF}
	}

	const nImages = 20
	const imageSize = 10

	images := make([]*image.Paletted, nImages)
	delays := make([]int, nImages)

	for i := 0; i < nImages; i++ {
		m := image.NewPaletted(image.Rect(0, 0, imageSize, imageSize), p)
		for x := 0; x < imageSize; x++ {
			for y := 0; y < imageSize; y++ {
				//m.SetColorIndex(x, y, uint8(x*y/(i+1)))
				m.SetColorIndex(x, y, uint8(x*i))
			}
		}

		images[i] = m
		delays[i] = 30
	}

	file, _ := os.Create("new_image.gif")
	animation := &gif.GIF{images, delays, 0}
	fmt.Println(EncodeAll(file, animation))

	/*file, _ := os.Open("image_test.gif")
	    animation, _ := gif.DecodeAll(file)
	    fmt.Println(animation)

		file, _ = os.Create("new_image.gif")
		fmt.Println(EncodeAll(file, animation))*/
}
<|endoftext|>


<commit_before>// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package gitea

import (
	"encoding/json"
	"fmt"
)

// User represents a user
// swagger:model
type User struct {
	// the user's id
	ID int64 `json:"id"`
	// the user's username
	UserName string `json:"login"`
	// the user's full name
	FullName string `json:"full_name"`
	// swagger:strfmt email
	Email string `json:"email"`
	// URL to the user's avatar
	AvatarURL string `json:"avatar_url"`
	// User locale
	Language string `json:"language"`
}

// MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility
func (u User) MarshalJSON() ([]byte, error) {
	// Re-declaring User to avoid recursion
	type shadow User
	return json.Marshal(struct {
		shadow
		CompatUserName string `json:"username"`
	}{shadow(u), u.UserName})
}

// GetUserInfo get user info by user's name
func (c *Client) GetUserInfo(user string) (*User, error) {
	u := new(User)
	err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u)
	return u, err
}

// GetMyUserInfo get user info of current user
func (c *Client) GetMyUserInfo() (*User, error) {
	u := new(User)
	err := c.getParsedResponse("GET", "/user", nil, nil, u)
	return u, err
}
<commit_msg>add IsAdmin to user (#151)<commit_after>// Copyright 2014 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package gitea

import (
	"encoding/json"
	"fmt"
)

// User represents a user
// swagger:model
type User struct {
	// the user's id
	ID int64 `json:"id"`
	// the user's username
	UserName string `json:"login"`
	// the user's full name
	FullName string `json:"full_name"`
	// swagger:strfmt email
	Email string `json:"email"`
	// URL to the user's avatar
	AvatarURL string `json:"avatar_url"`
	// User locale
	Language string `json:"language"`
	// Is the user an administrator
	IsAdmin bool `json:"is_admin"`
}

// MarshalJSON implements the json.Marshaler interface for User, adding field(s) for backward compatibility
func (u User) MarshalJSON() ([]byte, error) {
	// Re-declaring User to avoid recursion
	type shadow User
	return json.Marshal(struct {
		shadow
		CompatUserName string `json:"username"`
	}{shadow(u), u.UserName})
}

// GetUserInfo get user info by user's name
func (c *Client) GetUserInfo(user string) (*User, error) {
	u := new(User)
	err := c.getParsedResponse("GET", fmt.Sprintf("/users/%s", user), nil, nil, u)
	return u, err
}

// GetMyUserInfo get user info of current user
func (c *Client) GetMyUserInfo() (*User, error) {
	u := new(User)
	err := c.getParsedResponse("GET", "/user", nil, nil, u)
	return u, err
}
<|endoftext|>


<commit_before>package pov

type Tree struct {
	root string
	children []*Tree
}

// New creates and returns a new Tree with the given root value and children.
func New(value string, children ...*Tree) *Tree {
	return &Tree{
		root: value,
		children: children,
	}
}

// Value returns the value at the root of a tree.
func (tr *Tree) Value() string {
	if tr == nil {
		return ""
	}
	return tr.root
}

// Children returns a slice containing the children of a tree.
// There is no need to sort the elements in the result slice,
// they can be in any order.
func (tr *Tree) Children() []*Tree {
	if tr == nil {
		return []*Tree{}
	}
	return tr.children
}

// String describes a tree in a compact S-expression format.
// This helps to make test outputs more readable.
// Feel free to adapt this method as you see fit.
func (tr *Tree) String() string {
	if tr == nil {
		return "nil"
	}
	result := tr.Value()
	if len(tr.Children()) == 0 {
		return result
	}
	for _, ch := range tr.Children() {
		result += " " + ch.String()
	}
	return "(" + result + ")"
}

// POV problem-specific functions

// FromPov returns the pov from the node specified in the argument.
func (tr *Tree) FromPov(from string) *Tree {
	panic("Please implement this function")
}

// PathTo returns the shortest path between two nodes in the tree.
func (tr *Tree) PathTo(from, to string) []string {
	panic("Please implement this function")
}
<commit_msg>Solve pov<commit_after>package pov

type Tree struct {
	value string
	children []*Tree
	parent *Tree
}

// New creates and returns a new Tree with the given root value and children.
func New(value string, children ...*Tree) *Tree {
	node := &Tree{
		value: value,
		children: children,
	}
	for _, child := range children {
		child.parent = node
	}

	return node
}

// Value returns the value at the root of a tree.
func (tr *Tree) Value() string {
	return tr.value
}

// Children returns a slice containing the children of a tree.
// There is no need to sort the elements in the result slice,
// they can be in any order.
func (tr *Tree) Children() []*Tree {
	return tr.children
}

// String describes a tree in a compact S-expression format.
// This helps to make test outputs more readable.
// Feel free to adapt this method as you see fit.
func (tr *Tree) String() string {
	if tr == nil {
		return "nil"
	}
	result := tr.Value()
	if len(tr.Children()) == 0 {
		return result
	}
	for _, ch := range tr.Children() {
		result += " " + ch.String()
	}
	return "(" + result + ")"
}

// POV problem-specific functions

// FromPov returns the pov from the node specified in the argument.
func (tr *Tree) FromPov(from string) *Tree {
	fromTree := tr.findNode(from)
	if fromTree == nil {
		return nil
	}

	// constuct path from `from` to `root` to not lose track of parents during reversal
	path := []*Tree{fromTree}
	for p := fromTree.parent; p != nil; p = p.parent {
		path = append(path, p)
	}
	// from is already root
	if len(path) == 0 {
		return fromTree
	}

	fromTree.parent = nil
	for i := 0; i < len(path)-1; i++ {
		flip(path[i], path[i+1])
	}
	return fromTree
}

// PathTo returns the shortest path between two nodes in the tree.
func (tr *Tree) PathTo(from, to string) []string {
	newPov := tr.FromPov(to)
	if newPov == nil {
		return nil
	}
	fromNode := newPov.findNode(from)
	if fromNode == nil {
		return nil
	}
	path := []string{fromNode.value}
	for p := fromNode.parent; p != nil; p = p.parent {
		path = append(path, p.value)
		if p.value == to {
			return path
		}
	}
	return nil
}

func (tr *Tree) findNode(value string) *Tree {
	if tr.value == value {
		return tr
	}
	for _, child := range tr.children {
		if found := child.findNode(value); found != nil {
			return found
		}
	}
	return nil
}

func flip(child *Tree, parent *Tree) {
	parent.parent = child
	parent.removeChild(child)
	child.addChild(parent)
}

func (tr *Tree) removeChild(child *Tree) {
	for i, ch := range tr.children {
		if ch == child {
			tr.children = append(tr.children[:i], tr.children[i+1:]...)
			return
		}
	}
}

func (tr *Tree) addChild(child *Tree) {
	tr.children = append(tr.children, child)
}
<|endoftext|>


<commit_before>/*
Package goushahidi provides a client for accessing data from Ushahidi API.

Access different data using the API:
         client := goushahidi.NewClient(nil)

         // list all incidences
         incidences, err := client.GetIncidences()

The full Ushahidi API is documented at https://wiki.ushahidi.com/display/WIKI/Ushahidi+3.x+REST+API.
*/
package goushahidi

import (
	"bytes"
	"encoding/json"
	"flag"
	"net/http"
	"net/url"
)

const (
	libraryVersion = "0.1"
)

type Client struct {
	client         *http.Client
	BaseURL        *url.URL
	DefaultBaseURL string
}

func NewClient(DefaultBaseURL string) *Client {
	flag.Parse()
	baseURL, _ := url.Parse(DefaultBaseURL)
	return &Client{client: http.DefaultClient, BaseURL: baseURL}

}

func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
	rel, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}

	u := c.BaseURL.ResolveReference(rel)
	buf := new(bytes.Buffer)
	if body != nil {
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return nil, err
		}

	}

	req, err := http.NewRequest(method, u.String(), buf)
	if err != nil {
		return nil, err
	}

	return req, nil

}
<commit_msg>removed flag, not needed, user should pass this when using lib.<commit_after>/*
Package goushahidi provides a client for accessing data from Ushahidi API.

Access different data using the API:
         client := goushahidi.NewClient(nil)

         // list all incidences
         incidences, err := client.GetIncidences()

The full Ushahidi API is documented at https://wiki.ushahidi.com/display/WIKI/Ushahidi+3.x+REST+API.
*/
package goushahidi

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/url"
)

const (
	libraryVersion = "0.1"
)

type Client struct {
	client         *http.Client
	BaseURL        *url.URL
	DefaultBaseURL string
}

func NewClient(DefaultBaseURL string) *Client {
	baseURL, _ := url.Parse(DefaultBaseURL)
	return &Client{client: http.DefaultClient, BaseURL: baseURL}

}

func (c *Client) NewRequest(method, urlStr string, body interface{}) (*http.Request, error) {
	rel, err := url.Parse(urlStr)
	if err != nil {
		return nil, err
	}

	u := c.BaseURL.ResolveReference(rel)
	buf := new(bytes.Buffer)
	if body != nil {
		err := json.NewEncoder(buf).Encode(body)
		if err != nil {
			return nil, err
		}

	}

	req, err := http.NewRequest(method, u.String(), buf)
	if err != nil {
		return nil, err
	}

	return req, nil

}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2017 AlexRuzin (stan.ruzin@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package govfs

import (
    "testing"
    "time"
    "os"
    "io"
    "bytes"
    "runtime"
    "github.com/AlexRuzin/util"
    "strconv"
)

const FS_DATABASE_FILE string = "test_db"

func TestFSWriter(t *testing.T) {
    /*
     * This test will generate the raw fs stream file, along with some contents
     *  that will be later loaded by the TestFSReader() method
     */
    util.DebugOut("[+] Running Standard I/O Sanity Test...")

    /* Remove the test database if it exists */
    var filename = gen_raw_filename(FS_DATABASE_FILE)
    if _, err := os.Stat(filename); os.IsExist(err) {
        os.Remove(filename)
    }

    var header, err = CreateDatabase(filename, FLAG_DB_CREATE)
    if header == nil || err != nil {
        drive_fail(err.Error(), t)
    }
    if err := header.StartIOController(); err != nil {
        drive_fail("TEST1.1: Failed to start IOController", t)
    }
    util.DebugOut("[+] Test 1 PASS")

    // The root file "/" must at least exist
    if err := header.Create("/"); err == nil {
        drive_fail("TEST2: Failed to return root handle", t)
    }
    util.DebugOut("[+] Test 2 PASS")

    /*
     * Try to delete the root file "/"
     */
    if header.Delete("/") == nil {
        drive_fail("TEST3: Cannot delete root -- critical", t)
    }
    util.DebugOut("[+] Test 3 PASS")

    /*
     * Attempt to write to a nonexistant file
     */
    var data = []byte{ 1, 2 }
    if header.Write("/folder5/folder5/file5", data) == nil {
        drive_fail("TEST4: Cannot write to a nonexistant file", t)
    }
    util.DebugOut("[+] Test 4 PASS")

    /*
     * Create empty file file9
     */
    if err := header.Create("/folder5/folder4/folder2/file9"); err != nil {
        drive_fail("TEST4.1: file9 cannot be created", t)
    }
    util.DebugOut("[+] Test 4.1 PASS")

    /*
     * Attempt to create a new file0
     */
    if err := header.Create("/folder0/folder0/file0"); err != nil {
        drive_fail("TEST5.0: file0 cannot be created", t)
    }
    util.DebugOut("[+] Test 5.0 PASS")

    /*
     * Attempt to create a new file0, this will fail since it should already exist
     */
    if err := header.Create("/folder0/folder0/file0"); err == nil {
        drive_fail("TEST5.1: file0 cannot be created twice", t)
    }
    util.DebugOut("[+] Test 5.1 PASS")

    /*
     * Write some data into file0
     */
    data = []byte{ 1, 2, 3, 4 }
    if header.Write("/folder0/folder0/file0", data) != nil {
        drive_fail("TEST6: Failed to write data in file0", t)
    }
    util.DebugOut("[+] Test 6 PASS")

    /*
     * Check that the size of file0 is 4
     */
    if k, _ := header.GetFileSize("/folder0/folder0/file0"); k != uint(len(data)) {
        drive_fail("TEST6.1: The size of data does not match", t)
    }
    util.DebugOut("[+] Test 6.1 PASS")

    /*
     * Attempt to create a new file3
     */
    if err := header.Create("/folder1/folder0/file3"); err != nil {
        drive_fail("TEST7: file3 cannot be created", t)
    }
    util.DebugOut("[+] Test 7 PASS")

    /*
     * Write some data into file3
     */
    var data2 = []byte{ 1, 2, 3, 4, 5, 6, 7 }
    if header.Write("/folder1/folder0/file3", data2) != nil {
        drive_fail("TEST8: Failed to write data in file3", t)
    }
    util.DebugOut("[+] Test 8 PASS")

    /*
     * Write some data into file3
     */
    if header.Write("/folder1/folder0/file3", data2) != nil {
        drive_fail("TEST8.1: Failed to write data in file3", t)
    }
    util.DebugOut("[+] Test 8.1 PASS")

    /*
     * Read the written data from file0 and compare
     */
    output_data, _ := header.Read("/folder0/folder0/file0")
    if output_data == nil || len(output_data) != len(data) || header.t_size - 7 /* len(file3) */ != len(data) {
        drive_fail("TEST9: Failed to read data from file0", t)
    }
    util.DebugOut("[+] Test 9 PASS")

    /*
     * Read the written data from file3 and compare
     */
    output_data, _ = header.Read("/folder1/folder0/file3")
    if output_data == nil || len(output_data) != len(data2) || header.t_size - 4 /* len(file0) */ != len(data2) {
        drive_fail("TEST10: Failed to read data from file3", t)
    }
    util.DebugOut("[+] Test 10 PASS")

    /* Test GetFileListDirectory */
    fileEnum, err := header.GetFileListDirectory("/folder1/")
    if err != nil {
        drive_fail("TEST10.1: Failed to retrieve file listing", t)
    }
    for _, v := range fileEnum {
        util.DebugOut(v)
    }

    /*
     * Write other data to file0
     */
    data = []byte{ 1, 2, 3 }
    if header.Write("/folder0/folder0/file0", data) != nil {
        drive_fail("TEST11: Failed to write data in file1", t)
    }
    util.DebugOut("[+] Test 11 PASS")

    /*
     * Read the new data from file0
     */
    output_data, _ = header.Read("/folder0/folder0/file0")
    if output_data == nil || len(output_data) != len(data) {
        drive_fail("TEST12: Failed to read data from file1", t)
    }
    util.DebugOut("[+] Test 12 PASS")

    /*
     * Attempt to create a new file5. This will be a blank file
     */
    if err := header.Create("/folder2/file7"); err != nil {
        drive_fail("TEST13: file3 cannot be created", t)
    }
    util.DebugOut("[+] Test 13 PASS")

    /*
     * Delete file0 -- complete this
     */
    // FIXME/ADDME

    /*
     * Create just a folder
     */
    if err := header.Create("/folder2/file5/"); err != nil {
        drive_fail("TEST15: folder file5 cannot be created", t)
    }
    util.DebugOut("[+] Test 15 PASS")

    /*
     * Tests the Reader interface
     */
    reader, err := header.NewReader("/folder0/folder0/file0")
    if err != nil {
        drive_fail("TEST15.1: Failed to create Reader", t)
    }

    file0data := make([]byte, 3)
    data_read, err := reader.Read(file0data)
    if data_read != len(data) || err != io.EOF || bytes.Compare(file0data, data) != 0 {
        drive_fail("TEST15.2: Failed to read from NewReader", t)
    }

    /* Test the reader interface again */
    file0data = make([]byte, 1)
    data_read, err = reader.Read(file0data)
    if data_read != 1 || err != nil || file0data[0] != 1 {
        drive_fail("TEST15.3: Invalid Reader interface behaviour", t)
    }
    util.DebugOut("[+] Test 15.1, 15.2, 15.3 PASS -- Reader interface")

    /*
     * Tests the Writer interface
     */
    file0data = []byte{1, 2, 3, 4, 5, 6, 7, 8}
    writer, err := header.NewWriter("/folder0/folder0/file0")
    if writer == nil || err != nil {
        drive_fail("TEST15.4: Invalid Writer object", t)
    }

    written, err := writer.Write(file0data)
    if written != len(file0data) || err != io.EOF {
        drive_fail("TEST15.4: Invalid Writer response", t)
    }

    file0data = make([]byte, 8)
    data_read, err = reader.Read(file0data)
    if data_read != 8 || err != io.EOF || file0data[0] != 1 || file0data[1] != 2 {
        drive_fail("TEST15.5: Invalid Reader data",t )
    }
    util.DebugOut("[+] Test 15.4, 15.5 PASS -- Writer interface")

    /*
     * Print out files
     */
    file_list := header.GetFileList()
    for _, e := range file_list {
        util.DebugOut(e)
    }

    /*
     * Unmount/commit database to file
     */
    if err := header.UnmountDB(0 /*FLAG_COMPRESS_FILES*/); err != nil {
        drive_fail("TEST16: Failed to commit database", t)
    }
    util.DebugOut("[+] Test 16 PASS. Raw FS stream written to: " + header.filename)
    util.DebugOut("Total File Content Size: " + strconv.Itoa(header.GetTotalFilesizes()))

    time.Sleep(10000)
}

func TestFSReader(t *testing.T) {
    /*
     * Read in FS_DATABASE_FILE and do basic tests
     */
    var filename = gen_raw_filename(FS_DATABASE_FILE)
    util.DebugOut("[+] Loading Raw FS stream file: " + filename)

    /* Remove the test database if it exists */
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        drive_fail("error: Standard raw fs stream " + filename + " does not exist", t)
    }

    header, err := CreateDatabase(filename, FLAG_DB_LOAD)
    if header == nil || err != nil {
        drive_fail("TEST1: Failed to obtain header", t)
    }

    if err := header.StartIOController(); err != nil {
        drive_fail("TEST1.1: Failed to start IOController", t)
    }
    util.DebugOut("[+] Test 1 PASS (Loaded FS stream)")

    /*
     * Tests the Writer interface
     */
    if err := header.Create("/NewestFolder/Subdirectory/GoodBoy.bin"); err != nil {
        drive_fail("TEST2.1: folder file5 cannot be created", t)
    }

    file0data := []byte("whattttttt")
    writer, err := header.NewWriter("/NewestFolder/Subdirectory/GoodBoy.bin")
    if writer == nil || err != nil {
        drive_fail("TEST2.2: Invalid Writer object", t)
    }

    written, err := writer.Write(file0data)
    if written != len(file0data) || err != io.EOF {
        drive_fail("TEST2.3: Invalid Writer response", t)
    }
    util.DebugOut("[+] Test 2 PASS")

    /*
     * Print out files
     */
    file_list := header.GetFileList()
    for _, e := range file_list {
        util.DebugOut(e)
    }

    util.DebugOut("Total File Content Size: " + strconv.Itoa(int(header.GetTotalFilesizes())))
}

func gen_raw_filename(suffix string) string {
    if runtime.GOOS == "windows" {
        return os.Getenv("TEMP") + "\\" + suffix + ".db"
    }

    if runtime.GOOS == "linux" {
        return suffix + ".db"
    }

    return suffix + ".db"
}

func drive_fail(output string, t *testing.T) {
    t.Errorf(output)
    t.FailNow()
}
<commit_msg>Fixed test method to use FLAG_COMPRESS and FLAG_ENCRYPT<commit_after>/*
 * Copyright (c) 2017 AlexRuzin (stan.ruzin@gmail.com)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

package govfs

import (
    "testing"
    "time"
    "os"
    "io"
    "bytes"
    "runtime"
    "github.com/AlexRuzin/util"
    "strconv"
)

const FS_DATABASE_FILE string = "test_db"

func TestFSWriter(t *testing.T) {
    /*
     * This test will generate the raw fs stream file, along with some contents
     *  that will be later loaded by the TestFSReader() method
     */
    util.DebugOut("[+] Running Standard I/O Sanity Test...")

    /* Remove the test database if it exists */
    var filename = gen_raw_filename(FS_DATABASE_FILE)
    if _, err := os.Stat(filename); os.IsExist(err) {
        os.Remove(filename)
    }

    var header, err = CreateDatabase(filename, FLAG_DB_CREATE | FLAG_ENCRYPT | FLAG_COMPRESS)
    if header == nil || err != nil {
        drive_fail(err.Error(), t)
    }
    if err := header.StartIOController(); err != nil {
        drive_fail("TEST1.1: Failed to start IOController", t)
    }
    util.DebugOut("[+] Test 1 PASS")

    // The root file "/" must at least exist
    if err := header.Create("/"); err == nil {
        drive_fail("TEST2: Failed to return root handle", t)
    }
    util.DebugOut("[+] Test 2 PASS")

    /*
     * Try to delete the root file "/"
     */
    if header.Delete("/") == nil {
        drive_fail("TEST3: Cannot delete root -- critical", t)
    }
    util.DebugOut("[+] Test 3 PASS")

    /*
     * Attempt to write to a nonexistant file
     */
    var data = []byte{ 1, 2 }
    if header.Write("/folder5/folder5/file5", data) == nil {
        drive_fail("TEST4: Cannot write to a nonexistant file", t)
    }
    util.DebugOut("[+] Test 4 PASS")

    /*
     * Create empty file file9
     */
    if err := header.Create("/folder5/folder4/folder2/file9"); err != nil {
        drive_fail("TEST4.1: file9 cannot be created", t)
    }
    util.DebugOut("[+] Test 4.1 PASS")

    /*
     * Attempt to create a new file0
     */
    if err := header.Create("/folder0/folder0/file0"); err != nil {
        drive_fail("TEST5.0: file0 cannot be created", t)
    }
    util.DebugOut("[+] Test 5.0 PASS")

    /*
     * Attempt to create a new file0, this will fail since it should already exist
     */
    if err := header.Create("/folder0/folder0/file0"); err == nil {
        drive_fail("TEST5.1: file0 cannot be created twice", t)
    }
    util.DebugOut("[+] Test 5.1 PASS")

    /*
     * Write some data into file0
     */
    data = []byte{ 1, 2, 3, 4 }
    if header.Write("/folder0/folder0/file0", data) != nil {
        drive_fail("TEST6: Failed to write data in file0", t)
    }
    util.DebugOut("[+] Test 6 PASS")

    /*
     * Check that the size of file0 is 4
     */
    if k, _ := header.GetFileSize("/folder0/folder0/file0"); k != uint(len(data)) {
        drive_fail("TEST6.1: The size of data does not match", t)
    }
    util.DebugOut("[+] Test 6.1 PASS")

    /*
     * Attempt to create a new file3
     */
    if err := header.Create("/folder1/folder0/file3"); err != nil {
        drive_fail("TEST7: file3 cannot be created", t)
    }
    util.DebugOut("[+] Test 7 PASS")

    /*
     * Write some data into file3
     */
    var data2 = []byte{ 1, 2, 3, 4, 5, 6, 7 }
    if header.Write("/folder1/folder0/file3", data2) != nil {
        drive_fail("TEST8: Failed to write data in file3", t)
    }
    util.DebugOut("[+] Test 8 PASS")

    /*
     * Write some data into file3
     */
    if header.Write("/folder1/folder0/file3", data2) != nil {
        drive_fail("TEST8.1: Failed to write data in file3", t)
    }
    util.DebugOut("[+] Test 8.1 PASS")

    /*
     * Read the written data from file0 and compare
     */
    output_data, _ := header.Read("/folder0/folder0/file0")
    if output_data == nil || len(output_data) != len(data) || header.t_size - 7 /* len(file3) */ != len(data) {
        drive_fail("TEST9: Failed to read data from file0", t)
    }
    util.DebugOut("[+] Test 9 PASS")

    /*
     * Read the written data from file3 and compare
     */
    output_data, _ = header.Read("/folder1/folder0/file3")
    if output_data == nil || len(output_data) != len(data2) || header.t_size - 4 /* len(file0) */ != len(data2) {
        drive_fail("TEST10: Failed to read data from file3", t)
    }
    util.DebugOut("[+] Test 10 PASS")

    /* Test GetFileListDirectory */
    fileEnum, err := header.GetFileListDirectory("/folder1/")
    if err != nil {
        drive_fail("TEST10.1: Failed to retrieve file listing", t)
    }
    for _, v := range fileEnum {
        util.DebugOut(v)
    }

    /*
     * Write other data to file0
     */
    data = []byte{ 1, 2, 3 }
    if header.Write("/folder0/folder0/file0", data) != nil {
        drive_fail("TEST11: Failed to write data in file1", t)
    }
    util.DebugOut("[+] Test 11 PASS")

    /*
     * Read the new data from file0
     */
    output_data, _ = header.Read("/folder0/folder0/file0")
    if output_data == nil || len(output_data) != len(data) {
        drive_fail("TEST12: Failed to read data from file1", t)
    }
    util.DebugOut("[+] Test 12 PASS")

    /*
     * Attempt to create a new file5. This will be a blank file
     */
    if err := header.Create("/folder2/file7"); err != nil {
        drive_fail("TEST13: file3 cannot be created", t)
    }
    util.DebugOut("[+] Test 13 PASS")

    /*
     * Delete file0 -- complete this
     */
    // FIXME/ADDME

    /*
     * Create just a folder
     */
    if err := header.Create("/folder2/file5/"); err != nil {
        drive_fail("TEST15: folder file5 cannot be created", t)
    }
    util.DebugOut("[+] Test 15 PASS")

    /*
     * Tests the Reader interface
     */
    reader, err := header.NewReader("/folder0/folder0/file0")
    if err != nil {
        drive_fail("TEST15.1: Failed to create Reader", t)
    }

    file0data := make([]byte, 3)
    data_read, err := reader.Read(file0data)
    if data_read != len(data) || err != io.EOF || bytes.Compare(file0data, data) != 0 {
        drive_fail("TEST15.2: Failed to read from NewReader", t)
    }

    /* Test the reader interface again */
    file0data = make([]byte, 1)
    data_read, err = reader.Read(file0data)
    if data_read != 1 || err != nil || file0data[0] != 1 {
        drive_fail("TEST15.3: Invalid Reader interface behaviour", t)
    }
    util.DebugOut("[+] Test 15.1, 15.2, 15.3 PASS -- Reader interface")

    /*
     * Tests the Writer interface
     */
    file0data = []byte{1, 2, 3, 4, 5, 6, 7, 8}
    writer, err := header.NewWriter("/folder0/folder0/file0")
    if writer == nil || err != nil {
        drive_fail("TEST15.4: Invalid Writer object", t)
    }

    written, err := writer.Write(file0data)
    if written != len(file0data) || err != io.EOF {
        drive_fail("TEST15.4: Invalid Writer response", t)
    }

    file0data = make([]byte, 8)
    data_read, err = reader.Read(file0data)
    if data_read != 8 || err != io.EOF || file0data[0] != 1 || file0data[1] != 2 {
        drive_fail("TEST15.5: Invalid Reader data",t )
    }
    util.DebugOut("[+] Test 15.4, 15.5 PASS -- Writer interface")

    /*
     * Print out files
     */
    file_list := header.GetFileList()
    for _, e := range file_list {
        util.DebugOut(e)
    }

    /*
     * Unmount/commit database to file
     */
    if err := header.UnmountDB(0 /*FLAG_COMPRESS_FILES*/); err != nil {
        drive_fail("TEST16: Failed to commit database", t)
    }
    util.DebugOut("[+] Test 16 PASS. Raw FS stream written to: " + header.filename)
    util.DebugOut("Total File Content Size: " + strconv.Itoa(header.GetTotalFilesizes()))

    time.Sleep(10000)
}

func TestFSReader(t *testing.T) {
    /*
     * Read in FS_DATABASE_FILE and do basic tests
     */
    var filename = gen_raw_filename(FS_DATABASE_FILE)
    util.DebugOut("[+] Loading Raw FS stream file: " + filename)

    /* Remove the test database if it exists */
    if _, err := os.Stat(filename); os.IsNotExist(err) {
        drive_fail("error: Standard raw fs stream " + filename + " does not exist", t)
    }

    header, err := CreateDatabase(filename, FLAG_DB_LOAD | FLAG_COMPRESS | FLAG_ENCRYPT)
    if header == nil || err != nil {
        drive_fail("TEST1: Failed to obtain header", t)
    }

    if err := header.StartIOController(); err != nil {
        drive_fail("TEST1.1: Failed to start IOController", t)
    }
    util.DebugOut("[+] Test 1 PASS (Loaded FS stream)")

    /*
     * Tests the Writer interface
     */
    if err := header.Create("/NewestFolder/Subdirectory/GoodBoy.bin"); err != nil {
        drive_fail("TEST2.1: folder file5 cannot be created", t)
    }

    file0data := []byte("whattttttt")
    writer, err := header.NewWriter("/NewestFolder/Subdirectory/GoodBoy.bin")
    if writer == nil || err != nil {
        drive_fail("TEST2.2: Invalid Writer object", t)
    }

    written, err := writer.Write(file0data)
    if written != len(file0data) || err != io.EOF {
        drive_fail("TEST2.3: Invalid Writer response", t)
    }
    util.DebugOut("[+] Test 2 PASS")

    /*
     * Print out files
     */
    file_list := header.GetFileList()
    for _, e := range file_list {
        util.DebugOut(e)
    }

    util.DebugOut("Total File Content Size: " + strconv.Itoa(int(header.GetTotalFilesizes())))
}

func gen_raw_filename(suffix string) string {
    if runtime.GOOS == "windows" {
        return os.Getenv("TEMP") + "\\" + suffix + ".db"
    }

    if runtime.GOOS == "linux" {
        return suffix + ".db"
    }

    return suffix + ".db"
}

func drive_fail(output string, t *testing.T) {
    t.Errorf(output)
    t.FailNow()
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2015 zulily, Inc.
 *
 * 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 sampler

import (
	"errors"
	"fmt"
	"net"
	"regexp"
	"strconv"
	"strings"

	"github.com/garyburd/redigo/redis"
)

// Options is a configuration struct that instructs the sampler pkg to sample
// the redis instance listening on a particular host/port with a specified
// number/percentage of random keys.
type Options struct {
	Host string
	Port int

	// MinSamples indicates the minimum number of random keys to sample from the redis
	// instance.  Note that this does not mean **unique** keys, just an absolute
	// number of random keys.  Therefore, this number should be small relative to
	// the number of keys in the redis instance.
	MinSamples int

	// SampleRate indicates the percentage of the keyspace to sample.
	// Accordingly, values should be between 0.0 and 1.0.  If a non-zero value is
	// given for both `SampleRate` and `MinSamples`, the actual number of keys
	// sampled will be the greater of the two values, once the key count has been
	// calculated using the `SampleRate`.
	SampleRate float32
}

// A ValueType represents the various data types that redis can store. The
// string representation of a ValueType matches what is returned from redis'
// `TYPE` command.
type ValueType string

var (
	// TypeString represents a redis string value
	TypeString ValueType = "string"
	// TypeSortedSet represents a redis sorted set value
	TypeSortedSet ValueType = "zset"
	// TypeSet represents a redis set value
	TypeSet ValueType = "set"
	// TypeHash represents a redis hash value
	TypeHash ValueType = "hash"
	// TypeList represents a redis list value
	TypeList ValueType = "list"
	// TypeUnknown means that the redis value type is undefined, and indicates an error
	TypeUnknown ValueType = "unknown"

	// ErrNoKeys is the error returned when a specified redis instance contains
	// no keys, or the key count could not be determined
	ErrNoKeys error = errors.New("No keys are present in the configured redis instance")

	// keysExpr captures the key count from the matching line of output from
	// redis' "INFO" command
	keysExpr = regexp.MustCompile("^db\\d+:keys=(\\d+),")
)

// AnyKey is an AggregatorFunc that puts any sampled key (regardless of key
// name or redis data type) into a generic "any" bucket.
func AnyKey(key string, valueType ValueType) []string {
	return []string{"any"}
}

// An Aggregator returns 0 or more arbitrary strings, to be used during a
// sampling operation as aggregation groups or "buckets". For example, an
// Aggregator that takes the first letter of the key would cause a Sampler to
// aggregate stats by each letter of the alphabet
type Aggregator interface {
	Groups(key string, valueType ValueType) []string
}

// The AggregatorFunc type is an adapter to allow the use of
// ordinary functions as Aggregators.  If f is a function
// with the appropriate signature, AggregatorFunc(f) is an
// Aggregator object that calls f.
type AggregatorFunc func(key string, valueType ValueType) []string

// Groups provides 0 or more groups to aggregate `key` to, when sampling redis keys.
func (f AggregatorFunc) Groups(key string, valueType ValueType) []string {
	return f(key, valueType)
}

// flush is a convenience func for flushing a redis pipeline, receiving the
// replies, and returning them, along with any error
func flush(conn redis.Conn) ([]interface{}, error) {
	return redis.Values(conn.Do(""))
}

// ensureEntry is a convenience func for obtaining the Stats instance for the
// specified `group`, creating a new one if no such entry already exists
func ensureEntry(m map[string]*Results, group string, init func() *Results) *Results {
	var stats *Results
	var ok bool
	if stats, ok = m[group]; !ok {
		stats = init()
		m[group] = stats
	}
	return stats
}

// randomKey obtains a random redis key and its ValueType from the supplied redis connection
func randomKey(conn redis.Conn) (key string, vt ValueType, err error) {
	key, err = redis.String(conn.Do("RANDOMKEY"))
	if err != nil {
		return key, TypeUnknown, err
	}

	typeStr, err := redis.String(conn.Do("TYPE", key))
	if err != nil {
		return key, TypeUnknown, err
	}

	return key, ValueType(typeStr), nil
}

// keyCount obtains a the number of keys in the redis instance.
func keyCount(conn redis.Conn) (count int64, err error) {
	resp, err := redis.String(conn.Do("INFO"))
	if err != nil {
		return count, err
	}

	for _, str := range strings.Split(resp, "\n") {
		if matches := keysExpr.FindStringSubmatch(str); len(matches) >= 2 {
			if count, err = strconv.ParseInt(matches[1], 10, 64); err == nil && count != 0 {
				return count, nil
			}
			return count, ErrNoKeys
		}
	}

	return 0, ErrNoKeys
}

func sampleString(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	val, err := redis.String(conn.Do("GET", key))
	if err != nil {
		return err
	}

	for _, agg := range aggregator.Groups(key, TypeString) {
		s := ensureEntry(stats, agg, NewResults)
		s.observeString(key, val)
	}
	return nil
}

func sampleList(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	// TODO: Let's not always get the first element, like the orig. sampler
	conn.Send("LLEN", key)
	conn.Send("LRANGE", key, 0, 0)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		ms, err := redis.Strings(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeList) {
			s := ensureEntry(stats, g, NewResults)
			s.observeList(key, l, ms[0])
		}
	}
	return nil
}

func sampleSet(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("SCARD", key)
	conn.Send("SRANDMEMBER", key)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		m, err := redis.String(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeSet) {
			s := ensureEntry(stats, g, NewResults)
			s.observeSet(key, l, m)
		}
	}
	return nil
}

func sampleSortedSet(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("ZCARD", key)
	// TODO: Let's not always get the first element, like the orig. sampler
	conn.Send("ZRANGE", key, 0, 0)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		ms, err := redis.Strings(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeSortedSet) {
			s := ensureEntry(stats, g, NewResults)
			s.observeSortedSet(key, l, ms[0])
		}
	}
	return nil
}

func sampleHash(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("HLEN", key)
	conn.Send("HKEYS", key)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		for _, g := range aggregator.Groups(key, TypeHash) {

			// TODO: Let's not always get the first hash field, like the orig. sampler
			l, err := redis.Int(replies[0], nil)
			fields, err := redis.Strings(replies[1], err)
			if err != nil {
				return err
			}
			val, err := redis.String(conn.Do("HGET", key, fields[0]))
			if err != nil {
				return err
			}
			s := ensureEntry(stats, g, NewResults)
			s.observeHash(key, l, fields[0], val)
		}
	}
	return nil
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

// Run performs the configured sampling operation against the redis instance,
// aggregating statistics using the provided Aggregator.  If any errors occurr,
// the sampling is short-circuited, and the error is returned.  In such a case,
// the results should be considered invalid.
func Run(opts Options, aggregator Aggregator) (map[string]*Results, error) {

	stats := make(map[string]*Results)
	var err error

	if opts.SampleRate < 0.0 || opts.SampleRate > 1.0 {
		return stats, errors.New("SampleRate must be between 0.0 and 1.0")
	}

	if opts.MinSamples <= 0 && opts.SampleRate == 0.0 {
		return stats, errors.New("MinSamples cannot be 0")
	}

	conn, err := redis.Dial("tcp", net.JoinHostPort(opts.Host, strconv.Itoa(opts.Port)))
	if err != nil {
		return stats, fmt.Errorf("Error connecting to the redis instance at: %s:%d : %s", opts.Host, opts.Port, err.Error())
	}

	numSamples := opts.MinSamples

	if kc, err := keyCount(conn); err != nil {
		return stats, err
	} else {
		fmt.Printf("redis at %s:%d has %d keys\n", opts.Host, opts.Port, kc)
		if opts.SampleRate > 0.0 {
			v := int(float32(kc) * opts.SampleRate)
			numSamples = max(max(v, numSamples), 1)
		}
	}

	interval := numSamples / 100
	if interval == 0 {
		interval = 1
	}
	lastInterval := 0

	for i := 0; i < numSamples; i++ {
		key, vt, err := randomKey(conn)
		if err != nil {
			return stats, err
		}

		if i/interval != lastInterval {
			fmt.Printf("sampled %d keys from redis at: %s:%d...\n", i, opts.Host, opts.Port)
			lastInterval = i / interval
		}

		switch ValueType(vt) {
		case TypeString:
			err = sampleString(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeList:
			err = sampleList(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeSet:
			err = sampleSet(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeSortedSet:
			err = sampleSortedSet(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		case TypeHash:
			err = sampleHash(key, conn, aggregator, stats)
			if err != nil {
				return stats, err
			}
		default:
			return stats, fmt.Errorf("unknown type for redis key: %s", key)
		}
	}
	return stats, nil
}
<commit_msg>Returns the actual key count for the instance, alongside the aggregated stats<commit_after>/*
 * Copyright (C) 2015 zulily, Inc.
 *
 * 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 sampler

import (
	"errors"
	"fmt"
	"net"
	"regexp"
	"strconv"
	"strings"

	"github.com/garyburd/redigo/redis"
)

// Options is a configuration struct that instructs the sampler pkg to sample
// the redis instance listening on a particular host/port with a specified
// number/percentage of random keys.
type Options struct {
	Host string
	Port int

	// MinSamples indicates the minimum number of random keys to sample from the redis
	// instance.  Note that this does not mean **unique** keys, just an absolute
	// number of random keys.  Therefore, this number should be small relative to
	// the number of keys in the redis instance.
	MinSamples int

	// SampleRate indicates the percentage of the keyspace to sample.
	// Accordingly, values should be between 0.0 and 1.0.  If a non-zero value is
	// given for both `SampleRate` and `MinSamples`, the actual number of keys
	// sampled will be the greater of the two values, once the key count has been
	// calculated using the `SampleRate`.
	SampleRate float32
}

// A ValueType represents the various data types that redis can store. The
// string representation of a ValueType matches what is returned from redis'
// `TYPE` command.
type ValueType string

var (
	// TypeString represents a redis string value
	TypeString ValueType = "string"
	// TypeSortedSet represents a redis sorted set value
	TypeSortedSet ValueType = "zset"
	// TypeSet represents a redis set value
	TypeSet ValueType = "set"
	// TypeHash represents a redis hash value
	TypeHash ValueType = "hash"
	// TypeList represents a redis list value
	TypeList ValueType = "list"
	// TypeUnknown means that the redis value type is undefined, and indicates an error
	TypeUnknown ValueType = "unknown"

	// ErrNoKeys is the error returned when a specified redis instance contains
	// no keys, or the key count could not be determined
	ErrNoKeys error = errors.New("No keys are present in the configured redis instance")

	// keysExpr captures the key count from the matching line of output from
	// redis' "INFO" command
	keysExpr = regexp.MustCompile("^db\\d+:keys=(\\d+),")
)

// AnyKey is an AggregatorFunc that puts any sampled key (regardless of key
// name or redis data type) into a generic "any" bucket.
func AnyKey(key string, valueType ValueType) []string {
	return []string{"any"}
}

// An Aggregator returns 0 or more arbitrary strings, to be used during a
// sampling operation as aggregation groups or "buckets". For example, an
// Aggregator that takes the first letter of the key would cause a Sampler to
// aggregate stats by each letter of the alphabet
type Aggregator interface {
	Groups(key string, valueType ValueType) []string
}

// The AggregatorFunc type is an adapter to allow the use of
// ordinary functions as Aggregators.  If f is a function
// with the appropriate signature, AggregatorFunc(f) is an
// Aggregator object that calls f.
type AggregatorFunc func(key string, valueType ValueType) []string

// Groups provides 0 or more groups to aggregate `key` to, when sampling redis keys.
func (f AggregatorFunc) Groups(key string, valueType ValueType) []string {
	return f(key, valueType)
}

// flush is a convenience func for flushing a redis pipeline, receiving the
// replies, and returning them, along with any error
func flush(conn redis.Conn) ([]interface{}, error) {
	return redis.Values(conn.Do(""))
}

// ensureEntry is a convenience func for obtaining the Stats instance for the
// specified `group`, creating a new one if no such entry already exists
func ensureEntry(m map[string]*Results, group string, init func() *Results) *Results {
	var stats *Results
	var ok bool
	if stats, ok = m[group]; !ok {
		stats = init()
		m[group] = stats
	}
	return stats
}

// randomKey obtains a random redis key and its ValueType from the supplied redis connection
func randomKey(conn redis.Conn) (key string, vt ValueType, err error) {
	key, err = redis.String(conn.Do("RANDOMKEY"))
	if err != nil {
		return key, TypeUnknown, err
	}

	typeStr, err := redis.String(conn.Do("TYPE", key))
	if err != nil {
		return key, TypeUnknown, err
	}

	return key, ValueType(typeStr), nil
}

// keyCount obtains a the number of keys in the redis instance.
func keyCount(conn redis.Conn) (count int64, err error) {
	resp, err := redis.String(conn.Do("INFO"))
	if err != nil {
		return count, err
	}

	for _, str := range strings.Split(resp, "\n") {
		if matches := keysExpr.FindStringSubmatch(str); len(matches) >= 2 {
			if count, err = strconv.ParseInt(matches[1], 10, 64); err == nil && count != 0 {
				return count, nil
			}
			return count, ErrNoKeys
		}
	}

	return 0, ErrNoKeys
}

func sampleString(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	val, err := redis.String(conn.Do("GET", key))
	if err != nil {
		return err
	}

	for _, agg := range aggregator.Groups(key, TypeString) {
		s := ensureEntry(stats, agg, NewResults)
		s.observeString(key, val)
	}
	return nil
}

func sampleList(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	// TODO: Let's not always get the first element, like the orig. sampler
	conn.Send("LLEN", key)
	conn.Send("LRANGE", key, 0, 0)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		ms, err := redis.Strings(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeList) {
			s := ensureEntry(stats, g, NewResults)
			s.observeList(key, l, ms[0])
		}
	}
	return nil
}

func sampleSet(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("SCARD", key)
	conn.Send("SRANDMEMBER", key)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		m, err := redis.String(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeSet) {
			s := ensureEntry(stats, g, NewResults)
			s.observeSet(key, l, m)
		}
	}
	return nil
}

func sampleSortedSet(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("ZCARD", key)
	// TODO: Let's not always get the first element, like the orig. sampler
	conn.Send("ZRANGE", key, 0, 0)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		l, err := redis.Int(replies[0], nil)
		ms, err := redis.Strings(replies[1], err)
		if err != nil {
			return err
		}

		for _, g := range aggregator.Groups(key, TypeSortedSet) {
			s := ensureEntry(stats, g, NewResults)
			s.observeSortedSet(key, l, ms[0])
		}
	}
	return nil
}

func sampleHash(key string, conn redis.Conn, aggregator Aggregator, stats map[string]*Results) error {
	conn.Send("HLEN", key)
	conn.Send("HKEYS", key)
	replies, err := flush(conn)
	if err != nil {
		return err
	}

	if len(replies) >= 2 {
		for _, g := range aggregator.Groups(key, TypeHash) {

			// TODO: Let's not always get the first hash field, like the orig. sampler
			l, err := redis.Int(replies[0], nil)
			fields, err := redis.Strings(replies[1], err)
			if err != nil {
				return err
			}
			val, err := redis.String(conn.Do("HGET", key, fields[0]))
			if err != nil {
				return err
			}
			s := ensureEntry(stats, g, NewResults)
			s.observeHash(key, l, fields[0], val)
		}
	}
	return nil
}

func max(a, b int) int {
	if a > b {
		return a
	} else {
		return b
	}
}

// Run performs the configured sampling operation against the redis instance,
// returning aggregated statistics using the provided Aggregator, as well as
// the actual key count for the redis instance.  If any errors occurr, the
// sampling is short-circuited, and the error is returned.  In such a case, the
// results should be considered invalid.
func Run(opts Options, aggregator Aggregator) (map[string]*Results, int64, error) {

	stats := make(map[string]*Results)
	var err error
	var keys int64

	if opts.SampleRate < 0.0 || opts.SampleRate > 1.0 {
		return stats, keys, errors.New("SampleRate must be between 0.0 and 1.0")
	}

	if opts.MinSamples <= 0 && opts.SampleRate == 0.0 {
		return stats, keys, errors.New("MinSamples cannot be 0")
	}

	conn, err := redis.Dial("tcp", net.JoinHostPort(opts.Host, strconv.Itoa(opts.Port)))
	if err != nil {
		return stats, keys, fmt.Errorf("Error connecting to the redis instance at: %s:%d : %s", opts.Host, opts.Port, err.Error())
	}

	numSamples := opts.MinSamples

	if keys, err = keyCount(conn); err != nil {
		return stats, keys, err
	} else {
		fmt.Printf("redis at %s:%d has %d keys\n", opts.Host, opts.Port, keys)
		if opts.SampleRate > 0.0 {
			v := int(float32(keys) * opts.SampleRate)
			numSamples = max(max(v, numSamples), 1)
		}
	}

	interval := numSamples / 100
	if interval == 0 {
		interval = 1
	}
	lastInterval := 0

	for i := 0; i < numSamples; i++ {
		key, vt, err := randomKey(conn)
		if err != nil {
			return stats, keys, err
		}

		if i/interval != lastInterval {
			fmt.Printf("sampled %d keys from redis at: %s:%d...\n", i, opts.Host, opts.Port)
			lastInterval = i / interval
		}

		switch ValueType(vt) {
		case TypeString:
			if err = sampleString(key, conn, aggregator, stats); err != nil {
				return stats, keys, err
			}
		case TypeList:
			if err = sampleList(key, conn, aggregator, stats); err != nil {
				return stats, keys, err
			}
		case TypeSet:
			if err = sampleSet(key, conn, aggregator, stats); err != nil {
				return stats, keys, err
			}
		case TypeSortedSet:
			if err = sampleSortedSet(key, conn, aggregator, stats); err != nil {
				return stats, keys, err
			}
		case TypeHash:
			if err = sampleHash(key, conn, aggregator, stats); err != nil {
				return stats, keys, err
			}
		default:
			return stats, keys, fmt.Errorf("unknown type for redis key: %s", key)
		}
	}
	return stats, keys, nil
}
<|endoftext|>


<commit_before>// Package squarify implements the Squarified Treemap algorithm of Bruls, Huizing, and Van Wijk:
//
//    http://www.win.tue.nl/~vanwijk/stm.pdf
//
// The basic idea is to generate a tiling of items of various sizes, each of which may have children which
// are tiled nested inside their parent.
//
// Tiling is performed by calling the Squarify() function.
package squarify

import (
  "sort"
  "fmt"
)

// The TreeSizer interface must be implemented by any tree that will be Squarified.
type TreeSizer interface {
  // Size is the logical size of the item and will be used to compute it's area.
  Size() float64
  // NumChildren is the number of children that this tree node has.
  NumChildren() int
  // Child returns the child of this node at the specified index.
  Child(i int) TreeSizer
}

// Rect represents a two-dimensional rectangle having the upper-left corner at coordinate (X,Y) and having
// width W and height H.
type Rect struct {
  X, Y, W, H float64
}

// Block represents the output for a single TreeSizer node from Squarify(). It's the layed-out
// position and size of the TreeSizer node and the associated TreeSizer that it represents.
type Block struct {
  // Position and size of the layed-out TreeSizer
  Rect
  // The TreeSizer represented by this Block
  TreeSizer TreeSizer
}

// area is the product of an intermediate step that stores the computed area for a
// TreeSizer
type area struct {
  Area float64
  TreeSizer TreeSizer
}

// direction represents the direction of layout for a row: Vertical or Horizontal.
type direction int

const (
  // Lay out the row vertically
  Vertical direction = iota
  // Lay out the row horizontally
  Horizontal
)

const (
  DoSort = true
  DontSort = false
)

// Margins defines the empty space that should exist between a parent Block and it's internal
// children Blocks when layed out. Similar to how the margins of a page separate the content from
// the page edges. The left (L), right (R), top (T) and bottom (B) margins are specified separately.
type Margins struct {
  L,R,T,B float64
}

// Meta is metadata that is returned by Squarify() for each Block it lays out.
type Meta struct {
  // Depth is the depth of the Block in the TreeSizer tree, starting from 0.
  // This is useful when coloring the blocks, for example.
  Depth int
}

// Options controls how Squarify() behaves.
type Options struct {
  // Maximum depth in the tree to descend to. Blocks at depth <= MaxDepth are layed out,
  // and > MaxDepth are ignored. Depth is counted starting at the children of the root node being 1.
  // If MaxDepth is left as the zero value then a MaxDepth of 20 is used.
  MaxDepth int
  // Margins between a parent and it's children.
  Margins *Margins
  // Sort the blocks by size within their parent. This pushes larger blocks to the left/above
  // smaller blocks.
  Sort bool
  // MinW and MinH limit the smallness of Blocks that are output. Blocks who's width is < MinW
  // or who's height is < MinH are not output, nor are their children processed.
  MinW, MinH float64
}

// Squarify implements the Squarified Treemap algorithm. It lays out the children of `root` inside the area
// represented by rect with areas proportional to the Size() of the children. Squarify returns
// a slice of Blocks for rendering and a slice of Metas which contain metainformation corresponding
// to the Blocks. Element block[i] has metadata in meta[i].
//
func Squarify(root TreeSizer, rect Rect, options Options) (blocks []Block, meta []Meta) {
  if options.MaxDepth <= 0 {
    options.MaxDepth = 20
  }

  return squarify(root, Block{Rect: rect}, options, 0)
}

// row is an internal structure used to represent the current row or column of blocks
// being layed out.
type row struct {
  areas []*area
  X,Y float64
  min, max float64 // Min and max areas in the row
  sum float64 // Sum of areas
  Width float64
  Dir direction
}

// newRow returns a new row layed out in the specified direction.
func newRow(dir direction, width,x,y float64) *row {
  return &row{
    areas: make([]*area,0),
    Width: width,
    X: x,
    Y: y,
    Dir: dir,
  }
}

// push adds an area to the end of the row.
func (r *row) push(a *area) {
  if a.Area <= 0 {
    // We use 0 area as a sentinel in min and max.
    panic("Area must be >= 0")
  }

  r.areas = append(r.areas, a)
  r.updateCached(a)
}

// pop removes and returns the area at the end of the row.
func (r *row) pop() *area {
  r.min = 0
  r.max = 0
  r.sum = 0

  if len(r.areas) > 0 {
    last := len(r.areas)-1
    a := r.areas[last]
    r.areas[last] = nil
    r.areas = r.areas[0:last]
    return a
  } else {
    return nil
  }
}

// pushTemporarily pushes area `a` onto the Row, runs f(), then pops the Row.
// The Row is the same before and after this call.
func (r *row) pushTemporarily(a *area, f func()) {
  min := r.min
  max := r.max
  sum := r.sum
  r.push(a)
  f()
  r.pop()
  r.min = min
  r.max = max
  r.sum = sum
}

// calcCached calculates cached values for the row.
func (r *row) calcCached() {
  r.min = 0
  r.max = 0
  r.sum = 0
  for _,a := range r.areas {
    r.updateCached(a)
  }
}

// size returns the number of elements in the row
func (r row) size() int {
  return len(r.areas)
}

// updateCached updates the cached row values with `a` added to the row.
func (r *row) updateCached(a *area) {
  if r.min <= 0 || a.Area < r.min {
    r.min = a.Area
  }
  if r.max <= 0 || a.Area > r.max {
    r.max = a.Area
  }
  r.sum += a.Area
}

// Calculate the worst aspect ratio of all rectangles in the row
func (r *row) worst() float64 {
  if r.min == 0 {
    // We need to calculate min, max, and sum
    r.calcCached()
  }

  w2 := r.Width*r.Width
  sum2 := r.sum*r.sum
  worst1 := w2*r.max / sum2
  worst2 := sum2 / (r.min * w2)
  if worst1 > worst2 {
    return worst1
  } else {
    return worst2
  }
}

// makeBlocks creates the final slice of blocks for the row.
func (r *row) makeBlocks() (height float64, blocks []Block) {
  if r.min == 0 {
    // We need to calculate min, max, and sum
    r.calcCached()
  }

  blocks = make([]Block,0)
  x := r.X
  y := r.Y

  for _, a := range r.areas {
    // Item width relative to the row
    relativeWidth := a.Area/r.sum
    itemWidth := relativeWidth * r.Width
    itemHeight := a.Area/itemWidth

    if height == 0 {
      height = itemHeight
    } else if itemHeight != height {
      itemHeight = height
    }

    if r.Dir == Vertical {
      // swap
      itemWidth, itemHeight = itemHeight, itemWidth
    }

    blocks = append(blocks, Block{Rect: Rect{X: x, Y: y, W: itemWidth, H: itemHeight}, TreeSizer: a.TreeSizer})

    if r.Dir == Vertical {
      y += itemHeight
    } else {
      x += itemWidth
    }
  }

  return
}

// Internal squarify function. Squarify is a frontend to this.
func squarify(root TreeSizer, block Block, options Options, depth int) (blocks []Block, meta []Meta) {
  blocks = make([]Block, 0)
  meta = make([]Meta, 0)

fmt.Println("squarify: depth", depth,"  maxDepth", options.MaxDepth)
  if block.W <= options.MinW || block.H <= options.MinH || depth >= options.MaxDepth {
    return
  }

  output := func(newBlocks []Block) {
    for i := 0; i < len(newBlocks); i++ {
      // Filter out any blocks that are just placeholders for extra space
      if newBlocks[i].TreeSizer != nil {
        // Filter out any blocks that are too small
        if newBlocks[i].W > options.MinW || newBlocks[i].H > options.MinH {
          blocks = append(blocks, newBlocks[i])
          meta = append(meta, Meta{Depth: depth})
        }
      }
    }
  }

  areas := areas(root, block, options.Sort)

  rowX := block.X
  rowY := block.Y
  freeWidth := block.W
  freeHeight := block.H

  makeRow := func() (row *row) {
    if block.W > block.H {
      row = newRow(Vertical, freeHeight, rowX, rowY)
    } else {
      row = newRow(Horizontal, freeWidth, rowX, rowY)
    }
    return row
  }

  // Decide which direction to create the new row
  row := makeRow()

  for _, a := range areas {
    if row.size() > 0 {
      worstBefore := row.worst()
      worstAfter := float64(0)
      row.pushTemporarily(&a, func() {
        worstAfter = row.worst()
      })

      if worstBefore < worstAfter {
        // It's better to make a new row now.
        // Output the current blocks and make a new row
        offset, newBlocks := row.makeBlocks()
        output(newBlocks)

        if row.Dir == Vertical {
          rowX += offset
          freeWidth -= offset
        } else {
          rowY += offset
          freeHeight -= offset
        }

        row = makeRow()
      }
    }

    cp := &area{}
    *cp = a
    row.push(cp)
  }

  if row.size() > 0 {
    _, newBlocks := row.makeBlocks()
    output(newBlocks)
  }

  // Now, for each of the items we just processed, if they have children then
  // lay them out inside their parent box. The available area may be reduced by
  // certain size.
  for _, block := range(blocks) {
    if block.TreeSizer != nil {
      if options.Margins != nil {
        block.X += options.Margins.L
        block.Y += options.Margins.T
        block.W -= options.Margins.L + options.Margins.R
        block.H -= options.Margins.T + options.Margins.B
      }

      newBlocks, newMeta := squarify(block.TreeSizer, block, options, depth+1)
      blocks = append(blocks, newBlocks...)
      meta = append(meta, newMeta...)
    }
  }

  return
}

// Sort areas by area.
type byAreaAndPlaceholder []area

func (a byAreaAndPlaceholder) Len() int {
  return len(a)
}

func (a byAreaAndPlaceholder) Less(i, j int) bool {
  //return a[i].Area > a[j].Area

  if a[i].TreeSizer != nil && a[j].TreeSizer != nil || a[i].TreeSizer == nil && a[j].TreeSizer == nil {
    return a[i].Area > a[j].Area
  } else {
    return a[i].TreeSizer != nil
  }
}

func (a byAreaAndPlaceholder) Swap(i, j int) {
  a[i], a[j] = a[j], a[i]
}

// areas computes the areas for the children of `root` and sorts them if dosort is true.
// The area of a child is calculated as it's proportion of the parent's Block's size, where
// `root` is the parent, and `Block` is the dimensions of the parent.
func areas(root TreeSizer, block Block, dosort bool) (areas []area) {
  blockArea := block.W * block.H

  areas = make([]area,0)
  itemsTotalSize := float64(0)

  for i := 0; i < root.NumChildren(); i++ {
    item := root.Child(i)

    // Ignore 0-size items
    if item.Size() <= 0 {
      continue
    }

    areas = append(areas, area{Area: item.Size()/root.Size()*blockArea, TreeSizer: item})
    itemsTotalSize += item.Size()
  }

  // Add a placeholder area for extra space
  if itemsTotalSize < root.Size() {
    a := (root.Size()-itemsTotalSize)/root.Size()*blockArea
    areas = append(areas, area{Area: a, TreeSizer: nil})
  }

  if dosort {
    sort.Sort(byAreaAndPlaceholder(areas))
  }

  return
}

<commit_msg>Cleanup<commit_after>// Package squarify implements the Squarified Treemap algorithm of Bruls, Huizing, and Van Wijk:
//
//    http://www.win.tue.nl/~vanwijk/stm.pdf
//
// The basic idea is to generate a tiling of items of various sizes, each of which may have children which
// are tiled nested inside their parent.
//
// Tiling is performed by calling the Squarify() function.
package squarify

import (
  "sort"
)

// The TreeSizer interface must be implemented by any tree that will be Squarified.
type TreeSizer interface {
  // Size is the logical size of the item and will be used to compute it's area.
  Size() float64
  // NumChildren is the number of children that this tree node has.
  NumChildren() int
  // Child returns the child of this node at the specified index.
  Child(i int) TreeSizer
}

// Rect represents a two-dimensional rectangle having the upper-left corner at coordinate (X,Y) and having
// width W and height H.
type Rect struct {
  X, Y, W, H float64
}

// Block represents the output for a single TreeSizer node from Squarify(). It's the layed-out
// position and size of the TreeSizer node and the associated TreeSizer that it represents.
type Block struct {
  // Position and size of the layed-out TreeSizer
  Rect
  // The TreeSizer represented by this Block
  TreeSizer TreeSizer
}

// area is the product of an intermediate step that stores the computed area for a
// TreeSizer
type area struct {
  Area float64
  TreeSizer TreeSizer
}

// direction represents the direction of layout for a row: Vertical or Horizontal.
type direction int

const (
  // Lay out the row vertically
  Vertical direction = iota
  // Lay out the row horizontally
  Horizontal
)

const (
  DoSort = true
  DontSort = false
)

// Margins defines the empty space that should exist between a parent Block and it's internal
// children Blocks when layed out. Similar to how the margins of a page separate the content from
// the page edges. The left (L), right (R), top (T) and bottom (B) margins are specified separately.
type Margins struct {
  L,R,T,B float64
}

// Meta is metadata that is returned by Squarify() for each Block it lays out.
type Meta struct {
  // Depth is the depth of the Block in the TreeSizer tree, starting from 0.
  // This is useful when coloring the blocks, for example.
  Depth int
}

// Options controls how Squarify() behaves.
type Options struct {
  // Maximum depth in the tree to descend to. Blocks at depth <= MaxDepth are layed out,
  // and > MaxDepth are ignored. Depth is counted starting at the children of the root node being 1.
  // If MaxDepth is left as the zero value then a MaxDepth of 20 is used.
  MaxDepth int
  // Margins between a parent and it's children.
  Margins *Margins
  // Sort the blocks by size within their parent. This pushes larger blocks to the left/above
  // smaller blocks.
  Sort bool
  // MinW and MinH limit the smallness of Blocks that are output. Blocks who's width is < MinW
  // or who's height is < MinH are not output, nor are their children processed.
  MinW, MinH float64
}

// Squarify implements the Squarified Treemap algorithm. It lays out the children of `root` inside the area
// represented by rect with areas proportional to the Size() of the children. Squarify returns
// a slice of Blocks for rendering and a slice of Metas which contain metainformation corresponding
// to the Blocks. Element block[i] has metadata in meta[i].
//
func Squarify(root TreeSizer, rect Rect, options Options) (blocks []Block, meta []Meta) {
  if options.MaxDepth <= 0 {
    options.MaxDepth = 20
  }

  return squarify(root, Block{Rect: rect}, options, 0)
}

// row is an internal structure used to represent the current row or column of blocks
// being layed out.
type row struct {
  areas []*area
  X,Y float64
  min, max float64 // Min and max areas in the row
  sum float64 // Sum of areas
  Width float64
  Dir direction
}

// newRow returns a new row layed out in the specified direction.
func newRow(dir direction, width,x,y float64) *row {
  return &row{
    areas: make([]*area,0),
    Width: width,
    X: x,
    Y: y,
    Dir: dir,
  }
}

// push adds an area to the end of the row.
func (r *row) push(a *area) {
  if a.Area <= 0 {
    // We use 0 area as a sentinel in min and max.
    panic("Area must be >= 0")
  }

  r.areas = append(r.areas, a)
  r.updateCached(a)
}

// pop removes and returns the area at the end of the row.
func (r *row) pop() *area {
  r.min = 0
  r.max = 0
  r.sum = 0

  if len(r.areas) > 0 {
    last := len(r.areas)-1
    a := r.areas[last]
    r.areas[last] = nil
    r.areas = r.areas[0:last]
    return a
  } else {
    return nil
  }
}

// pushTemporarily pushes area `a` onto the Row, runs f(), then pops the Row.
// The Row is the same before and after this call.
func (r *row) pushTemporarily(a *area, f func()) {
  min := r.min
  max := r.max
  sum := r.sum
  r.push(a)
  f()
  r.pop()
  r.min = min
  r.max = max
  r.sum = sum
}

// calcCached calculates cached values for the row.
func (r *row) calcCached() {
  r.min = 0
  r.max = 0
  r.sum = 0
  for _,a := range r.areas {
    r.updateCached(a)
  }
}

// size returns the number of elements in the row
func (r row) size() int {
  return len(r.areas)
}

// updateCached updates the cached row values with `a` added to the row.
func (r *row) updateCached(a *area) {
  if r.min <= 0 || a.Area < r.min {
    r.min = a.Area
  }
  if r.max <= 0 || a.Area > r.max {
    r.max = a.Area
  }
  r.sum += a.Area
}

// Calculate the worst aspect ratio of all rectangles in the row
func (r *row) worst() float64 {
  if r.min == 0 {
    // We need to calculate min, max, and sum
    r.calcCached()
  }

  w2 := r.Width*r.Width
  sum2 := r.sum*r.sum
  worst1 := w2*r.max / sum2
  worst2 := sum2 / (r.min * w2)
  if worst1 > worst2 {
    return worst1
  } else {
    return worst2
  }
}

// makeBlocks creates the final slice of blocks for the row.
func (r *row) makeBlocks() (height float64, blocks []Block) {
  if r.min == 0 {
    // We need to calculate min, max, and sum
    r.calcCached()
  }

  blocks = make([]Block,0)
  x := r.X
  y := r.Y

  for _, a := range r.areas {
    // Item width relative to the row
    relativeWidth := a.Area/r.sum
    itemWidth := relativeWidth * r.Width
    itemHeight := a.Area/itemWidth

    if height == 0 {
      height = itemHeight
    } else if itemHeight != height {
      itemHeight = height
    }

    if r.Dir == Vertical {
      // swap
      itemWidth, itemHeight = itemHeight, itemWidth
    }

    blocks = append(blocks, Block{Rect: Rect{X: x, Y: y, W: itemWidth, H: itemHeight}, TreeSizer: a.TreeSizer})

    if r.Dir == Vertical {
      y += itemHeight
    } else {
      x += itemWidth
    }
  }

  return
}

// Internal squarify function. Squarify is a frontend to this.
func squarify(root TreeSizer, block Block, options Options, depth int) (blocks []Block, meta []Meta) {
  blocks = make([]Block, 0)
  meta = make([]Meta, 0)

  if block.W <= options.MinW || block.H <= options.MinH || depth >= options.MaxDepth {
    return
  }

  output := func(newBlocks []Block) {
    for i := 0; i < len(newBlocks); i++ {
      // Filter out any blocks that are just placeholders for extra space
      if newBlocks[i].TreeSizer != nil {
        // Filter out any blocks that are too small
        if newBlocks[i].W > options.MinW || newBlocks[i].H > options.MinH {
          blocks = append(blocks, newBlocks[i])
          meta = append(meta, Meta{Depth: depth})
        }
      }
    }
  }

  areas := areas(root, block, options.Sort)

  rowX := block.X
  rowY := block.Y
  freeWidth := block.W
  freeHeight := block.H

  makeRow := func() (row *row) {
    if block.W > block.H {
      row = newRow(Vertical, freeHeight, rowX, rowY)
    } else {
      row = newRow(Horizontal, freeWidth, rowX, rowY)
    }
    return row
  }

  // Decide which direction to create the new row
  row := makeRow()

  for _, a := range areas {
    if row.size() > 0 {
      worstBefore := row.worst()
      worstAfter := float64(0)
      row.pushTemporarily(&a, func() {
        worstAfter = row.worst()
      })

      if worstBefore < worstAfter {
        // It's better to make a new row now.
        // Output the current blocks and make a new row
        offset, newBlocks := row.makeBlocks()
        output(newBlocks)

        if row.Dir == Vertical {
          rowX += offset
          freeWidth -= offset
        } else {
          rowY += offset
          freeHeight -= offset
        }

        row = makeRow()
      }
    }

    cp := &area{}
    *cp = a
    row.push(cp)
  }

  if row.size() > 0 {
    _, newBlocks := row.makeBlocks()
    output(newBlocks)
  }

  // Now, for each of the items we just processed, if they have children then
  // lay them out inside their parent box. The available area may be reduced by
  // certain size.
  for _, block := range(blocks) {
    if block.TreeSizer != nil {
      if options.Margins != nil {
        block.X += options.Margins.L
        block.Y += options.Margins.T
        block.W -= options.Margins.L + options.Margins.R
        block.H -= options.Margins.T + options.Margins.B
      }

      newBlocks, newMeta := squarify(block.TreeSizer, block, options, depth+1)
      blocks = append(blocks, newBlocks...)
      meta = append(meta, newMeta...)
    }
  }

  return
}

// Sort areas by area.
type byAreaAndPlaceholder []area

func (a byAreaAndPlaceholder) Len() int {
  return len(a)
}

func (a byAreaAndPlaceholder) Less(i, j int) bool {
  //return a[i].Area > a[j].Area

  if a[i].TreeSizer != nil && a[j].TreeSizer != nil || a[i].TreeSizer == nil && a[j].TreeSizer == nil {
    return a[i].Area > a[j].Area
  } else {
    return a[i].TreeSizer != nil
  }
}

func (a byAreaAndPlaceholder) Swap(i, j int) {
  a[i], a[j] = a[j], a[i]
}

// areas computes the areas for the children of `root` and sorts them if dosort is true.
// The area of a child is calculated as it's proportion of the parent's Block's size, where
// `root` is the parent, and `Block` is the dimensions of the parent.
func areas(root TreeSizer, block Block, dosort bool) (areas []area) {
  blockArea := block.W * block.H

  areas = make([]area,0)
  itemsTotalSize := float64(0)

  for i := 0; i < root.NumChildren(); i++ {
    item := root.Child(i)

    // Ignore 0-size items
    if item.Size() <= 0 {
      continue
    }

    areas = append(areas, area{Area: item.Size()/root.Size()*blockArea, TreeSizer: item})
    itemsTotalSize += item.Size()
  }

  // Add a placeholder area for extra space
  if itemsTotalSize < root.Size() {
    a := (root.Size()-itemsTotalSize)/root.Size()*blockArea
    areas = append(areas, area{Area: a, TreeSizer: nil})
  }

  if dosort {
    sort.Sort(byAreaAndPlaceholder(areas))
  }

  return
}

<|endoftext|>


<commit_before>package sra

// What do the different SRA accessions represent?
// http://www.ncbi.nlm.nih.gov/books/NBK56913/#search.what_do_the_different_sra_accessi

import (
	"encoding/xml"
	"log"
	"path/filepath"
	"strings"
	"time"
)

type SRASetter interface {
	GetItems() []Itemer
	GetAccessions() []string
}

type Itemer interface {
	String() string
	XMLString() string
	GetAccession() string
}

type SraItem struct {
	Id           string
	SubmissionId string
	XMLFileName  string
	Type         string
	Status       string
	Updated      time.Time
	Published    time.Time
	Received     time.Time
	Visibility   string
	Data         Itemer
}

func (si *SraItem) setId() {
	accession := si.Data.GetAccession()
	si.Id = accession
}

func (si *SraItem) AddAttrFromAccessionRecords(
	db *map[string]*AccessionRecord,
) {
	if data, ok := (*db)[si.Id]; ok {
		si.Status = data.Status
		si.Updated = data.Updated
		si.Published = data.Published
		si.Received = data.Received
		si.Visibility = data.Visibility
	}
}

func NewSraItemsFromXML(filename string, contents []byte) []*SraItem {
	basename := filepath.Base(filename)
	id, sraType, _ := parseXMLFileName(basename)
	set := parseXMLContents(sraType, contents)

	sraItems := make([]*SraItem, 0)

	for _, item := range set.GetItems() {
		si := &SraItem{
			SubmissionId: id,
			XMLFileName:  basename,
			Type:         sraType,
			Data:         item,
		}
		si.setId()
		sraItems = append(sraItems, si)
	}
	return sraItems
}

func parseXMLFileName(filename string) (string, string, string) {
	items := strings.Split(filename, ".")
	submissionAccession, sraType, extension := items[0], items[1], items[2]
	return submissionAccession, sraType, extension
}

func parseXMLContents(sraType string, contents []byte) SRASetter {
	var data SRASetter
	switch sraType {
	case "analysis":
		var analyses SraAnalysisSet
		xml.Unmarshal(contents, &analyses)
		data = analyses
	case "experiment":
		var exps SraExpSet
		xml.Unmarshal(contents, &exps)
		data = exps
	case "run":
		var runs SraRunSet
		xml.Unmarshal(contents, &runs)
		data = runs
	case "sample":
		var samples SraSampleSet
		xml.Unmarshal(contents, &samples)
		data = samples
	case "study":
		var studies SraStudySet
		xml.Unmarshal(contents, &studies)
		data = studies
	case "submission":
		var submission SraSubmission
		xml.Unmarshal(contents, &submission)
		data = submission
	default:
		log.Fatalf(
			"Don't know how to parse '%s' XML contents!",
			sraType,
		)
	}
	return data
}
<commit_msg>+ introduce a grotesque UnMarshalJSON function<commit_after>package sra

// What do the different SRA accessions represent?
// http://www.ncbi.nlm.nih.gov/books/NBK56913/#search.what_do_the_different_sra_accessi

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"log"
	"path/filepath"
	"strings"
	"time"
)

type SRASetter interface {
	GetItems() []Itemer
	GetAccessions() []string
}

type Itemer interface {
	String() string
	XMLString() string
	GetAccession() string
}

type SraItem struct {
	Id           string
	SubmissionId string
	XMLFileName  string
	Type         string
	Status       string
	Updated      time.Time
	Published    time.Time
	Received     time.Time
	Visibility   string
	Data         Itemer
}

func (si *SraItem) setId() {
	accession := si.Data.GetAccession()
	si.Id = accession
}

func (si *SraItem) AddAttrFromAccessionRecords(
	db *map[string]*AccessionRecord,
) {
	if data, ok := (*db)[si.Id]; ok {
		si.Status = data.Status
		si.Updated = data.Updated
		si.Published = data.Published
		si.Received = data.Received
		si.Visibility = data.Visibility
	}
}

func (si *SraItem) UnmarshalJSON(data []byte) error {
	var aux struct {
		Id           string
		SubmissionId string
		XMLFileName  string
		Type         string
		Status       string
		Visibility   string
		Updated      string
		Published    string
		Received     string
		Data         json.RawMessage
	}

	dec := json.NewDecoder(bytes.NewReader(data))
	if err := dec.Decode(&aux); err != nil {
		return fmt.Errorf("decode sra item: %v", err)
	}

	si.Id = aux.Id
	si.SubmissionId = aux.SubmissionId
	si.XMLFileName = aux.XMLFileName
	si.Type = aux.Type
	si.Status = aux.Status
	si.Visibility = aux.Visibility

	t, err := time.Parse("2006-01-02T15:04:05Z", aux.Updated)
	if err != nil {
		return fmt.Errorf(
			"%s: '%s' : %s\n",
			"JSON Unmarshal error: Trouble parsing timestamp",
			aux.Updated,
			err,
		)
	}
	si.Updated = t

	t, err = time.Parse("2006-01-02T15:04:05Z", aux.Published)
	if err != nil {
		return fmt.Errorf(
			"%s: '%s' : %s\n",
			"JSON Unmarshal error: Trouble parsing timestamp",
			aux.Published,
			err,
		)
	}
	si.Published = t

	t, err = time.Parse("2006-01-02T15:04:05Z", aux.Received)
	if err != nil {
		return fmt.Errorf(
			"%s: '%s' : %s\n",
			"JSON Unmarshal error: Trouble parsing timestamp",
			aux.Received,
			err,
		)
	}
	si.Received = t

	var item Itemer
	switch si.Type {
	case "analysis":
		var analysis SraAnalysis
		err = json.Unmarshal([]byte(aux.Data), &analysis)
		if err != nil {
			return fmt.Errorf(
				"[SraAnalysis] JSON Unmarshal error: %v", err,
			)
		}
		item = analysis
	case "experiment":
		var exp SraExp
		err = json.Unmarshal([]byte(aux.Data), &exp)
		if err != nil {
			return fmt.Errorf(
				"[SraExp] JSON Unmarshal error: %v", err,
			)
		}
		item = exp
	case "run":
		var run SraRun
		err = json.Unmarshal([]byte(aux.Data), &run)
		if err != nil {
			return fmt.Errorf(
				"[SraRun] JSON Unmarshal error: %v", err,
			)
		}
		item = run
	case "sample":
		var sample SraSample
		err = json.Unmarshal([]byte(aux.Data), &sample)
		if err != nil {
			return fmt.Errorf(
				"[SraSample] JSON Unmarshal error: %v", err,
			)
		}
		item = sample
	case "study":
		var study SraStudy
		err = json.Unmarshal([]byte(aux.Data), &study)
		if err != nil {
			return fmt.Errorf(
				"[SraStudy] JSON Unmarshal error: %v", err,
			)
		}
		item = study
	case "submission":
		var submission SraSubmission
		err = json.Unmarshal([]byte(aux.Data), &submission)
		if err != nil {
			return fmt.Errorf(
				"[SraSubmission] JSON Unmarshal error: %v", err,
			)
		}
		item = submission
	default:
		return fmt.Errorf(
			"Don't know how to parse sra item of type '%s'\n",
			si.Type,
		)
	}

	si.Data = item
	return nil
}

func NewSraItemsFromXML(filename string, contents []byte) []*SraItem {
	basename := filepath.Base(filename)
	id, sraType, _ := parseXMLFileName(basename)
	set := parseXMLContents(sraType, contents)

	sraItems := make([]*SraItem, 0)

	for _, item := range set.GetItems() {
		si := &SraItem{
			SubmissionId: id,
			XMLFileName:  basename,
			Type:         sraType,
			Data:         item,
		}
		si.setId()
		sraItems = append(sraItems, si)
	}
	return sraItems
}

func parseXMLFileName(filename string) (string, string, string) {
	items := strings.Split(filename, ".")
	submissionAccession, sraType, extension := items[0], items[1], items[2]
	return submissionAccession, sraType, extension
}

func parseXMLContents(sraType string, contents []byte) SRASetter {
	var data SRASetter
	switch sraType {
	case "analysis":
		var analyses SraAnalysisSet
		xml.Unmarshal(contents, &analyses)
		data = analyses
	case "experiment":
		var exps SraExpSet
		xml.Unmarshal(contents, &exps)
		data = exps
	case "run":
		var runs SraRunSet
		xml.Unmarshal(contents, &runs)
		data = runs
	case "sample":
		var samples SraSampleSet
		xml.Unmarshal(contents, &samples)
		data = samples
	case "study":
		var studies SraStudySet
		xml.Unmarshal(contents, &studies)
		data = studies
	case "submission":
		var submission SraSubmission
		xml.Unmarshal(contents, &submission)
		data = submission
	default:
		log.Fatalf(
			"Don't know how to parse '%s' XML contents!",
			sraType,
		)
	}
	return data
}
<|endoftext|>