<commit_before>package tcgif

import (
	"image"
	"image/color"
	"image/gif"
	"sort"
)

type coord struct {
	X, Y int
}

type colorCount struct {
	C      color.Color
	Coords []coord
}

type colorCountList []colorCount

func (p colorCountList) Len() int           { return len(p) }
func (p colorCountList) Less(i, j int) bool { return len(p[i].Coords) < len(p[j].Coords) }
func (p colorCountList) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

type GIFMaker struct {
	frameDelay int
	finalDelay int

	frameLimit uint

	backfill bool
	popsort  bool
}

type Option func(*GIFMaker)

func WithFrameDelay(delay int) Option {
	return func(g *GIFMaker) {
		g.frameDelay = delay
	}
}

func WithFinalDelay(delay int) Option {
	return func(g *GIFMaker) {
		g.finalDelay = delay
	}
}

func WithFrameLimit(limit uint) Option {
	return func(g *GIFMaker) {
		g.frameLimit = limit
	}
}

func WithBackfill(backfill bool) Option {
	return func(g *GIFMaker) {
		g.backfill = backfill
	}
}

func WithPopularitySort(popsort bool) Option {
	return func(g *GIFMaker) {
		g.popsort = popsort
	}
}

func NewGIFMaker(opts ...Option) *GIFMaker {
	gm := &GIFMaker{
		frameDelay: 2,
		finalDelay: 300,
		frameLimit: 0,
		backfill:   true,
		popsort:    true,
	}

	for _, opt := range opts {
		opt(gm)
	}

	return gm
}

func (gm *GIFMaker) MakeGIF(img image.Image) (*gif.GIF, error) {
	b := img.Bounds()
	colormap := make(map[color.Color][]coord)

	for y := 0; y <= b.Max.Y; y++ {
		for x := 0; x <= b.Max.X; x++ {
			c := img.At(x, y)
			colormap[c] = append(colormap[c], coord{x, y})
		}
	}

	colorhisto := make(colorCountList, 0)
	for c, e := range colormap {
		colorhisto = append(colorhisto, colorCount{c, e})
	}

	if gm.popsort {
		sort.Sort(sort.Reverse(colorhisto))
	}

	seglen := (len(colorhisto) / 254) + 1
	segments := make([]colorCountList, seglen)

	x := 0
	for _, xxx := range colorhisto {
		n := x / 254 //integer division
		segments[n] = append(segments[n], xxx)

		x++
	}

	limitSeglen := seglen
	if gm.frameLimit != 0 && int(gm.frameLimit) < limitSeglen {
		limitSeglen = int(gm.frameLimit)
	}

	g := &gif.GIF{}
	for i := 0; i < limitSeglen; i++ {
		pimg := image.NewPaletted(b, color.Palette{})
		// Add trasparency first so it's used as the matte color
		pimg.Palette = append(pimg.Palette, color.Transparent)
		g.Image = append(g.Image, pimg)

		for _, ch := range segments[i] {
			pimg.Palette = append(pimg.Palette, ch.C)
			ind := pimg.Palette.Index(ch.C)

			for _, ccoord := range ch.Coords {
				pimg.SetColorIndex(ccoord.X, ccoord.Y, uint8(ind))
			}
		}

		if gm.backfill {
			for j := i + 1; j < seglen; j++ {
				for _, ch := range segments[j] {
					ind := pimg.Palette.Index(ch.C)

					for _, ccoord := range ch.Coords {
						pimg.SetColorIndex(ccoord.X, ccoord.Y, uint8(ind))
					}
				}
			}
		}
	}

	g.Delay = make([]int, len(g.Image))
	for i := range g.Delay {
		g.Delay[i] = gm.frameDelay
	}

	g.Delay[len(g.Delay)-1] = gm.finalDelay

	return g, nil
}
<commit_msg>Adds docs<commit_after>package tcgif

import (
	"image"
	"image/color"
	"image/gif"
	"sort"
)

type coord struct {
	X, Y int
}

type colorCount struct {
	C      color.Color
	Coords []coord
}

type colorCountList []colorCount

func (p colorCountList) Len() int           { return len(p) }
func (p colorCountList) Less(i, j int) bool { return len(p[i].Coords) < len(p[j].Coords) }
func (p colorCountList) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// GIFMaker is a struct used to create a "Truecolor GIF" from an image
type GIFMaker struct {
	frameDelay int
	finalDelay int

	frameLimit uint

	backfill bool
	popsort  bool
}

// Option is a function passed to NewGIFMaker to modify the behavior of GIFMaker
type Option func(*GIFMaker)

// WithFrameDelay sets the delay for each frame in the GIF in 100ths of a second
// excluding the final frame which is determined by WithFinalDelay
func WithFrameDelay(delay int) Option {
	return func(g *GIFMaker) {
		g.frameDelay = delay
	}
}

// WithFinalDelay sets the delay for the final frame of the GIF
// in 100ths of a second - default is 300
func WithFinalDelay(delay int) Option {
	return func(g *GIFMaker) {
		g.finalDelay = delay
	}
}

// WithFrameLimit sets the maximum number of frames to be included in the GIF
func WithFrameLimit(limit uint) Option {
	return func(g *GIFMaker) {
		g.frameLimit = limit
	}
}

// WithBackfill sets whether the GIF will be backfilled preemptively with the
// closest color in the palette.
func WithBackfill(backfill bool) Option {
	return func(g *GIFMaker) {
		g.backfill = backfill
	}
}

// WithPopularitySort sets whether the total palette is sorted by popularity
//
// Disabling this option will result in the palette being sorted by the order
// in which the colors were found to the image. This can improve performance
// but may result in a less visually pleasing GIF
func WithPopularitySort(popsort bool) Option {
	return func(g *GIFMaker) {
		g.popsort = popsort
	}
}

// NewGIFMaker creates a new GIFMaker with the specified options
func NewGIFMaker(opts ...Option) *GIFMaker {
	gm := &GIFMaker{
		frameDelay: 2,
		finalDelay: 300,
		frameLimit: 0,
		backfill:   true,
		popsort:    true,
	}

	for _, opt := range opts {
		opt(gm)
	}

	return gm
}

// MakeGIF creates a "Truecolor GIF" from the given image using the given options
func (gm *GIFMaker) MakeGIF(img image.Image) (*gif.GIF, error) {
	b := img.Bounds()
	colormap := make(map[color.Color][]coord)

	for y := 0; y <= b.Max.Y; y++ {
		for x := 0; x <= b.Max.X; x++ {
			c := img.At(x, y)
			colormap[c] = append(colormap[c], coord{x, y})
		}
	}

	colorhisto := make(colorCountList, 0)
	for c, e := range colormap {
		colorhisto = append(colorhisto, colorCount{c, e})
	}

	if gm.popsort {
		sort.Sort(sort.Reverse(colorhisto))
	}

	seglen := (len(colorhisto) / 254) + 1
	segments := make([]colorCountList, seglen)

	x := 0
	for _, xxx := range colorhisto {
		n := x / 254 //integer division
		segments[n] = append(segments[n], xxx)

		x++
	}

	limitSeglen := seglen
	if gm.frameLimit != 0 && int(gm.frameLimit) < limitSeglen {
		limitSeglen = int(gm.frameLimit)
	}

	g := &gif.GIF{}
	for i := 0; i < limitSeglen; i++ {
		pimg := image.NewPaletted(b, color.Palette{})
		// Add trasparency first so it's used as the matte color
		pimg.Palette = append(pimg.Palette, color.Transparent)
		g.Image = append(g.Image, pimg)

		for _, ch := range segments[i] {
			pimg.Palette = append(pimg.Palette, ch.C)
			ind := pimg.Palette.Index(ch.C)

			for _, ccoord := range ch.Coords {
				pimg.SetColorIndex(ccoord.X, ccoord.Y, uint8(ind))
			}
		}

		if gm.backfill {
			for j := i + 1; j < seglen; j++ {
				for _, ch := range segments[j] {
					ind := pimg.Palette.Index(ch.C)

					for _, ccoord := range ch.Coords {
						pimg.SetColorIndex(ccoord.X, ccoord.Y, uint8(ind))
					}
				}
			}
		}
	}

	g.Delay = make([]int, len(g.Image))
	for i := range g.Delay {
		g.Delay[i] = gm.frameDelay
	}

	g.Delay[len(g.Delay)-1] = gm.finalDelay

	return g, nil
}
<|endoftext|>


<commit_before>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package utils

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strings"

	"github.com/juju/loggo"

	"launchpad.net/juju-core/juju/osenv"
)

var (
	aptLogger  = loggo.GetLogger("juju.utils.apt")
	aptProxyRE = regexp.MustCompile(`(?im)^\s*Acquire::(?P<protocol>[a-z]+)::Proxy\s+"(?P<proxy>[^"]+)";\s*$`)

	// AptConfFile is the full file path for the proxy settings that are
	// written by cloud-init and the machine environ worker.
	AptConfFile = "/etc/apt/apt.conf.d/42-juju-proxy-settings"
)

// Some helpful functions for running apt in a sane way

// AptCommandOutput calls cmd.Output, this is used as an overloading point so we
// can test what *would* be run without actually executing another program
var AptCommandOutput = (*exec.Cmd).CombinedOutput

// This is the default apt-get command used in cloud-init, the various settings
// mean that apt won't actually block waiting for a prompt from the user.
var aptGetCommand = []string{
	"apt-get", "--option=Dpkg::Options::=--force-confold",
	"--option=Dpkg::options::=--force-unsafe-io", "--assume-yes", "--quiet",
}

// aptEnvOptions are options we need to pass to apt-get to not have it prompt
// the user
var aptGetEnvOptions = []string{"DEBIAN_FRONTEND=noninteractive"}

// cloudArchivePackages maintaines a list of packages that AptGetPreparePackages
// should reference when determining the --target-release for a given series.
// http://reqorts.qa.ubuntu.com/reports/ubuntu-server/cloud-archive/cloud-tools_versions.html
var cloudArchivePackages = map[string]bool{
	"cloud-utils":             true,
	"curtin":                  true,
	"djorm-ext-pgarray":       true,
	"golang":                  true,
	"iproute2":                true,
	"isc-dhcp":                true,
	"juju-core":               true,
	"libseccomp":              true,
	"libv8-3.14":              true,
	"lxc":                     true,
	"maas":                    true,
	"mongodb":                 true,
	"python-django":           true,
	"python-django-piston":    true,
	"python-jujuclient":       true,
	"python-tx-tftp":          true,
	"python-websocket-client": true,
	"raphael 2.1.0-1ubuntu1":  true,
	"simplestreams":           true,
	"txlongpoll":              true,
	"uvtool":                  true,
	"yui3":                    true,
}

// targetRelease returns a string base on the current series
// that is suitable for use with the apt-get --target-release option
func targetRelease(series string) string {
	switch series {
	case "precise":
		return "precise-updates/cloud-tools"
	default:
		return ""
	}
}

// AptGetPreparePackages returns a slice of installCommands. Each item
// in the slice is suitable for passing directly to AptGetInstall.
//
// AptGetPreparePackages will inspect the series passed to it
// and properly generate an installCommand entry with a --target-release
// should the series be an LTS release with cloud archive packages.
func AptGetPreparePackages(packages []string, series string) [][]string {
	var installCommands [][]string
	if target := targetRelease(series); target != "" {
		var pkgs []string
		pkgs_with_target := []string{"--target-release", target}
		for _, pkg := range packages {
			if cloudArchivePackages[pkg] {
				pkgs_with_target = append(pkgs_with_target, pkg)
			} else {
				pkgs = append(pkgs, pkg)
			}
		}

		// We check for >2 here so that we only append pkgs_with_target
		// if there was an actual package in the slice.
		if len(pkgs_with_target) > 2 {
			installCommands = append(installCommands, pkgs_with_target)
		}

		// Sometimes we may end up with all cloudArchivePackages
		// in that case we do not want to append an empty slice of pkgs
		if len(pkgs) > 0 {
			installCommands = append(installCommands, pkgs)
		}
	} else {
		installCommands = append(installCommands, packages)
	}
	return installCommands
}

// AptGetInstall runs 'apt-get install packages' for the packages listed here
func AptGetInstall(packages ...string) error {
	cmdArgs := append([]string(nil), aptGetCommand...)
	cmdArgs = append(cmdArgs, "install")
	cmdArgs = append(cmdArgs, packages...)
	aptLogger.Infof("Running: %s", cmdArgs)
	cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
	cmd.Env = append(os.Environ(), aptGetEnvOptions...)
	out, err := AptCommandOutput(cmd)
	if err != nil {
		aptLogger.Errorf("apt-get command failed: %v\nargs: %#v\n%s",
			err, cmdArgs, string(out))
		return fmt.Errorf("apt-get failed: %v", err)
	}
	return nil
}

// AptConfigProxy will consult apt-config about the configured proxy
// settings. If there are no proxy settings configured, an empty string is
// returned.
func AptConfigProxy() (string, error) {
	cmdArgs := []string{
		"apt-config",
		"dump",
		"Acquire::http::Proxy",
		"Acquire::https::Proxy",
		"Acquire::ftp::Proxy",
	}
	cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
	out, err := AptCommandOutput(cmd)
	if err != nil {
		aptLogger.Errorf("apt-config command failed: %v\nargs: %#v\n%s",
			err, cmdArgs, string(out))
		return "", fmt.Errorf("apt-config failed: %v", err)
	}
	return string(bytes.Join(aptProxyRE.FindAll(out, -1), []byte("\n"))), nil
}

// DetectAptProxies will parse the results of AptConfigProxy to return a
// ProxySettings instance.
func DetectAptProxies() (result osenv.ProxySettings, err error) {
	output, err := AptConfigProxy()
	if err != nil {
		return result, err
	}
	for _, match := range aptProxyRE.FindAllStringSubmatch(output, -1) {
		switch match[1] {
		case "http":
			result.Http = match[2]
		case "https":
			result.Https = match[2]
		case "ftp":
			result.Ftp = match[2]
		}
	}
	return result, nil
}

// AptProxyContent produces the format expected by the apt config files
// from the ProxySettings struct.
func AptProxyContent(proxy osenv.ProxySettings) string {
	lines := []string{}
	addLine := func(proxy, value string) {
		if value != "" {
			lines = append(lines, fmt.Sprintf(
				"Acquire::%s::Proxy %q;", proxy, value))
		}
	}
	addLine("http", proxy.Http)
	addLine("https", proxy.Https)
	addLine("ftp", proxy.Ftp)
	return strings.Join(lines, "\n")
}

// IsUbuntu executes lxb_release to see if the host OS is Ubuntu.
func IsUbuntu() bool {
	out, err := RunCommand("lsb_release", "-i", "-s")
	if err != nil {
		return false
	}
	return strings.TrimSpace(out) == "Ubuntu"
}

// IsPackageInstalled uses dpkg-query to determine if the `packageName`
// package is installed.
func IsPackageInstalled(packageName string) bool {
	_, err := RunCommand("dpkg-query", "--status", packageName)
	return err == nil
}
<commit_msg>utils/apt: updates based on review<commit_after>// Copyright 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package utils

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"regexp"
	"strings"

	"github.com/juju/loggo"

	"launchpad.net/juju-core/juju/osenv"
)

var (
	aptLogger  = loggo.GetLogger("juju.utils.apt")
	aptProxyRE = regexp.MustCompile(`(?im)^\s*Acquire::(?P<protocol>[a-z]+)::Proxy\s+"(?P<proxy>[^"]+)";\s*$`)

	// AptConfFile is the full file path for the proxy settings that are
	// written by cloud-init and the machine environ worker.
	AptConfFile = "/etc/apt/apt.conf.d/42-juju-proxy-settings"
)

// Some helpful functions for running apt in a sane way

// AptCommandOutput calls cmd.Output, this is used as an overloading point so we
// can test what *would* be run without actually executing another program
var AptCommandOutput = (*exec.Cmd).CombinedOutput

// This is the default apt-get command used in cloud-init, the various settings
// mean that apt won't actually block waiting for a prompt from the user.
var aptGetCommand = []string{
	"apt-get", "--option=Dpkg::Options::=--force-confold",
	"--option=Dpkg::options::=--force-unsafe-io", "--assume-yes", "--quiet",
}

// aptEnvOptions are options we need to pass to apt-get to not have it prompt
// the user
var aptGetEnvOptions = []string{"DEBIAN_FRONTEND=noninteractive"}

// cloudArchivePackages maintaines a list of packages that AptGetPreparePackages
// should reference when determining the --target-release for a given series.
// http://reqorts.qa.ubuntu.com/reports/ubuntu-server/cloud-archive/cloud-tools_versions.html
var cloudArchivePackages = map[string]bool{
	"cloud-utils":             true,
	"curtin":                  true,
	"djorm-ext-pgarray":       true,
	"golang":                  true,
	"iproute2":                true,
	"isc-dhcp":                true,
	"juju-core":               true,
	"libseccomp":              true,
	"libv8-3.14":              true,
	"lxc":                     true,
	"maas":                    true,
	"mongodb":                 true,
	"python-django":           true,
	"python-django-piston":    true,
	"python-jujuclient":       true,
	"python-tx-tftp":          true,
	"python-websocket-client": true,
	"raphael 2.1.0-1ubuntu1":  true,
	"simplestreams":           true,
	"txlongpoll":              true,
	"uvtool":                  true,
	"yui3":                    true,
}

// targetRelease returns a string base on the current series
// that is suitable for use with the apt-get --target-release option
func targetRelease(series string) string {
	switch series {
	case "precise":
		return "precise-updates/cloud-tools"
	default:
		return ""
	}
}

// AptGetPreparePackages returns a slice of installCommands. Each item
// in the slice is suitable for passing directly to AptGetInstall.
//
// AptGetPreparePackages will inspect the series passed to it
// and properly generate an installCommand entry with a --target-release
// should the series be an LTS release with cloud archive packages.
func AptGetPreparePackages(packages []string, series string) [][]string {
	var installCommands [][]string
	if target := targetRelease(series); target == "" {
		return append(installCommands, packages)
	} else {
		var pkgs []string
		pkgs_with_target := []string{"--target-release", target}
		for _, pkg := range packages {
			if cloudArchivePackages[pkg] {
				pkgs_with_target = append(pkgs_with_target, pkg)
			} else {
				pkgs = append(pkgs, pkg)
			}
		}

		// We check for >2 here so that we only append pkgs_with_target
		// if there was an actual package in the slice.
		if len(pkgs_with_target) > 2 {
			installCommands = append(installCommands, pkgs_with_target)
		}

		// Sometimes we may end up with all cloudArchivePackages
		// in that case we do not want to append an empty slice of pkgs
		if len(pkgs) > 0 {
			installCommands = append(installCommands, pkgs)
		}

		return installCommands
	}
}

// AptGetInstall runs 'apt-get install packages' for the packages listed here
func AptGetInstall(packages ...string) error {
	cmdArgs := append([]string(nil), aptGetCommand...)
	cmdArgs = append(cmdArgs, "install")
	cmdArgs = append(cmdArgs, packages...)
	aptLogger.Infof("Running: %s", cmdArgs)
	cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
	cmd.Env = append(os.Environ(), aptGetEnvOptions...)
	out, err := AptCommandOutput(cmd)
	if err != nil {
		aptLogger.Errorf("apt-get command failed: %v\nargs: %#v\n%s",
			err, cmdArgs, string(out))
		return fmt.Errorf("apt-get failed: %v", err)
	}
	return nil
}

// AptConfigProxy will consult apt-config about the configured proxy
// settings. If there are no proxy settings configured, an empty string is
// returned.
func AptConfigProxy() (string, error) {
	cmdArgs := []string{
		"apt-config",
		"dump",
		"Acquire::http::Proxy",
		"Acquire::https::Proxy",
		"Acquire::ftp::Proxy",
	}
	cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
	out, err := AptCommandOutput(cmd)
	if err != nil {
		aptLogger.Errorf("apt-config command failed: %v\nargs: %#v\n%s",
			err, cmdArgs, string(out))
		return "", fmt.Errorf("apt-config failed: %v", err)
	}
	return string(bytes.Join(aptProxyRE.FindAll(out, -1), []byte("\n"))), nil
}

// DetectAptProxies will parse the results of AptConfigProxy to return a
// ProxySettings instance.
func DetectAptProxies() (result osenv.ProxySettings, err error) {
	output, err := AptConfigProxy()
	if err != nil {
		return result, err
	}
	for _, match := range aptProxyRE.FindAllStringSubmatch(output, -1) {
		switch match[1] {
		case "http":
			result.Http = match[2]
		case "https":
			result.Https = match[2]
		case "ftp":
			result.Ftp = match[2]
		}
	}
	return result, nil
}

// AptProxyContent produces the format expected by the apt config files
// from the ProxySettings struct.
func AptProxyContent(proxy osenv.ProxySettings) string {
	lines := []string{}
	addLine := func(proxy, value string) {
		if value != "" {
			lines = append(lines, fmt.Sprintf(
				"Acquire::%s::Proxy %q;", proxy, value))
		}
	}
	addLine("http", proxy.Http)
	addLine("https", proxy.Https)
	addLine("ftp", proxy.Ftp)
	return strings.Join(lines, "\n")
}

// IsUbuntu executes lxb_release to see if the host OS is Ubuntu.
func IsUbuntu() bool {
	out, err := RunCommand("lsb_release", "-i", "-s")
	if err != nil {
		return false
	}
	return strings.TrimSpace(out) == "Ubuntu"
}

// IsPackageInstalled uses dpkg-query to determine if the `packageName`
// package is installed.
func IsPackageInstalled(packageName string) bool {
	_, err := RunCommand("dpkg-query", "--status", packageName)
	return err == nil
}
<|endoftext|>


<commit_before>package gherkin

type Location struct {
	Line   int `json:"line"`
	Column int `json:"column"`
}

type Node struct {
	Location *Location `json:"location,omitempty"`
	Type     string    `json:"type"`
}

type Feature struct {
	Node
	Tags                []*Tag        `json:"tags"`
	Language            string        `json:"language,omitempty"`
	Keyword             string        `json:"keyword"`
	Name                string        `json:"name"`
	Description         string        `json:"description,omitempty"`
	Background          *Background   `json:"background,omitempty"`
	ScenarioDefinitions []interface{} `json:"scenarioDefinitions"`
}

func (f *Feature) DescribeTo(v Visitor) {
	v.VisitFeature(f)
}

type Tag struct {
	Node
	Location *Location `json:"location,omitempty"`
	Name     string    `json:"name"`
}

type Background struct {
	ScenarioDefinition
}

func (b *Background) DescribeTo(v Visitor) {
	v.VisitBackground(b)
}

type Scenario struct {
	ScenarioDefinition
	Tags []*Tag `json:"tags"`
}

func (s *Scenario) DescribeTo(v Visitor) {
	v.VisitScenario(s)
}

type ScenarioOutline struct {
	ScenarioDefinition
	Tags     []*Tag      `json:"tags"`
	Examples []*Examples `json:"examples,omitempty"`
}

func (s *ScenarioOutline) DescribeTo(v Visitor) {
	v.VisitScenarioOutline(s)
}

type Examples struct {
	Node
	Tags        []*Tag      `json:"tags"`
	Keyword     string      `json:"keyword"`
	Name        string      `json:"name"`
	Description string      `json:"description,omitempty"`
	Rows        []*TableRow `json:"rows"`
}

func (e *Examples) DescribeTo(v Visitor) {
	v.VisitExamples(e)
}

type TableRow struct {
	Node
	Cells []*TableCell `json:"cells"`
}

type TableCell struct {
	Node
	Value string `json:"value"`
}

type ScenarioDefinition struct {
	Node
	Keyword     string  `json:"keyword"`
	Name        string  `json:"name"`
	Description string  `json:"description,omitempty"`
	Steps       []*Step `json:"steps"`
}

type Step struct {
	Node
	Keyword  string      `json:"keyword"`
	Text     string      `json:"text"`
	Argument interface{} `json:"argument,omitempty"`
}

func (s *Step) DescribeTo(v Visitor) {
	v.VisitStep(s)
}

type DocString struct {
	Node
	ContentType string `json:"contentType"`
	Content     string `json:"content"`
	Delimitter  string `json:"-"`
}

type DataTable struct {
	Node
	Rows []*TableRow `json:"rows"`
}

type HasTags interface {
	Tags() []*Tag
}

type HasSteps interface {
	Steps() []*Step
}

type DescribesItself interface {
	DescribeTo(Visitor)
}

type Visitor interface {
	VisitFeature(*Feature)
	VisitBackground(*Background)
	VisitScenario(*Scenario)
	VisitScenarioOutline(*ScenarioOutline)
	VisitExamples(*Examples)
	VisitStep(*Step)
}
<commit_msg>Remove unused visitor and interfaces from go impl<commit_after>package gherkin

type Location struct {
	Line   int `json:"line"`
	Column int `json:"column"`
}

type Node struct {
	Location *Location `json:"location,omitempty"`
	Type     string    `json:"type"`
}

type Feature struct {
	Node
	Tags                []*Tag        `json:"tags"`
	Language            string        `json:"language,omitempty"`
	Keyword             string        `json:"keyword"`
	Name                string        `json:"name"`
	Description         string        `json:"description,omitempty"`
	Background          *Background   `json:"background,omitempty"`
	ScenarioDefinitions []interface{} `json:"scenarioDefinitions"`
}

type Tag struct {
	Node
	Location *Location `json:"location,omitempty"`
	Name     string    `json:"name"`
}

type Background struct {
	ScenarioDefinition
}

type Scenario struct {
	ScenarioDefinition
	Tags []*Tag `json:"tags"`
}

type ScenarioOutline struct {
	ScenarioDefinition
	Tags     []*Tag      `json:"tags"`
	Examples []*Examples `json:"examples,omitempty"`
}

type Examples struct {
	Node
	Tags        []*Tag      `json:"tags"`
	Keyword     string      `json:"keyword"`
	Name        string      `json:"name"`
	Description string      `json:"description,omitempty"`
	Rows        []*TableRow `json:"rows"`
}

type TableRow struct {
	Node
	Cells []*TableCell `json:"cells"`
}

type TableCell struct {
	Node
	Value string `json:"value"`
}

type ScenarioDefinition struct {
	Node
	Keyword     string  `json:"keyword"`
	Name        string  `json:"name"`
	Description string  `json:"description,omitempty"`
	Steps       []*Step `json:"steps"`
}

type Step struct {
	Node
	Keyword  string      `json:"keyword"`
	Text     string      `json:"text"`
	Argument interface{} `json:"argument,omitempty"`
}

type DocString struct {
	Node
	ContentType string `json:"contentType"`
	Content     string `json:"content"`
	Delimitter  string `json:"-"`
}

type DataTable struct {
	Node
	Rows []*TableRow `json:"rows"`
}
<|endoftext|>


<commit_before>package radius

import (
	"bytes"
	"crypto"
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"reflect"
	"strconv"
)

type AVP struct {
	Type  AttributeType
	Value []byte
}

func (a AVP) Copy() AVP {
	value := make([]byte, len(a.Value))
	copy(value, a.Value)
	return AVP{
		Type:  a.Type,
		Value: a.Value,
	}
}
func (a AVP) Encode(b []byte) (n int, err error) {
	fullLen := len(a.Value) + 2 //type and length
	if fullLen > 255 || fullLen < 2 {
		return 0, errors.New("value too big for attribute")
	}
	b[0] = uint8(a.Type)
	b[1] = uint8(fullLen)
	copy(b[2:], a.Value)
	return fullLen, err
}

func (a AVP) Decode(p *Packet) interface{} {
	return getAttributeTypeDesc(a.Type).dataType.Value(p, a)
}

func (a AVP) String() string {
	return "AVP type: " + a.Type.String() + " " + getAttributeTypeDesc(a.Type).dataType.String(nil, a)
}

func (a AVP) StringWithPacket(p *Packet) string {
	return "AVP type: " + a.Type.String() + " " + getAttributeTypeDesc(a.Type).dataType.String(p, a)
}

type avpDataType interface {
	Value(p *Packet, a AVP) interface{}
	String(p *Packet, a AVP) string
}

var avpString avpStringt

type avpStringt struct{}

func (s avpStringt) Value(p *Packet, a AVP) interface{} {
	return string(a.Value)
}
func (s avpStringt) String(p *Packet, a AVP) string {
	return string(a.Value)
}

var avpIP avpIPt

type avpIPt struct{}

func (s avpIPt) Value(p *Packet, a AVP) interface{} {
	return net.IP(a.Value)
}
func (s avpIPt) String(p *Packet, a AVP) string {
	return net.IP(a.Value).String()
}

var avpUint32 avpUint32t

type avpUint32t struct{}

func (s avpUint32t) Value(p *Packet, a AVP) interface{} {
	return uint32(binary.BigEndian.Uint32(a.Value))
}
func (s avpUint32t) String(p *Packet, a AVP) string {
	return strconv.Itoa(int(binary.BigEndian.Uint32(a.Value)))
}

var avpBinary avpBinaryt

type avpBinaryt struct{}

func (s avpBinaryt) Value(p *Packet, a AVP) interface{} {
	return a.Value
}
func (s avpBinaryt) String(p *Packet, a AVP) string {
	return fmt.Sprintf("%#v", a.Value)
}

var avpPassword avpPasswordt

type avpPasswordt struct{}

func (s avpPasswordt) Value(p *Packet, a AVP) interface{} {
	if p == nil {
		return ""
	}

	buff := a.Value
	pass := make([]byte, 0)
	last := make([]byte, 16)
	copy(last, p.Authenticator[:])

	for len(buff) > 0 {
		m := crypto.Hash(crypto.MD5).New()
		m.Write(append([]byte(p.Secret), last...))
		h := m.Sum(nil)
		for i := 0; i < 16; i++ {
			pass = append(pass, buff[i]^h[i])
		}
		last = buff[:16]
		buff = buff[16:]
	}

	pass = bytes.TrimRight(pass, string([]rune{0}))
	return string(pass)
}
func (s avpPasswordt) String(p *Packet, a AVP) string {
	return s.Value(p, a).(string)
}

type avpUint32EnumList []string

func (s avpUint32EnumList) Value(p *Packet, a AVP) interface{} {
	return uint32(binary.BigEndian.Uint32(a.Value))
}
func (s avpUint32EnumList) String(p *Packet, a AVP) string {
	number := int(binary.BigEndian.Uint32(a.Value))
	if number > len(s) {
		return "unknow " + strconv.Itoa(number)
	}
	out := s[number]
	if out == "" {
		return "unknow " + strconv.Itoa(number)
	}
	return out
}

type avpUint32Enum struct {
	t interface{} // t should from a uint32 type like AcctStatusTypeEnum
}

func (s avpUint32Enum) Value(p *Packet, a AVP) interface{} {
	value := reflect.New(reflect.TypeOf(s.t)).Elem()
	value.SetUint(uint64(binary.BigEndian.Uint32(a.Value)))
	return value.Interface()
}
func (s avpUint32Enum) String(p *Packet, a AVP) string {
	number := binary.BigEndian.Uint32(a.Value)
	value := reflect.New(reflect.TypeOf(s.t)).Elem()
	value.SetUint(uint64(number))
	method := value.MethodByName("String")
	if !method.IsValid() {
		return strconv.Itoa(int(number))
	}
	out := method.Call(nil)
	return out[0].Interface().(string)
}

var avpEapMessage avpEapMessaget

type avpEapMessaget struct{}

func (s avpEapMessaget) Value(p *Packet, a AVP) interface{} {
	eap, err := EapDecode(a.Value)
	if err != nil {
		//TODO error handle
		fmt.Println("EapDecode fail ", err)
		return nil
	}
	return eap

}
func (s avpEapMessaget) String(p *Packet, a AVP) string {
	eap := s.Value(p, a)
	if eap == nil {
		return "nil"
	}
	return eap.(*EapPacket).String()
}

type AcctStatusTypeEnum uint32

const (
	AcctStatusTypeEnumStart         AcctStatusTypeEnum = 1
	AcctStatusTypeEnumStop          AcctStatusTypeEnum = 2
	AcctStatusTypeEnumInterimUpdate AcctStatusTypeEnum = 3
	AcctStatusTypeEnumAccountingOn  AcctStatusTypeEnum = 7
	AcctStatusTypeEnumAccountingOff AcctStatusTypeEnum = 8
)

func (e AcctStatusTypeEnum) String() string {
	switch e {
	case AcctStatusTypeEnumStart:
		return "Start"
	case AcctStatusTypeEnumStop:
		return "Stop"
	case AcctStatusTypeEnumInterimUpdate:
		return "InterimUpdate"
	case AcctStatusTypeEnumAccountingOn:
		return "AccountingOn"
	case AcctStatusTypeEnumAccountingOff:
		return "AccountingOff"
	}
	return "unknow code " + strconv.Itoa(int(e))
}

type NASPortTypeEnum uint32

// TODO finish it
const (
	NASPortTypeEnumAsync            NASPortTypeEnum = 0
	NASPortTypeEnumSync             NASPortTypeEnum = 1
	NASPortTypeEnumISDNSync         NASPortTypeEnum = 2
	NASPortTypeEnumISDNSyncV120     NASPortTypeEnum = 3
	NASPortTypeEnumISDNSyncV110     NASPortTypeEnum = 4
	NASPortTypeEnumVirtual          NASPortTypeEnum = 5
	NASPortTypeEnumPIAFS            NASPortTypeEnum = 6
	NASPortTypeEnumHDLCClearChannel NASPortTypeEnum = 7
	NASPortTypeEnumEthernet         NASPortTypeEnum = 15
	NASPortTypeEnumCable            NASPortTypeEnum = 17
)

func (e NASPortTypeEnum) String() string {
	switch e {
	case NASPortTypeEnumAsync:
		return "Async"
	case NASPortTypeEnumSync:
		return "Sync"
	case NASPortTypeEnumISDNSync:
		return "ISDNSync"
	case NASPortTypeEnumISDNSyncV120:
		return "ISDNSyncV120"
	case NASPortTypeEnumISDNSyncV110:
		return "ISDNSyncV110"
	case NASPortTypeEnumVirtual:
		return "Virtual"
	case NASPortTypeEnumPIAFS:
		return "PIAFS"
	case NASPortTypeEnumHDLCClearChannel:
		return "HDLCClearChannel"
	case NASPortTypeEnumEthernet:
		return "Ethernet"
	case NASPortTypeEnumCable:
		return "Cable"
	}
	return "unknow code " + strconv.Itoa(int(e))
}

type ServiceTypeEnum uint32

// TODO finish it
const (
	ServiceTypeEnumLogin          ServiceTypeEnum = 1
	ServiceTypeEnumFramed         ServiceTypeEnum = 2
	ServiceTypeEnumCallbackLogin  ServiceTypeEnum = 3
	ServiceTypeEnumCallbackFramed ServiceTypeEnum = 4
	ServiceTypeEnumOutbound       ServiceTypeEnum = 5
)

func (e ServiceTypeEnum) String() string {
	switch e {
	case ServiceTypeEnumLogin:
		return "Login"
	case ServiceTypeEnumFramed:
		return "Framed"
	case ServiceTypeEnumCallbackLogin:
		return "CallbackLogin"
	case ServiceTypeEnumCallbackFramed:
		return "CallbackFramed"
	case ServiceTypeEnumOutbound:
		return "Outbound"
	}
	return "unknow code " + strconv.Itoa(int(e))
}

type AcctTerminateCauseEnum uint32

const (
	AcctTerminateCauseEnumUserRequest        AcctTerminateCauseEnum = 1
	AcctTerminateCauseEnumLostCarrier        AcctTerminateCauseEnum = 2
	AcctTerminateCauseEnumLostService        AcctTerminateCauseEnum = 3
	AcctTerminateCauseEnumIdleTimeout        AcctTerminateCauseEnum = 4
	AcctTerminateCauseEnumSessionTimout      AcctTerminateCauseEnum = 5
	AcctTerminateCauseEnumAdminReset         AcctTerminateCauseEnum = 6
	AcctTerminateCauseEnumAdminReboot        AcctTerminateCauseEnum = 7
	AcctTerminateCauseEnumPortError          AcctTerminateCauseEnum = 8
	AcctTerminateCauseEnumNASError           AcctTerminateCauseEnum = 9
	AcctTerminateCauseEnumNASRequest         AcctTerminateCauseEnum = 10
	AcctTerminateCauseEnumNASReboot          AcctTerminateCauseEnum = 11
	AcctTerminateCauseEnumPortUnneeded       AcctTerminateCauseEnum = 12
	AcctTerminateCauseEnumPortPreempted      AcctTerminateCauseEnum = 13
	AcctTerminateCauseEnumPortSuspended      AcctTerminateCauseEnum = 14
	AcctTerminateCauseEnumServiceUnavailable AcctTerminateCauseEnum = 15
	AcctTerminateCauseEnumCallbkack          AcctTerminateCauseEnum = 16
	AcctTerminateCauseEnumUserError          AcctTerminateCauseEnum = 17
	AcctTerminateCauseEnumHostRequest        AcctStatusTypeEnum     = 18
)

func (e AcctTerminateCauseEnum) String() string {
	switch e {
	case AcctTerminateCauseEnumUserRequest:
		return "UserRequest"
	case AcctTerminateCauseEnumLostCarrier:
		return "LostCarrier"
	case AcctTerminateCauseEnumLostService:
		return "LostService"
	case AcctTerminateCauseEnumIdleTimeout:
		return "IdleTimeout"
	case AcctTerminateCauseEnumSessionTimout:
		return "SessionTimeout"
	case AcctTerminateCauseEnumAdminReset:
		return "AdminReset"
	case AcctTerminateCauseEnumAdminReboot:
		return "AdminReboot"
	case AcctTerminateCauseEnumPortError:
		return "PortError"
	case AcctTerminateCauseEnumNASError:
		return "NASError"
	case AcctTerminateCauseEnumNASRequest:
		return "NASRequest"
	case AcctTerminateCauseEnumNASReboot:
		return "NASReboot"
	case AcctTerminateCauseEnumPortUnneeded:
		return "PortUnneeded"
	case AcctTerminateCauseEnumPortPreempted:
		return "PortPreempted"
	case AcctTerminateCauseEnumPortSuspended:
		return "PortSuspended"
	case AcctTerminateCauseEnumServiceUnavailable:
		return "ServiceUnavailable"
	case AcctTerminateCauseEnumCallbkack:
		return "Callback"
	case AcctTerminateCauseEnumUserError:
		return "UserError"
	case AcctTerminateCauseEnumHostRequest:
		return "HostRequest"
	}
	return "unknow code " + strconv.Itoa(int(e))
}
<commit_msg>fix avp AcctTerminateCauseEnum<commit_after>package radius

import (
	"bytes"
	"crypto"
	"encoding/binary"
	"errors"
	"fmt"
	"net"
	"reflect"
	"strconv"
)

type AVP struct {
	Type  AttributeType
	Value []byte
}

func (a AVP) Copy() AVP {
	value := make([]byte, len(a.Value))
	copy(value, a.Value)
	return AVP{
		Type:  a.Type,
		Value: a.Value,
	}
}
func (a AVP) Encode(b []byte) (n int, err error) {
	fullLen := len(a.Value) + 2 //type and length
	if fullLen > 255 || fullLen < 2 {
		return 0, errors.New("value too big for attribute")
	}
	b[0] = uint8(a.Type)
	b[1] = uint8(fullLen)
	copy(b[2:], a.Value)
	return fullLen, err
}

func (a AVP) Decode(p *Packet) interface{} {
	return getAttributeTypeDesc(a.Type).dataType.Value(p, a)
}

func (a AVP) String() string {
	return "AVP type: " + a.Type.String() + " " + getAttributeTypeDesc(a.Type).dataType.String(nil, a)
}

func (a AVP) StringWithPacket(p *Packet) string {
	return "AVP type: " + a.Type.String() + " " + getAttributeTypeDesc(a.Type).dataType.String(p, a)
}

type avpDataType interface {
	Value(p *Packet, a AVP) interface{}
	String(p *Packet, a AVP) string
}

var avpString avpStringt

type avpStringt struct{}

func (s avpStringt) Value(p *Packet, a AVP) interface{} {
	return string(a.Value)
}
func (s avpStringt) String(p *Packet, a AVP) string {
	return string(a.Value)
}

var avpIP avpIPt

type avpIPt struct{}

func (s avpIPt) Value(p *Packet, a AVP) interface{} {
	return net.IP(a.Value)
}
func (s avpIPt) String(p *Packet, a AVP) string {
	return net.IP(a.Value).String()
}

var avpUint32 avpUint32t

type avpUint32t struct{}

func (s avpUint32t) Value(p *Packet, a AVP) interface{} {
	return uint32(binary.BigEndian.Uint32(a.Value))
}
func (s avpUint32t) String(p *Packet, a AVP) string {
	return strconv.Itoa(int(binary.BigEndian.Uint32(a.Value)))
}

var avpBinary avpBinaryt

type avpBinaryt struct{}

func (s avpBinaryt) Value(p *Packet, a AVP) interface{} {
	return a.Value
}
func (s avpBinaryt) String(p *Packet, a AVP) string {
	return fmt.Sprintf("%#v", a.Value)
}

var avpPassword avpPasswordt

type avpPasswordt struct{}

func (s avpPasswordt) Value(p *Packet, a AVP) interface{} {
	if p == nil {
		return ""
	}

	buff := a.Value
	pass := make([]byte, 0)
	last := make([]byte, 16)
	copy(last, p.Authenticator[:])

	for len(buff) > 0 {
		m := crypto.Hash(crypto.MD5).New()
		m.Write(append([]byte(p.Secret), last...))
		h := m.Sum(nil)
		for i := 0; i < 16; i++ {
			pass = append(pass, buff[i]^h[i])
		}
		last = buff[:16]
		buff = buff[16:]
	}

	pass = bytes.TrimRight(pass, string([]rune{0}))
	return string(pass)
}
func (s avpPasswordt) String(p *Packet, a AVP) string {
	return s.Value(p, a).(string)
}

type avpUint32EnumList []string

func (s avpUint32EnumList) Value(p *Packet, a AVP) interface{} {
	return uint32(binary.BigEndian.Uint32(a.Value))
}
func (s avpUint32EnumList) String(p *Packet, a AVP) string {
	number := int(binary.BigEndian.Uint32(a.Value))
	if number > len(s) {
		return "unknow " + strconv.Itoa(number)
	}
	out := s[number]
	if out == "" {
		return "unknow " + strconv.Itoa(number)
	}
	return out
}

type avpUint32Enum struct {
	t interface{} // t should from a uint32 type like AcctStatusTypeEnum
}

func (s avpUint32Enum) Value(p *Packet, a AVP) interface{} {
	value := reflect.New(reflect.TypeOf(s.t)).Elem()
	value.SetUint(uint64(binary.BigEndian.Uint32(a.Value)))
	return value.Interface()
}
func (s avpUint32Enum) String(p *Packet, a AVP) string {
	number := binary.BigEndian.Uint32(a.Value)
	value := reflect.New(reflect.TypeOf(s.t)).Elem()
	value.SetUint(uint64(number))
	method := value.MethodByName("String")
	if !method.IsValid() {
		return strconv.Itoa(int(number))
	}
	out := method.Call(nil)
	return out[0].Interface().(string)
}

var avpEapMessage avpEapMessaget

type avpEapMessaget struct{}

func (s avpEapMessaget) Value(p *Packet, a AVP) interface{} {
	eap, err := EapDecode(a.Value)
	if err != nil {
		//TODO error handle
		fmt.Println("EapDecode fail ", err)
		return nil
	}
	return eap

}
func (s avpEapMessaget) String(p *Packet, a AVP) string {
	eap := s.Value(p, a)
	if eap == nil {
		return "nil"
	}
	return eap.(*EapPacket).String()
}

type AcctStatusTypeEnum uint32

const (
	AcctStatusTypeEnumStart         AcctStatusTypeEnum = 1
	AcctStatusTypeEnumStop          AcctStatusTypeEnum = 2
	AcctStatusTypeEnumInterimUpdate AcctStatusTypeEnum = 3
	AcctStatusTypeEnumAccountingOn  AcctStatusTypeEnum = 7
	AcctStatusTypeEnumAccountingOff AcctStatusTypeEnum = 8
)

func (e AcctStatusTypeEnum) String() string {
	switch e {
	case AcctStatusTypeEnumStart:
		return "Start"
	case AcctStatusTypeEnumStop:
		return "Stop"
	case AcctStatusTypeEnumInterimUpdate:
		return "InterimUpdate"
	case AcctStatusTypeEnumAccountingOn:
		return "AccountingOn"
	case AcctStatusTypeEnumAccountingOff:
		return "AccountingOff"
	}
	return "unknow code " + strconv.Itoa(int(e))
}

type NASPortTypeEnum uint32

// TODO finish it
const (
	NASPortTypeEnumAsync            NASPortTypeEnum = 0
	NASPortTypeEnumSync             NASPortTypeEnum = 1
	NASPortTypeEnumISDNSync         NASPortTypeEnum = 2
	NASPortTypeEnumISDNSyncV120     NASPortTypeEnum = 3
	NASPortTypeEnumISDNSyncV110     NASPortTypeEnum = 4
	NASPortTypeEnumVirtual          NASPortTypeEnum = 5
	NASPortTypeEnumPIAFS            NASPortTypeEnum = 6
	NASPortTypeEnumHDLCClearChannel NASPortTypeEnum = 7
	NASPortTypeEnumEthernet         NASPortTypeEnum = 15
	NASPortTypeEnumCable            NASPortTypeEnum = 17
)

func (e NASPortTypeEnum) String() string {
	switch e {
	case NASPortTypeEnumAsync:
		return "Async"
	case NASPortTypeEnumSync:
		return "Sync"
	case NASPortTypeEnumISDNSync:
		return "ISDNSync"
	case NASPortTypeEnumISDNSyncV120:
		return "ISDNSyncV120"
	case NASPortTypeEnumISDNSyncV110:
		return "ISDNSyncV110"
	case NASPortTypeEnumVirtual:
		return "Virtual"
	case NASPortTypeEnumPIAFS:
		return "PIAFS"
	case NASPortTypeEnumHDLCClearChannel:
		return "HDLCClearChannel"
	case NASPortTypeEnumEthernet:
		return "Ethernet"
	case NASPortTypeEnumCable:
		return "Cable"
	}
	return "unknow code " + strconv.Itoa(int(e))
}

type ServiceTypeEnum uint32

// TODO finish it
const (
	ServiceTypeEnumLogin          ServiceTypeEnum = 1
	ServiceTypeEnumFramed         ServiceTypeEnum = 2
	ServiceTypeEnumCallbackLogin  ServiceTypeEnum = 3
	ServiceTypeEnumCallbackFramed ServiceTypeEnum = 4
	ServiceTypeEnumOutbound       ServiceTypeEnum = 5
)

func (e ServiceTypeEnum) String() string {
	switch e {
	case ServiceTypeEnumLogin:
		return "Login"
	case ServiceTypeEnumFramed:
		return "Framed"
	case ServiceTypeEnumCallbackLogin:
		return "CallbackLogin"
	case ServiceTypeEnumCallbackFramed:
		return "CallbackFramed"
	case ServiceTypeEnumOutbound:
		return "Outbound"
	}
	return "unknow code " + strconv.Itoa(int(e))
}

type AcctTerminateCauseEnum uint32

const (
	AcctTerminateCauseEnumUserRequest        AcctTerminateCauseEnum = 1
	AcctTerminateCauseEnumLostCarrier        AcctTerminateCauseEnum = 2
	AcctTerminateCauseEnumLostService        AcctTerminateCauseEnum = 3
	AcctTerminateCauseEnumIdleTimeout        AcctTerminateCauseEnum = 4
	AcctTerminateCauseEnumSessionTimout      AcctTerminateCauseEnum = 5
	AcctTerminateCauseEnumAdminReset         AcctTerminateCauseEnum = 6
	AcctTerminateCauseEnumAdminReboot        AcctTerminateCauseEnum = 7
	AcctTerminateCauseEnumPortError          AcctTerminateCauseEnum = 8
	AcctTerminateCauseEnumNASError           AcctTerminateCauseEnum = 9
	AcctTerminateCauseEnumNASRequest         AcctTerminateCauseEnum = 10
	AcctTerminateCauseEnumNASReboot          AcctTerminateCauseEnum = 11
	AcctTerminateCauseEnumPortUnneeded       AcctTerminateCauseEnum = 12
	AcctTerminateCauseEnumPortPreempted      AcctTerminateCauseEnum = 13
	AcctTerminateCauseEnumPortSuspended      AcctTerminateCauseEnum = 14
	AcctTerminateCauseEnumServiceUnavailable AcctTerminateCauseEnum = 15
	AcctTerminateCauseEnumCallbkack          AcctTerminateCauseEnum = 16
	AcctTerminateCauseEnumUserError          AcctTerminateCauseEnum = 17
	AcctTerminateCauseEnumHostRequest        AcctTerminateCauseEnum = 18
)

func (e AcctTerminateCauseEnum) String() string {
	switch e {
	case AcctTerminateCauseEnumUserRequest:
		return "UserRequest"
	case AcctTerminateCauseEnumLostCarrier:
		return "LostCarrier"
	case AcctTerminateCauseEnumLostService:
		return "LostService"
	case AcctTerminateCauseEnumIdleTimeout:
		return "IdleTimeout"
	case AcctTerminateCauseEnumSessionTimout:
		return "SessionTimeout"
	case AcctTerminateCauseEnumAdminReset:
		return "AdminReset"
	case AcctTerminateCauseEnumAdminReboot:
		return "AdminReboot"
	case AcctTerminateCauseEnumPortError:
		return "PortError"
	case AcctTerminateCauseEnumNASError:
		return "NASError"
	case AcctTerminateCauseEnumNASRequest:
		return "NASRequest"
	case AcctTerminateCauseEnumNASReboot:
		return "NASReboot"
	case AcctTerminateCauseEnumPortUnneeded:
		return "PortUnneeded"
	case AcctTerminateCauseEnumPortPreempted:
		return "PortPreempted"
	case AcctTerminateCauseEnumPortSuspended:
		return "PortSuspended"
	case AcctTerminateCauseEnumServiceUnavailable:
		return "ServiceUnavailable"
	case AcctTerminateCauseEnumCallbkack:
		return "Callback"
	case AcctTerminateCauseEnumUserError:
		return "UserError"
	case AcctTerminateCauseEnumHostRequest:
		return "HostRequest"
	}
	return "unknow code " + strconv.Itoa(int(e))
}
<|endoftext|>


<commit_before>package mpb

import (
	"bytes"
	"fmt"
	"io"
	"sync"
	"time"
	"unicode/utf8"

	"github.com/vbauerster/mpb/decor"
)

const (
	rLeft = iota
	rFill
	rTip
	rEmpty
	rRight
)

const (
	formatLen = 5
	etaAlpha  = 0.25
)

type fmtRunes [formatLen]rune

// Bar represents a progress Bar
type Bar struct {
	// quit channel to request b.server to quit
	quit chan struct{}
	// done channel is receiveable after b.server has been quit
	done chan struct{}
	ops  chan func(*state)

	// following are used after b.done is receiveable
	cacheState state

	once sync.Once
}

type (
	refill struct {
		char rune
		till int64
	}
	state struct {
		id               int
		width            int
		format           fmtRunes
		etaAlpha         float64
		total            int64
		current          int64
		dropRatio        int64
		trimLeftSpace    bool
		trimRightSpace   bool
		completed        bool
		aborted          bool
		dynamic          bool
		startTime        time.Time
		timeElapsed      time.Duration
		blockStartTime   time.Time
		timePerItem      time.Duration
		appendFuncs      []decor.DecoratorFunc
		prependFuncs     []decor.DecoratorFunc
		refill           *refill
		bufP, bufB, bufA *bytes.Buffer
		panic            string
	}
	writeBuf struct {
		buf                []byte
		completeAfterFlush bool
	}
)

func newBar(ID int, total int64, wg *sync.WaitGroup, cancel <-chan struct{}, options ...BarOption) *Bar {
	if total <= 0 {
		total = time.Now().Unix()
	}

	s := state{
		id:        ID,
		total:     total,
		etaAlpha:  etaAlpha,
		dropRatio: 10,
	}

	for _, opt := range options {
		opt(&s)
	}

	s.bufP = bytes.NewBuffer(make([]byte, 0, s.width/2))
	s.bufB = bytes.NewBuffer(make([]byte, 0, s.width))
	s.bufA = bytes.NewBuffer(make([]byte, 0, s.width/2))

	b := &Bar{
		quit: make(chan struct{}),
		done: make(chan struct{}),
		ops:  make(chan func(*state)),
	}

	go b.server(s, wg, cancel)
	return b
}

// RemoveAllPrependers removes all prepend functions
func (b *Bar) RemoveAllPrependers() {
	select {
	case b.ops <- func(s *state) {
		s.prependFuncs = nil
	}:
	case <-b.quit:
	}
}

// RemoveAllAppenders removes all append functions
func (b *Bar) RemoveAllAppenders() {
	select {
	case b.ops <- func(s *state) {
		s.appendFuncs = nil
	}:
	case <-b.quit:
	}
}

// ProxyReader wrapper for io operations, like io.Copy
func (b *Bar) ProxyReader(r io.Reader) *Reader {
	return &Reader{r, b}
}

// Increment shorthand for b.Incr(1)
func (b *Bar) Increment() {
	b.Incr(1)
}

// Incr increments progress bar
func (b *Bar) Incr(n int) {
	if n < 1 {
		return
	}
	select {
	case b.ops <- func(s *state) {
		next := time.Now()
		if s.current == 0 {
			s.startTime = next
			s.blockStartTime = next
		} else {
			now := time.Now()
			s.updateTimePerItemEstimate(n, now, next)
			s.timeElapsed = now.Sub(s.startTime)
		}
		sum := s.current + int64(n)
		if s.total > 0 && sum >= s.total {
			if s.dynamic {
				sum -= sum * s.dropRatio / 100
			} else {
				s.current = s.total
				s.completed = true
			}
			return
		}
		s.current = sum
	}:
	case <-b.quit:
	}
}

// ResumeFill fills bar with different r rune,
// from 0 to till amount of progress.
func (b *Bar) ResumeFill(r rune, till int64) {
	if till < 1 {
		return
	}
	select {
	case b.ops <- func(s *state) {
		s.refill = &refill{r, till}
	}:
	case <-b.quit:
	}
}

func (b *Bar) NumOfAppenders() int {
	result := make(chan int, 1)
	select {
	case b.ops <- func(s *state) { result <- len(s.appendFuncs) }:
		return <-result
	case <-b.done:
		return len(b.cacheState.appendFuncs)
	}
}

func (b *Bar) NumOfPrependers() int {
	result := make(chan int, 1)
	select {
	case b.ops <- func(s *state) { result <- len(s.prependFuncs) }:
		return <-result
	case <-b.done:
		return len(b.cacheState.prependFuncs)
	}
}

// ID returs id of the bar
func (b *Bar) ID() int {
	result := make(chan int, 1)
	select {
	case b.ops <- func(s *state) { result <- s.id }:
		return <-result
	case <-b.done:
		return b.cacheState.id
	}
}

func (b *Bar) Current() int64 {
	result := make(chan int64, 1)
	select {
	case b.ops <- func(s *state) { result <- s.current }:
		return <-result
	case <-b.done:
		return b.cacheState.current
	}
}

func (b *Bar) Total() int64 {
	result := make(chan int64, 1)
	select {
	case b.ops <- func(s *state) { result <- s.total }:
		return <-result
	case <-b.done:
		return b.cacheState.total
	}
}

// SetTotal sets total dynamically. The final param indicates the very last set,
// in other words you should set it to true when total is determined.
// Also you may consider providing your drop ratio via BarDropRatio BarOption func.
func (b *Bar) SetTotal(total int64, final bool) {
	select {
	case b.ops <- func(s *state) {
		s.total = total
		s.dynamic = !final
	}:
	case <-b.quit:
	}
}

// InProgress returns true, while progress is running.
// Can be used as condition in for loop
func (b *Bar) InProgress() bool {
	select {
	case <-b.quit:
		return false
	default:
		return true
	}
}

// Complete signals to the bar, that process has been completed.
// You should call this method when total is unknown and you've reached the point
// of process completion. If you don't call this method, it will be called
// implicitly, upon p.Stop() call.
func (b *Bar) Complete() {
	b.once.Do(b.shutdown)
}

func (b *Bar) shutdown() {
	close(b.quit)
}

func (b *Bar) server(s state, wg *sync.WaitGroup, cancel <-chan struct{}) {
	defer func() {
		b.cacheState = s
		close(b.done)
		wg.Done()
	}()

	for {
		select {
		case op := <-b.ops:
			op(&s)
		case <-cancel:
			s.aborted = true
			cancel = nil
			b.Complete()
		case <-b.quit:
			return
		}
	}
}

func (b *Bar) render(tw int, prependWs, appendWs *widthSync) <-chan *writeBuf {
	ch := make(chan *writeBuf, 1)

	go func() {
		select {
		case b.ops <- func(s *state) {
			defer func() {
				// recovering if external decorators panic
				if p := recover(); p != nil {
					s.panic = fmt.Sprintf("b#%02d panic: %v\n", s.id, p)
					s.prependFuncs = nil
					s.appendFuncs = nil

					ch <- &writeBuf{[]byte(s.panic), true}
				}
				close(ch)
			}()
			s.draw(tw, prependWs, appendWs)
			ch <- &writeBuf{s.toBytes(), s.isFull()}
		}:
		case <-b.done:
			s := b.cacheState
			var buf []byte
			if s.panic != "" {
				buf = []byte(s.panic)
			} else {
				s.draw(tw, prependWs, appendWs)
				buf = s.toBytes()
			}
			ch <- &writeBuf{buf, false}
			close(ch)
		}
	}()

	return ch
}

func (s *state) toBytes() []byte {
	buf := make([]byte, 0, s.bufP.Len()+s.bufB.Len()+s.bufA.Len())
	buf = concatenateBlocks(buf, s.bufP.Bytes(), s.bufB.Bytes(), s.bufA.Bytes())
	return buf
}

func (s *state) updateTimePerItemEstimate(amount int, now, next time.Time) {
	lastBlockTime := now.Sub(s.blockStartTime)
	lastItemEstimate := float64(lastBlockTime) / float64(amount)
	s.timePerItem = time.Duration((s.etaAlpha * lastItemEstimate) + (1-s.etaAlpha)*float64(s.timePerItem))
	s.blockStartTime = next
}

func (s *state) isFull() bool {
	if !s.completed {
		return false
	}
	bar := s.bufB.Bytes()
	var r rune
	var n int
	for i := 0; len(bar) > 0; i++ {
		r, n = utf8.DecodeLastRune(bar)
		bar = bar[:len(bar)-n]
		if i == 1 {
			break
		}
	}
	return r == s.format[rFill]
}

func (s *state) draw(termWidth int, prependWs, appendWs *widthSync) {
	if termWidth <= 0 {
		termWidth = s.width
	}

	stat := newStatistics(s)

	// render prepend functions to the left of the bar
	s.bufP.Reset()
	for i, f := range s.prependFuncs {
		s.bufP.WriteString(f(stat, prependWs.Listen[i], prependWs.Result[i]))
	}

	if !s.trimLeftSpace {
		s.bufP.WriteByte(' ')
	}

	// render append functions to the right of the bar
	s.bufA.Reset()
	if !s.trimRightSpace {
		s.bufA.WriteByte(' ')
	}

	for i, f := range s.appendFuncs {
		s.bufA.WriteString(f(stat, appendWs.Listen[i], appendWs.Result[i]))
	}

	prependCount := utf8.RuneCount(s.bufP.Bytes())
	appendCount := utf8.RuneCount(s.bufA.Bytes())

	s.fillBar(s.width)
	barCount := utf8.RuneCount(s.bufB.Bytes())
	totalCount := prependCount + barCount + appendCount
	if totalCount > termWidth {
		shrinkWidth := termWidth - prependCount - appendCount
		s.fillBar(shrinkWidth)
	}
	s.bufA.WriteByte('\n')
}

func (s *state) fillBar(width int) {
	s.bufB.Reset()
	if width <= 2 {
		return
	}

	// bar s.width without leftEnd and rightEnd runes
	barWidth := width - 2

	completedWidth := decor.CalcPercentage(s.total, s.current, barWidth)

	s.bufB.WriteRune(s.format[rLeft])

	if s.refill != nil {
		till := decor.CalcPercentage(s.total, s.refill.till, barWidth)
		// append refill rune
		for i := 0; i < till; i++ {
			s.bufB.WriteRune(s.refill.char)
		}
		for i := till; i < completedWidth; i++ {
			s.bufB.WriteRune(s.format[rFill])
		}
	} else {
		for i := 0; i < completedWidth; i++ {
			s.bufB.WriteRune(s.format[rFill])
		}
	}

	if completedWidth < barWidth && completedWidth > 0 {
		_, size := utf8.DecodeLastRune(s.bufB.Bytes())
		s.bufB.Truncate(s.bufB.Len() - size)
		s.bufB.WriteRune(s.format[rTip])
	}

	for i := completedWidth; i < barWidth; i++ {
		s.bufB.WriteRune(s.format[rEmpty])
	}

	s.bufB.WriteRune(s.format[rRight])
}

func newStatistics(s *state) *decor.Statistics {
	return &decor.Statistics{
		ID:                  s.id,
		Completed:           s.completed,
		Aborted:             s.aborted,
		Total:               s.total,
		Current:             s.current,
		StartTime:           s.startTime,
		TimeElapsed:         s.timeElapsed,
		TimePerItemEstimate: s.timePerItem,
	}
}

func concatenateBlocks(buf []byte, blocks ...[]byte) []byte {
	for _, block := range blocks {
		buf = append(buf, block...)
	}
	return buf
}

func (s *state) updateFormat(format string) {
	for i, n := 0, 0; len(format) > 0; i++ {
		s.format[i], n = utf8.DecodeRuneInString(format)
		format = format[n:]
	}
}
<commit_msg>Drop progress while total is dynamic<commit_after>package mpb

import (
	"bytes"
	"fmt"
	"io"
	"sync"
	"time"
	"unicode/utf8"

	"github.com/vbauerster/mpb/decor"
)

const (
	rLeft = iota
	rFill
	rTip
	rEmpty
	rRight
)

const (
	formatLen = 5
	etaAlpha  = 0.25
)

type fmtRunes [formatLen]rune

// Bar represents a progress Bar
type Bar struct {
	// quit channel to request b.server to quit
	quit chan struct{}
	// done channel is receiveable after b.server has been quit
	done chan struct{}
	ops  chan func(*state)

	// following are used after b.done is receiveable
	cacheState state

	once sync.Once
}

type (
	refill struct {
		char rune
		till int64
	}
	state struct {
		id               int
		width            int
		format           fmtRunes
		etaAlpha         float64
		total            int64
		current          int64
		dropRatio        int64
		trimLeftSpace    bool
		trimRightSpace   bool
		completed        bool
		aborted          bool
		dynamic          bool
		startTime        time.Time
		timeElapsed      time.Duration
		blockStartTime   time.Time
		timePerItem      time.Duration
		appendFuncs      []decor.DecoratorFunc
		prependFuncs     []decor.DecoratorFunc
		refill           *refill
		bufP, bufB, bufA *bytes.Buffer
		panic            string
	}
	writeBuf struct {
		buf                []byte
		completeAfterFlush bool
	}
)

func newBar(ID int, total int64, wg *sync.WaitGroup, cancel <-chan struct{}, options ...BarOption) *Bar {
	if total <= 0 {
		total = time.Now().Unix()
	}

	s := state{
		id:        ID,
		total:     total,
		etaAlpha:  etaAlpha,
		dropRatio: 10,
	}

	for _, opt := range options {
		opt(&s)
	}

	s.bufP = bytes.NewBuffer(make([]byte, 0, s.width/2))
	s.bufB = bytes.NewBuffer(make([]byte, 0, s.width))
	s.bufA = bytes.NewBuffer(make([]byte, 0, s.width/2))

	b := &Bar{
		quit: make(chan struct{}),
		done: make(chan struct{}),
		ops:  make(chan func(*state)),
	}

	go b.server(s, wg, cancel)
	return b
}

// RemoveAllPrependers removes all prepend functions
func (b *Bar) RemoveAllPrependers() {
	select {
	case b.ops <- func(s *state) {
		s.prependFuncs = nil
	}:
	case <-b.quit:
	}
}

// RemoveAllAppenders removes all append functions
func (b *Bar) RemoveAllAppenders() {
	select {
	case b.ops <- func(s *state) {
		s.appendFuncs = nil
	}:
	case <-b.quit:
	}
}

// ProxyReader wrapper for io operations, like io.Copy
func (b *Bar) ProxyReader(r io.Reader) *Reader {
	return &Reader{r, b}
}

// Increment shorthand for b.Incr(1)
func (b *Bar) Increment() {
	b.Incr(1)
}

// Incr increments progress bar
func (b *Bar) Incr(n int) {
	if n < 1 {
		return
	}
	select {
	case b.ops <- func(s *state) {
		next := time.Now()
		if s.current == 0 {
			s.startTime = next
			s.blockStartTime = next
		} else {
			now := time.Now()
			s.updateTimePerItemEstimate(n, now, next)
			s.timeElapsed = now.Sub(s.startTime)
		}
		s.current += int64(n)
		if s.dynamic {
			for s.current >= s.total {
				s.current -= s.current * s.dropRatio / 100
			}
		} else if s.current >= s.total {
			s.current = s.total
			s.completed = true
		}
	}:
	case <-b.quit:
	}
}

// ResumeFill fills bar with different r rune,
// from 0 to till amount of progress.
func (b *Bar) ResumeFill(r rune, till int64) {
	if till < 1 {
		return
	}
	select {
	case b.ops <- func(s *state) {
		s.refill = &refill{r, till}
	}:
	case <-b.quit:
	}
}

func (b *Bar) NumOfAppenders() int {
	result := make(chan int, 1)
	select {
	case b.ops <- func(s *state) { result <- len(s.appendFuncs) }:
		return <-result
	case <-b.done:
		return len(b.cacheState.appendFuncs)
	}
}

func (b *Bar) NumOfPrependers() int {
	result := make(chan int, 1)
	select {
	case b.ops <- func(s *state) { result <- len(s.prependFuncs) }:
		return <-result
	case <-b.done:
		return len(b.cacheState.prependFuncs)
	}
}

// ID returs id of the bar
func (b *Bar) ID() int {
	result := make(chan int, 1)
	select {
	case b.ops <- func(s *state) { result <- s.id }:
		return <-result
	case <-b.done:
		return b.cacheState.id
	}
}

func (b *Bar) Current() int64 {
	result := make(chan int64, 1)
	select {
	case b.ops <- func(s *state) { result <- s.current }:
		return <-result
	case <-b.done:
		return b.cacheState.current
	}
}

func (b *Bar) Total() int64 {
	result := make(chan int64, 1)
	select {
	case b.ops <- func(s *state) { result <- s.total }:
		return <-result
	case <-b.done:
		return b.cacheState.total
	}
}

// SetTotal sets total dynamically. The final param indicates the very last set,
// in other words you should set it to true when total is determined.
// Also you may consider providing your drop ratio via BarDropRatio BarOption func.
func (b *Bar) SetTotal(total int64, final bool) {
	select {
	case b.ops <- func(s *state) {
		s.total = total
		s.dynamic = !final
	}:
	case <-b.quit:
	}
}

// InProgress returns true, while progress is running.
// Can be used as condition in for loop
func (b *Bar) InProgress() bool {
	select {
	case <-b.quit:
		return false
	default:
		return true
	}
}

// Complete signals to the bar, that process has been completed.
// You should call this method when total is unknown and you've reached the point
// of process completion. If you don't call this method, it will be called
// implicitly, upon p.Stop() call.
func (b *Bar) Complete() {
	b.once.Do(b.shutdown)
}

func (b *Bar) shutdown() {
	close(b.quit)
}

func (b *Bar) server(s state, wg *sync.WaitGroup, cancel <-chan struct{}) {
	defer func() {
		b.cacheState = s
		close(b.done)
		wg.Done()
	}()

	for {
		select {
		case op := <-b.ops:
			op(&s)
		case <-cancel:
			s.aborted = true
			cancel = nil
			b.Complete()
		case <-b.quit:
			return
		}
	}
}

func (b *Bar) render(tw int, prependWs, appendWs *widthSync) <-chan *writeBuf {
	ch := make(chan *writeBuf, 1)

	go func() {
		select {
		case b.ops <- func(s *state) {
			defer func() {
				// recovering if external decorators panic
				if p := recover(); p != nil {
					s.panic = fmt.Sprintf("b#%02d panic: %v\n", s.id, p)
					s.prependFuncs = nil
					s.appendFuncs = nil

					ch <- &writeBuf{[]byte(s.panic), true}
				}
				close(ch)
			}()
			s.draw(tw, prependWs, appendWs)
			ch <- &writeBuf{s.toBytes(), s.isFull()}
		}:
		case <-b.done:
			s := b.cacheState
			var buf []byte
			if s.panic != "" {
				buf = []byte(s.panic)
			} else {
				s.draw(tw, prependWs, appendWs)
				buf = s.toBytes()
			}
			ch <- &writeBuf{buf, false}
			close(ch)
		}
	}()

	return ch
}

func (s *state) toBytes() []byte {
	buf := make([]byte, 0, s.bufP.Len()+s.bufB.Len()+s.bufA.Len())
	buf = concatenateBlocks(buf, s.bufP.Bytes(), s.bufB.Bytes(), s.bufA.Bytes())
	return buf
}

func (s *state) updateTimePerItemEstimate(amount int, now, next time.Time) {
	lastBlockTime := now.Sub(s.blockStartTime)
	lastItemEstimate := float64(lastBlockTime) / float64(amount)
	s.timePerItem = time.Duration((s.etaAlpha * lastItemEstimate) + (1-s.etaAlpha)*float64(s.timePerItem))
	s.blockStartTime = next
}

func (s *state) isFull() bool {
	if !s.completed {
		return false
	}
	bar := s.bufB.Bytes()
	var r rune
	var n int
	for i := 0; len(bar) > 0; i++ {
		r, n = utf8.DecodeLastRune(bar)
		bar = bar[:len(bar)-n]
		if i == 1 {
			break
		}
	}
	return r == s.format[rFill]
}

func (s *state) draw(termWidth int, prependWs, appendWs *widthSync) {
	if termWidth <= 0 {
		termWidth = s.width
	}

	stat := newStatistics(s)

	// render prepend functions to the left of the bar
	s.bufP.Reset()
	for i, f := range s.prependFuncs {
		s.bufP.WriteString(f(stat, prependWs.Listen[i], prependWs.Result[i]))
	}

	if !s.trimLeftSpace {
		s.bufP.WriteByte(' ')
	}

	// render append functions to the right of the bar
	s.bufA.Reset()
	if !s.trimRightSpace {
		s.bufA.WriteByte(' ')
	}

	for i, f := range s.appendFuncs {
		s.bufA.WriteString(f(stat, appendWs.Listen[i], appendWs.Result[i]))
	}

	prependCount := utf8.RuneCount(s.bufP.Bytes())
	appendCount := utf8.RuneCount(s.bufA.Bytes())

	s.fillBar(s.width)
	barCount := utf8.RuneCount(s.bufB.Bytes())
	totalCount := prependCount + barCount + appendCount
	if totalCount > termWidth {
		shrinkWidth := termWidth - prependCount - appendCount
		s.fillBar(shrinkWidth)
	}
	s.bufA.WriteByte('\n')
}

func (s *state) fillBar(width int) {
	s.bufB.Reset()
	if width <= 2 {
		return
	}

	// bar s.width without leftEnd and rightEnd runes
	barWidth := width - 2

	completedWidth := decor.CalcPercentage(s.total, s.current, barWidth)

	s.bufB.WriteRune(s.format[rLeft])

	if s.refill != nil {
		till := decor.CalcPercentage(s.total, s.refill.till, barWidth)
		// append refill rune
		for i := 0; i < till; i++ {
			s.bufB.WriteRune(s.refill.char)
		}
		for i := till; i < completedWidth; i++ {
			s.bufB.WriteRune(s.format[rFill])
		}
	} else {
		for i := 0; i < completedWidth; i++ {
			s.bufB.WriteRune(s.format[rFill])
		}
	}

	if completedWidth < barWidth && completedWidth > 0 {
		_, size := utf8.DecodeLastRune(s.bufB.Bytes())
		s.bufB.Truncate(s.bufB.Len() - size)
		s.bufB.WriteRune(s.format[rTip])
	}

	for i := completedWidth; i < barWidth; i++ {
		s.bufB.WriteRune(s.format[rEmpty])
	}

	s.bufB.WriteRune(s.format[rRight])
}

func newStatistics(s *state) *decor.Statistics {
	return &decor.Statistics{
		ID:                  s.id,
		Completed:           s.completed,
		Aborted:             s.aborted,
		Total:               s.total,
		Current:             s.current,
		StartTime:           s.startTime,
		TimeElapsed:         s.timeElapsed,
		TimePerItemEstimate: s.timePerItem,
	}
}

func concatenateBlocks(buf []byte, blocks ...[]byte) []byte {
	for _, block := range blocks {
		buf = append(buf, block...)
	}
	return buf
}

func (s *state) updateFormat(format string) {
	for i, n := 0, 0; len(format) > 0; i++ {
		s.format[i], n = utf8.DecodeRuneInString(format)
		format = format[n:]
	}
}
<|endoftext|>


<commit_before>// Tabix queries for go
package bix

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"unsafe"

	"github.com/biogo/hts/bgzf"
	"github.com/biogo/hts/bgzf/index"
	"github.com/biogo/hts/tabix"
	"github.com/brentp/irelate/interfaces"
	"github.com/brentp/irelate/parsers"
	"github.com/brentp/vcfgo"
)

// Bix provides read access to tabix files.
type Bix struct {
	*tabix.Index
	bgzf *bgzf.Reader
	path string

	VReader *vcfgo.Reader
	// index for 'ref' and 'alt' columns if they were present.
	refalt []int

	file *os.File
	buf  *bufio.Reader
}

// create a new bix that does as little as possible from the old bix
func newShort(old *Bix) (*Bix, error) {
	tbx := &Bix{
		Index:   old.Index,
		path:    old.path,
		VReader: old.VReader,
		refalt:  old.refalt,
	}
	var err error
	tbx.file, err = os.Open(tbx.path)
	if err != nil {
		return nil, err
	}
	tbx.bgzf, err = bgzf.NewReader(tbx.file, 1)
	if err != nil {
		return nil, err
	}
	return tbx, nil
}

// New returns a &Bix
func New(path string, workers ...int) (*Bix, error) {
	f, err := os.Open(path + ".tbi")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	gz, err := gzip.NewReader(f)
	if err != nil {
		return nil, err
	}
	defer gz.Close()

	idx, err := tabix.ReadFrom(gz)
	if err != nil {
		return nil, err
	}
	n := 1
	if len(workers) > 0 {
		n = workers[0]
	}

	b, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	bgz, err := bgzf.NewReader(b, n)
	if err != nil {
		return nil, err
	}

	var h []string
	tbx := &Bix{bgzf: bgz, path: path, file: b}

	buf := bufio.NewReader(bgz)
	l, err := buf.ReadString('\n')
	if err != nil {
		return tbx, err
	}

	for i := 0; i < int(idx.Skip) || rune(l[0]) == idx.MetaChar; i++ {
		h = append(h, l)
		l, err = buf.ReadString('\n')
		if err != nil {
			return tbx, err
		}
	}
	header := strings.Join(h, "")

	if len(h) > 0 && strings.HasSuffix(tbx.path, ".vcf.gz") {
		var err error
		h := strings.NewReader(header)

		tbx.VReader, err = vcfgo.NewReader(h, true)
		if err != nil {
			return nil, err
		}
	} else if len(h) > 0 {
		htab := strings.Split(strings.TrimSpace(h[len(h)-1]), "\t")
		// try to find ref and alternate columns to make an IREFALT
		for i, hdr := range htab {
			if l := strings.ToLower(hdr); l == "ref" || l == "reference" {
				tbx.refalt = append(tbx.refalt, i)
				break
			}
		}
		for i, hdr := range htab {
			if l := strings.ToLower(hdr); l == "alt" || l == "alternate" {
				tbx.refalt = append(tbx.refalt, i)
				break
			}
		}
		if len(tbx.refalt) != 2 {
			tbx.refalt = nil
		}
	}
	tbx.buf = buf
	tbx.Index = idx
	return tbx, nil
}

func (b *Bix) Close() error {
	b.bgzf.Close()
	b.file.Close()
	return nil
}

func (tbx *Bix) toPosition(toks [][]byte) interfaces.Relatable {
	isVCF := tbx.VReader != nil
	var g *parsers.Interval

	if isVCF {
		v := tbx.VReader.Parse(toks)
		return interfaces.AsRelatable(v)

	} else {
		g, _ = newgeneric(toks, int(tbx.Index.NameColumn-1), int(tbx.Index.BeginColumn-1),
			int(tbx.Index.EndColumn-1), tbx.Index.ZeroBased)
	}
	if tbx.refalt != nil {
		ra := parsers.RefAltInterval{Interval: *g, HasEnd: tbx.Index.EndColumn != tbx.Index.BeginColumn}
		ra.SetRefAlt(tbx.refalt)
		return &ra
	}
	return g
}

func unsafeString(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

// return an interval using the info from the tabix index
func newgeneric(fields [][]byte, chromCol int, startCol int, endCol int, zeroBased bool) (*parsers.Interval, error) {
	s, err := strconv.Atoi(unsafeString(fields[startCol]))
	if err != nil {
		return nil, err
	}
	if !zeroBased {
		s -= 1
	}
	e, err := strconv.Atoi(unsafeString(fields[endCol]))
	if err != nil {
		return nil, err
	}
	return parsers.NewInterval(string(fields[chromCol]), uint32(s), uint32(e), fields, uint32(0), nil), nil
}

func (tbx *Bix) ChunkedReader(chrom string, start, end int) (io.ReadCloser, error) {
	chunks, err := tbx.Chunks(chrom, start, end)
	if err == index.ErrNoReference {
		if strings.HasPrefix(chrom, "chr") {
			chunks, err = tbx.Chunks(chrom[3:], start, end)
		} else {
			chunks, err = tbx.Chunks("chr"+chrom, start, end)
		}
	}
	if err == index.ErrInvalid {
		return index.NewChunkReader(tbx.bgzf, []bgzf.Chunk{})
	} else if err == index.ErrNoReference {
		log.Printf("chromosome %s not found in %s\n", chrom, tbx.path)
		return index.NewChunkReader(tbx.bgzf, []bgzf.Chunk{})
	} else if err != nil {
		return nil, err
	}
	cr, err := index.NewChunkReader(tbx.bgzf, chunks)
	if err != nil {
		return nil, err
	}
	return cr, nil
}

// bixerator meets interfaces.RelatableIterator
type bixerator struct {
	rdr io.ReadCloser
	buf *bufio.Reader
	tbx *Bix

	region interfaces.IPosition
}

func makeFields(line []byte) [][]byte {
	fields := make([][]byte, 9)
	copy(fields[:8], bytes.SplitN(line, []byte{'\t'}, 8))
	s := 0
	for i, f := range fields {
		if i == 7 {
			break
		}
		s += len(f) + 1
	}
	e := bytes.IndexByte(line[s:], '\t')
	if e == -1 {
		e = len(line)
	} else {
		e += s
	}

	fields[7] = line[s:e]
	if len(line) > e+1 {
		fields[8] = line[e+1:]
	} else {
		fields = fields[:8]
	}

	return fields
}

func (b bixerator) Next() (interfaces.Relatable, error) {

	for {
		line, err := b.buf.ReadBytes('\n')

		if err == io.EOF && len(line) == 0 {
			return nil, io.EOF
		} else if err != nil {
			return nil, err
		}
		if len(line) == 0 {
			return nil, io.EOF
		}
		if line[len(line)-1] == '\n' {
			line = line[:len(line)-1]
		}
		in := true
		var toks [][]byte
		if b.region != nil {
			var err error

			in, err, toks = b.inBounds(line)
			if err != nil {
				return nil, err
			}
		} else {
			if b.tbx.VReader != nil {
				toks = makeFields(line)
			} else {
				toks = bytes.Split(line, []byte{'\t'})
			}
		}

		if in {
			return b.tbx.toPosition(toks), nil
		}
	}
	return nil, io.EOF
}

func (b bixerator) Close() error {
	if b.rdr != nil {
		b.rdr.Close()
	}
	return b.tbx.Close()
}

var _ interfaces.RelatableIterator = bixerator{}

func (tbx *Bix) Query(region interfaces.IPosition) (interfaces.RelatableIterator, error) {
	tbx2, err := newShort(tbx)
	if err != nil {
		return nil, err
	}
	if region == nil {
		var l string
		var err error
		buf := bufio.NewReader(tbx2.bgzf)
		l, err = buf.ReadString('\n')
		for i := 0; i < int(tbx2.Index.Skip) || rune(l[0]) == tbx2.Index.MetaChar; i++ {
			l, err = buf.ReadString('\n')
			if err != nil {
				return nil, err
			}
		}
		if tbx2.Index.Skip == 0 && rune(l[0]) != tbx2.Index.MetaChar {
			buf = bufio.NewReader(io.MultiReader(strings.NewReader(l), buf))
		}
		return bixerator{nil, buf, tbx2, region}, nil
	}

	cr, err := tbx2.ChunkedReader(region.Chrom(), int(region.Start()), int(region.End()))
	if err != nil {
		if cr != nil {
			tbx2.Close()
			cr.Close()
		}
		return nil, err
	}
	return bixerator{cr, bufio.NewReader(cr), tbx2, region}, nil
}

func (tbx *Bix) AddInfoToHeader(id, number, vtype, desc string) {
	if tbx.VReader == nil {
		return
	}
	tbx.VReader.AddInfoToHeader(id, number, vtype, desc)
}

func (tbx *Bix) GetHeaderType(field string) string {
	if tbx.VReader == nil {
		return ""
	}
	return tbx.VReader.GetHeaderType(field)
}

func (tbx *Bix) GetHeaderDescription(field string) string {
	if tbx.VReader == nil {
		return ""
	}
	if h, ok := tbx.VReader.Header.Infos[field]; ok {
		return h.Description
	}
	return ""
}

func (tbx *Bix) GetHeaderNumber(field string) string {
	if tbx.VReader == nil {
		return "1"
	}
	if h, ok := tbx.VReader.Header.Infos[field]; ok {
		return h.Number
	}
	return "1"
}

func (b *bixerator) inBounds(line []byte) (bool, error, [][]byte) {

	var readErr error
	line = bytes.TrimRight(line, "\r\n")
	var toks [][]byte
	if b.tbx.VReader != nil {
		toks = makeFields(line)
	} else {
		toks = bytes.Split(line, []byte{'\t'})
	}

	s, err := strconv.Atoi(unsafeString(toks[b.tbx.BeginColumn-1]))
	if err != nil {
		return false, err, toks
	}

	pos := s
	if !b.tbx.ZeroBased {
		pos -= 1
	}
	if pos >= int(b.region.End()) {
		return false, io.EOF, toks
	}

	if b.tbx.EndColumn != 0 {
		e, err := strconv.Atoi(unsafeString(toks[b.tbx.EndColumn-1]))
		if err != nil {
			return false, err, toks
		}
		if e < int(b.region.Start()) {
			return false, readErr, toks
		}
		return true, readErr, toks
	} else if b.tbx.VReader != nil {
		start := int(b.region.Start())
		alt := strings.Split(string(toks[4]), ",")
		lref := len(toks[3])
		if start >= pos+lref {
			for _, a := range alt {
				if a[0] != '<' || a == "<CN0>" {
					e := pos + lref
					if e > start {
						return true, readErr, toks
					}
				} else if strings.HasPrefix(a, "<DEL") || strings.HasPrefix(a, "<DUP") || strings.HasPrefix(a, "<INV") || strings.HasPrefix(a, "<CN") {
					info := string(toks[7])
					if idx := strings.Index(info, ";END="); idx != -1 {
						v := info[idx+5 : idx+5+strings.Index(info[idx+5:], ";")]
						e, err := strconv.Atoi(v)
						if err != nil {
							return false, err, toks
						}
						if e > start {
							return true, readErr, toks
						}
					} else {
						log.Println("no end:", b.tbx.path, string(toks[0]), pos, string(toks[3]), a)
					}
				}
			}
		} else {
			return true, readErr, toks
		}
		return false, readErr, toks
	}
	return false, readErr, toks

}
<commit_msg>propagate workers<commit_after>// Tabix queries for go
package bix

import (
	"bufio"
	"bytes"
	"compress/gzip"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"unsafe"

	"github.com/biogo/hts/bgzf"
	"github.com/biogo/hts/bgzf/index"
	"github.com/biogo/hts/tabix"
	"github.com/brentp/irelate/interfaces"
	"github.com/brentp/irelate/parsers"
	"github.com/brentp/vcfgo"
)

// Bix provides read access to tabix files.
type Bix struct {
	*tabix.Index
	bgzf    *bgzf.Reader
	path    string
	workers int

	VReader *vcfgo.Reader
	// index for 'ref' and 'alt' columns if they were present.
	refalt []int

	file *os.File
	buf  *bufio.Reader
}

// create a new bix that does as little as possible from the old bix
func newShort(old *Bix) (*Bix, error) {
	tbx := &Bix{
		Index:   old.Index,
		path:    old.path,
		workers: old.workers,
		VReader: old.VReader,
		refalt:  old.refalt,
	}
	var err error
	tbx.file, err = os.Open(tbx.path)
	if err != nil {
		return nil, err
	}
	tbx.bgzf, err = bgzf.NewReader(tbx.file, old.workers)
	if err != nil {
		return nil, err
	}
	return tbx, nil
}

// New returns a &Bix
func New(path string, workers ...int) (*Bix, error) {
	f, err := os.Open(path + ".tbi")
	if err != nil {
		return nil, err
	}
	defer f.Close()

	gz, err := gzip.NewReader(f)
	if err != nil {
		return nil, err
	}
	defer gz.Close()

	idx, err := tabix.ReadFrom(gz)
	if err != nil {
		return nil, err
	}
	n := 1
	if len(workers) > 0 {
		n = workers[0]
	}

	b, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	bgz, err := bgzf.NewReader(b, n)
	if err != nil {
		return nil, err
	}

	var h []string
	tbx := &Bix{bgzf: bgz, path: path, file: b, workers: n}

	buf := bufio.NewReader(bgz)
	l, err := buf.ReadString('\n')
	if err != nil {
		return tbx, err
	}

	for i := 0; i < int(idx.Skip) || rune(l[0]) == idx.MetaChar; i++ {
		h = append(h, l)
		l, err = buf.ReadString('\n')
		if err != nil {
			return tbx, err
		}
	}
	header := strings.Join(h, "")

	if len(h) > 0 && strings.HasSuffix(tbx.path, ".vcf.gz") {
		var err error
		h := strings.NewReader(header)

		tbx.VReader, err = vcfgo.NewReader(h, true)
		if err != nil {
			return nil, err
		}
	} else if len(h) > 0 {
		htab := strings.Split(strings.TrimSpace(h[len(h)-1]), "\t")
		// try to find ref and alternate columns to make an IREFALT
		for i, hdr := range htab {
			if l := strings.ToLower(hdr); l == "ref" || l == "reference" {
				tbx.refalt = append(tbx.refalt, i)
				break
			}
		}
		for i, hdr := range htab {
			if l := strings.ToLower(hdr); l == "alt" || l == "alternate" {
				tbx.refalt = append(tbx.refalt, i)
				break
			}
		}
		if len(tbx.refalt) != 2 {
			tbx.refalt = nil
		}
	}
	tbx.buf = buf
	tbx.Index = idx
	return tbx, nil
}

func (b *Bix) Close() error {
	b.bgzf.Close()
	b.file.Close()
	return nil
}

func (tbx *Bix) toPosition(toks [][]byte) interfaces.Relatable {
	isVCF := tbx.VReader != nil
	var g *parsers.Interval

	if isVCF {
		v := tbx.VReader.Parse(toks)
		return interfaces.AsRelatable(v)

	} else {
		g, _ = newgeneric(toks, int(tbx.Index.NameColumn-1), int(tbx.Index.BeginColumn-1),
			int(tbx.Index.EndColumn-1), tbx.Index.ZeroBased)
	}
	if tbx.refalt != nil {
		ra := parsers.RefAltInterval{Interval: *g, HasEnd: tbx.Index.EndColumn != tbx.Index.BeginColumn}
		ra.SetRefAlt(tbx.refalt)
		return &ra
	}
	return g
}

func unsafeString(b []byte) string {
	return *(*string)(unsafe.Pointer(&b))
}

// return an interval using the info from the tabix index
func newgeneric(fields [][]byte, chromCol int, startCol int, endCol int, zeroBased bool) (*parsers.Interval, error) {
	s, err := strconv.Atoi(unsafeString(fields[startCol]))
	if err != nil {
		return nil, err
	}
	if !zeroBased {
		s -= 1
	}
	e, err := strconv.Atoi(unsafeString(fields[endCol]))
	if err != nil {
		return nil, err
	}
	return parsers.NewInterval(string(fields[chromCol]), uint32(s), uint32(e), fields, uint32(0), nil), nil
}

func (tbx *Bix) ChunkedReader(chrom string, start, end int) (io.ReadCloser, error) {
	chunks, err := tbx.Chunks(chrom, start, end)
	if err == index.ErrNoReference {
		if strings.HasPrefix(chrom, "chr") {
			chunks, err = tbx.Chunks(chrom[3:], start, end)
		} else {
			chunks, err = tbx.Chunks("chr"+chrom, start, end)
		}
	}
	if err == index.ErrInvalid {
		return index.NewChunkReader(tbx.bgzf, []bgzf.Chunk{})
	} else if err == index.ErrNoReference {
		log.Printf("chromosome %s not found in %s\n", chrom, tbx.path)
		return index.NewChunkReader(tbx.bgzf, []bgzf.Chunk{})
	} else if err != nil {
		return nil, err
	}
	cr, err := index.NewChunkReader(tbx.bgzf, chunks)
	if err != nil {
		return nil, err
	}
	return cr, nil
}

// bixerator meets interfaces.RelatableIterator
type bixerator struct {
	rdr io.ReadCloser
	buf *bufio.Reader
	tbx *Bix

	region interfaces.IPosition
}

func makeFields(line []byte) [][]byte {
	fields := make([][]byte, 9)
	copy(fields[:8], bytes.SplitN(line, []byte{'\t'}, 8))
	s := 0
	for i, f := range fields {
		if i == 7 {
			break
		}
		s += len(f) + 1
	}
	e := bytes.IndexByte(line[s:], '\t')
	if e == -1 {
		e = len(line)
	} else {
		e += s
	}

	fields[7] = line[s:e]
	if len(line) > e+1 {
		fields[8] = line[e+1:]
	} else {
		fields = fields[:8]
	}

	return fields
}

func (b bixerator) Next() (interfaces.Relatable, error) {

	for {
		line, err := b.buf.ReadBytes('\n')

		if err == io.EOF && len(line) == 0 {
			return nil, io.EOF
		} else if err != nil {
			return nil, err
		}
		if len(line) == 0 {
			return nil, io.EOF
		}
		if line[len(line)-1] == '\n' {
			line = line[:len(line)-1]
		}
		in := true
		var toks [][]byte
		if b.region != nil {
			var err error

			in, err, toks = b.inBounds(line)
			if err != nil {
				return nil, err
			}
		} else {
			if b.tbx.VReader != nil {
				toks = makeFields(line)
			} else {
				toks = bytes.Split(line, []byte{'\t'})
			}
		}

		if in {
			return b.tbx.toPosition(toks), nil
		}
	}
	return nil, io.EOF
}

func (b bixerator) Close() error {
	if b.rdr != nil {
		b.rdr.Close()
	}
	return b.tbx.Close()
}

var _ interfaces.RelatableIterator = bixerator{}

func (tbx *Bix) Query(region interfaces.IPosition) (interfaces.RelatableIterator, error) {
	tbx2, err := newShort(tbx)
	if err != nil {
		return nil, err
	}
	if region == nil {
		var l string
		var err error
		buf := bufio.NewReader(tbx2.bgzf)
		l, err = buf.ReadString('\n')
		for i := 0; i < int(tbx2.Index.Skip) || rune(l[0]) == tbx2.Index.MetaChar; i++ {
			l, err = buf.ReadString('\n')
			if err != nil {
				return nil, err
			}
		}
		if tbx2.Index.Skip == 0 && rune(l[0]) != tbx2.Index.MetaChar {
			buf = bufio.NewReader(io.MultiReader(strings.NewReader(l), buf))
		}
		return bixerator{nil, buf, tbx2, region}, nil
	}

	cr, err := tbx2.ChunkedReader(region.Chrom(), int(region.Start()), int(region.End()))
	if err != nil {
		if cr != nil {
			tbx2.Close()
			cr.Close()
		}
		return nil, err
	}
	return bixerator{cr, bufio.NewReader(cr), tbx2, region}, nil
}

func (tbx *Bix) AddInfoToHeader(id, number, vtype, desc string) {
	if tbx.VReader == nil {
		return
	}
	tbx.VReader.AddInfoToHeader(id, number, vtype, desc)
}

func (tbx *Bix) GetHeaderType(field string) string {
	if tbx.VReader == nil {
		return ""
	}
	return tbx.VReader.GetHeaderType(field)
}

func (tbx *Bix) GetHeaderDescription(field string) string {
	if tbx.VReader == nil {
		return ""
	}
	if h, ok := tbx.VReader.Header.Infos[field]; ok {
		return h.Description
	}
	return ""
}

func (tbx *Bix) GetHeaderNumber(field string) string {
	if tbx.VReader == nil {
		return "1"
	}
	if h, ok := tbx.VReader.Header.Infos[field]; ok {
		return h.Number
	}
	return "1"
}

func (b *bixerator) inBounds(line []byte) (bool, error, [][]byte) {

	var readErr error
	line = bytes.TrimRight(line, "\r\n")
	var toks [][]byte
	if b.tbx.VReader != nil {
		toks = makeFields(line)
	} else {
		toks = bytes.Split(line, []byte{'\t'})
	}

	s, err := strconv.Atoi(unsafeString(toks[b.tbx.BeginColumn-1]))
	if err != nil {
		return false, err, toks
	}

	pos := s
	if !b.tbx.ZeroBased {
		pos -= 1
	}
	if pos >= int(b.region.End()) {
		return false, io.EOF, toks
	}

	if b.tbx.EndColumn != 0 {
		e, err := strconv.Atoi(unsafeString(toks[b.tbx.EndColumn-1]))
		if err != nil {
			return false, err, toks
		}
		if e < int(b.region.Start()) {
			return false, readErr, toks
		}
		return true, readErr, toks
	} else if b.tbx.VReader != nil {
		start := int(b.region.Start())
		alt := strings.Split(string(toks[4]), ",")
		lref := len(toks[3])
		if start >= pos+lref {
			for _, a := range alt {
				if a[0] != '<' || a == "<CN0>" {
					e := pos + lref
					if e > start {
						return true, readErr, toks
					}
				} else if strings.HasPrefix(a, "<DEL") || strings.HasPrefix(a, "<DUP") || strings.HasPrefix(a, "<INV") || strings.HasPrefix(a, "<CN") {
					info := string(toks[7])
					if idx := strings.Index(info, ";END="); idx != -1 {
						v := info[idx+5 : idx+5+strings.Index(info[idx+5:], ";")]
						e, err := strconv.Atoi(v)
						if err != nil {
							return false, err, toks
						}
						if e > start {
							return true, readErr, toks
						}
					} else {
						log.Println("no end:", b.tbx.path, string(toks[0]), pos, string(toks[3]), a)
					}
				}
			}
		} else {
			return true, readErr, toks
		}
		return false, readErr, toks
	}
	return false, readErr, toks

}
<|endoftext|>


<commit_before>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package fitasks

import (
	"crypto/x509/pkix"
	"fmt"
	"sort"
	"strings"
	"time"

	"k8s.io/klog/v2"
	"k8s.io/kops/pkg/pki"
	"k8s.io/kops/upup/pkg/fi"
)

// +kops:fitask
type Keypair struct {
	// Name is the name of the keypair
	Name *string
	// AlternateNames a list of alternative names for this certificate
	AlternateNames []string `json:"alternateNames"`
	// Lifecycle is context for a task
	Lifecycle fi.Lifecycle
	// Signer is the keypair to use to sign, for when we want to use an alternative CA
	Signer *Keypair
	// Subject is the certificate subject
	Subject string `json:"subject"`
	// Type the type of certificate i.e. CA, server, client etc
	Type string `json:"type"`
	// LegacyFormat is whether the keypair is stored in a legacy format.
	LegacyFormat bool `json:"oldFormat"`

	certificates *fi.TaskDependentResource
	keyset       *fi.Keyset
}

var _ fi.HasCheckExisting = &Keypair{}
var _ fi.HasName = &Keypair{}

// It's important always to check for the existing key, so we don't regenerate keys e.g. on terraform
func (e *Keypair) CheckExisting(c *fi.Context) bool {
	return true
}

var _ fi.CompareWithID = &Keypair{}

func (e *Keypair) CompareWithID() *string {
	return &e.Subject
}

func (e *Keypair) Find(c *fi.Context) (*Keypair, error) {
	name := fi.StringValue(e.Name)
	if name == "" {
		return nil, nil
	}

	keyset, err := c.Keystore.FindKeyset(name)
	if err != nil {
		return nil, err
	}
	if keyset == nil || keyset.Primary == nil || keyset.Primary.Certificate == nil {
		return nil, nil
	}
	cert := keyset.Primary.Certificate
	if keyset.Primary.PrivateKey == nil {
		return nil, fmt.Errorf("found cert in store, but did not find private key: %q", name)
	}

	var alternateNames []string
	alternateNames = append(alternateNames, cert.Certificate.DNSNames...)
	alternateNames = append(alternateNames, cert.Certificate.EmailAddresses...)
	for _, ip := range cert.Certificate.IPAddresses {
		alternateNames = append(alternateNames, ip.String())
	}
	sort.Strings(alternateNames)

	actual := &Keypair{
		Name:           &name,
		AlternateNames: alternateNames,
		Subject:        pki.PkixNameToString(&cert.Subject),
		Type:           pki.BuildTypeDescription(cert.Certificate),
		LegacyFormat:   keyset.LegacyFormat,
	}

	actual.Signer = &Keypair{Subject: pki.PkixNameToString(&cert.Certificate.Issuer)}

	// Avoid spurious changes
	actual.Lifecycle = e.Lifecycle

	if err := e.setResources(keyset); err != nil {
		return nil, fmt.Errorf("error setting resources: %v", err)
	}

	return actual, nil
}

func (e *Keypair) Run(c *fi.Context) error {
	err := e.normalize()
	if err != nil {
		return err
	}
	return fi.DefaultDeltaRunMethod(e, c)
}

func (e *Keypair) normalize() error {
	var alternateNames []string

	for _, s := range e.AlternateNames {
		s = strings.TrimSpace(s)
		if s == "" {
			continue
		}
		alternateNames = append(alternateNames, s)
	}

	sort.Strings(alternateNames)
	e.AlternateNames = alternateNames

	return nil
}

func (_ *Keypair) CheckChanges(a, e, changes *Keypair) error {
	if a != nil {
		if changes.Name != nil {
			return fi.CannotChangeField("Name")
		}
	}
	return nil
}

func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
	name := fi.StringValue(e.Name)
	if name == "" {
		return fi.RequiredField("Name")
	}

	changeStoredFormat := false
	createCertificate := false
	if a == nil {
		createCertificate = true
		klog.V(8).Infof("creating brand new certificate")
	} else if changes != nil {
		klog.V(8).Infof("creating certificate as changes are not nil")
		if changes.AlternateNames != nil {
			createCertificate = true
			klog.V(8).Infof("creating certificate new AlternateNames")
		} else if changes.Subject != "" {
			createCertificate = true
			klog.V(8).Infof("creating certificate new Subject")
		} else if changes.Type != "" {
			createCertificate = true
			klog.Infof("creating certificate %q as Type has changed (actual=%v, expected=%v)", name, a.Type, e.Type)
		} else if a.LegacyFormat {
			changeStoredFormat = true
		} else {
			klog.Warningf("Ignoring changes in key: %v", fi.DebugAsJsonString(changes))
		}
	}

	if createCertificate {
		klog.V(2).Infof("Creating PKI keypair %q", name)

		keyset, err := c.Keystore.FindKeyset(name)
		if err != nil {
			return err
		}
		if keyset == nil {
			keyset = &fi.Keyset{
				Items: map[string]*fi.KeysetItem{},
			}
		}

		// We always reuse the private key if it exists,
		// if we change keys we often have to regenerate e.g. the service accounts
		// TODO: Eventually rotate keys / don't always reuse?
		var privateKey *pki.PrivateKey
		if keyset.Primary != nil {
			privateKey = keyset.Primary.PrivateKey
		}
		if privateKey == nil {
			klog.V(2).Infof("Creating privateKey %q", name)
		}

		signer := fi.CertificateIDCA
		if e.Signer != nil {
			signer = fi.StringValue(e.Signer.Name)
		}

		klog.Infof("Issuing new certificate: %q", *e.Name)

		serial := pki.BuildPKISerial(time.Now().UnixNano())

		subjectPkix, err := parsePkixName(e.Subject)
		if err != nil {
			return fmt.Errorf("error parsing Subject: %v", err)
		}

		if len(subjectPkix.ToRDNSequence()) == 0 {
			return fmt.Errorf("subject name was empty for SSL keypair %q", *e.Name)
		}

		req := pki.IssueCertRequest{
			Signer:         signer,
			Type:           e.Type,
			Subject:        *subjectPkix,
			AlternateNames: e.AlternateNames,
			PrivateKey:     privateKey,
			Serial:         serial,
		}
		cert, privateKey, _, err := pki.IssueCert(&req, c.Keystore)
		if err != nil {
			return err
		}

		serialString := cert.Certificate.SerialNumber.String()
		ki := &fi.KeysetItem{
			Id:          serialString,
			Certificate: cert,
			PrivateKey:  privateKey,
		}

		keyset.LegacyFormat = false
		keyset.Items[ki.Id] = ki
		keyset.Primary = ki
		err = c.Keystore.StoreKeyset(name, keyset)
		if err != nil {
			return err
		}

		if err := e.setResources(keyset); err != nil {
			return fmt.Errorf("error setting resources: %v", err)
		}

		// Make double-sure it round-trips
		_, err = c.Keystore.FindKeyset(name)
		if err != nil {
			return err
		}

		klog.V(8).Infof("created certificate with cn=%s", cert.Subject.CommonName)
	}

	// TODO: Check correct subject / flags

	if changeStoredFormat {
		// We fetch and reinsert the same keypair, forcing an update to our preferred format
		// TODO: We're assuming that we want to save in the preferred format
		keyset, err := c.Keystore.FindKeyset(name)
		if err != nil {
			return err
		}
		keyset.LegacyFormat = false
		err = c.Keystore.StoreKeyset(name, keyset)
		if err != nil {
			return err
		}

		klog.Infof("updated Keypair %q to new format", name)
	}

	return nil
}

func parsePkixName(s string) (*pkix.Name, error) {
	name := new(pkix.Name)

	tokens := strings.Split(s, ",")
	for _, token := range tokens {
		token = strings.TrimSpace(token)
		kv := strings.SplitN(token, "=", 2)
		if len(kv) != 2 {
			return nil, fmt.Errorf("unrecognized token (expected k=v): %q", token)
		}
		k := strings.ToLower(kv[0])
		v := kv[1]

		switch k {
		case "cn":
			name.CommonName = v
		case "o":
			name.Organization = append(name.Organization, v)
		default:
			return nil, fmt.Errorf("unrecognized key %q in token %q", k, token)
		}
	}

	return name, nil
}

func (e *Keypair) ensureResources() {
	if e.certificates == nil {
		e.certificates = &fi.TaskDependentResource{Task: e}
	}
}

func (e *Keypair) setResources(keyset *fi.Keyset) error {
	e.ensureResources()

	s, err := keyset.ToCertificateBytes()
	if err != nil {
		return err
	}
	e.certificates.Resource = fi.NewBytesResource(s)

	e.keyset = keyset
	return nil
}

func (e *Keypair) Keyset() *fi.Keyset {
	return e.keyset
}

func (e *Keypair) Certificates() *fi.TaskDependentResource {
	e.ensureResources()
	return e.certificates
}
<commit_msg>Fix dryrun cluster creation<commit_after>/*
Copyright 2019 The Kubernetes Authors.

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

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

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

package fitasks

import (
	"crypto/x509/pkix"
	"fmt"
	"sort"
	"strings"
	"time"

	"k8s.io/klog/v2"
	"k8s.io/kops/pkg/pki"
	"k8s.io/kops/upup/pkg/fi"
)

// +kops:fitask
type Keypair struct {
	// Name is the name of the keypair
	Name *string
	// AlternateNames a list of alternative names for this certificate
	AlternateNames []string `json:"alternateNames"`
	// Lifecycle is context for a task
	Lifecycle fi.Lifecycle
	// Signer is the keypair to use to sign, for when we want to use an alternative CA
	Signer *Keypair
	// Subject is the certificate subject
	Subject string `json:"subject"`
	// Type the type of certificate i.e. CA, server, client etc
	Type string `json:"type"`
	// LegacyFormat is whether the keypair is stored in a legacy format.
	LegacyFormat bool `json:"oldFormat"`

	certificates *fi.TaskDependentResource
	keyset       *fi.Keyset
}

var _ fi.HasCheckExisting = &Keypair{}
var _ fi.HasName = &Keypair{}

// It's important always to check for the existing key, so we don't regenerate keys e.g. on terraform
func (e *Keypair) CheckExisting(c *fi.Context) bool {
	return true
}

var _ fi.CompareWithID = &Keypair{}

func (e *Keypair) CompareWithID() *string {
	return &e.Subject
}

func (e *Keypair) Find(c *fi.Context) (*Keypair, error) {
	name := fi.StringValue(e.Name)
	if name == "" {
		return nil, nil
	}

	keyset, err := c.Keystore.FindKeyset(name)
	if err != nil {
		return nil, err
	}
	if keyset == nil || keyset.Primary == nil || keyset.Primary.Certificate == nil {
		return nil, nil
	}
	cert := keyset.Primary.Certificate
	if keyset.Primary.PrivateKey == nil {
		return nil, fmt.Errorf("found cert in store, but did not find private key: %q", name)
	}

	var alternateNames []string
	alternateNames = append(alternateNames, cert.Certificate.DNSNames...)
	alternateNames = append(alternateNames, cert.Certificate.EmailAddresses...)
	for _, ip := range cert.Certificate.IPAddresses {
		alternateNames = append(alternateNames, ip.String())
	}
	sort.Strings(alternateNames)

	actual := &Keypair{
		Name:           &name,
		AlternateNames: alternateNames,
		Subject:        pki.PkixNameToString(&cert.Subject),
		Type:           pki.BuildTypeDescription(cert.Certificate),
		LegacyFormat:   keyset.LegacyFormat,
	}

	actual.Signer = &Keypair{Subject: pki.PkixNameToString(&cert.Certificate.Issuer)}

	// Avoid spurious changes
	actual.Lifecycle = e.Lifecycle

	if err := e.setResources(keyset); err != nil {
		return nil, fmt.Errorf("error setting resources: %v", err)
	}

	return actual, nil
}

func (e *Keypair) Run(c *fi.Context) error {
	err := e.normalize()
	if err != nil {
		return err
	}
	return fi.DefaultDeltaRunMethod(e, c)
}

func (e *Keypair) normalize() error {
	var alternateNames []string

	for _, s := range e.AlternateNames {
		s = strings.TrimSpace(s)
		if s == "" {
			continue
		}
		alternateNames = append(alternateNames, s)
	}

	sort.Strings(alternateNames)
	e.AlternateNames = alternateNames

	return nil
}

func (_ *Keypair) CheckChanges(a, e, changes *Keypair) error {
	if a != nil {
		if changes.Name != nil {
			return fi.CannotChangeField("Name")
		}
	}
	return nil
}

func (_ *Keypair) Render(c *fi.Context, a, e, changes *Keypair) error {
	name := fi.StringValue(e.Name)
	if name == "" {
		return fi.RequiredField("Name")
	}

	changeStoredFormat := false
	createCertificate := false
	if a == nil {
		createCertificate = true
		klog.V(8).Infof("creating brand new certificate")
	} else if changes != nil {
		klog.V(8).Infof("creating certificate as changes are not nil")
		if changes.AlternateNames != nil {
			createCertificate = true
			klog.V(8).Infof("creating certificate new AlternateNames")
		} else if changes.Subject != "" {
			createCertificate = true
			klog.V(8).Infof("creating certificate new Subject")
		} else if changes.Type != "" {
			createCertificate = true
			klog.Infof("creating certificate %q as Type has changed (actual=%v, expected=%v)", name, a.Type, e.Type)
		} else if a.LegacyFormat {
			changeStoredFormat = true
		} else {
			klog.Warningf("Ignoring changes in key: %v", fi.DebugAsJsonString(changes))
		}
	}

	if createCertificate {
		klog.V(2).Infof("Creating PKI keypair %q", name)

		keyset, err := c.Keystore.FindKeyset(name)
		if err != nil {
			return err
		}
		if keyset == nil {
			keyset = &fi.Keyset{
				Items: map[string]*fi.KeysetItem{},
			}
		}

		// We always reuse the private key if it exists,
		// if we change keys we often have to regenerate e.g. the service accounts
		// TODO: Eventually rotate keys / don't always reuse?
		var privateKey *pki.PrivateKey
		if keyset.Primary != nil {
			privateKey = keyset.Primary.PrivateKey
		}
		if privateKey == nil {
			klog.V(2).Infof("Creating privateKey %q", name)
		}

		signer := fi.CertificateIDCA
		if e.Signer != nil {
			signer = fi.StringValue(e.Signer.Name)
		}

		klog.Infof("Issuing new certificate: %q", *e.Name)

		serial := pki.BuildPKISerial(time.Now().UnixNano())

		subjectPkix, err := parsePkixName(e.Subject)
		if err != nil {
			return fmt.Errorf("error parsing Subject: %v", err)
		}

		if len(subjectPkix.ToRDNSequence()) == 0 {
			return fmt.Errorf("subject name was empty for SSL keypair %q", *e.Name)
		}

		req := pki.IssueCertRequest{
			Signer:         signer,
			Type:           e.Type,
			Subject:        *subjectPkix,
			AlternateNames: e.AlternateNames,
			PrivateKey:     privateKey,
			Serial:         serial,
		}
		cert, privateKey, _, err := pki.IssueCert(&req, c.Keystore)
		if err != nil {
			return err
		}

		serialString := cert.Certificate.SerialNumber.String()
		ki := &fi.KeysetItem{
			Id:          serialString,
			Certificate: cert,
			PrivateKey:  privateKey,
		}

		keyset.LegacyFormat = false
		keyset.Items[ki.Id] = ki
		keyset.Primary = ki
		err = c.Keystore.StoreKeyset(name, keyset)
		if err != nil {
			return err
		}

		if err := e.setResources(keyset); err != nil {
			return fmt.Errorf("error setting resources: %v", err)
		}

		// Make double-sure it round-trips
		_, err = c.Keystore.FindKeyset(name)
		if err != nil {
			return err
		}

		klog.V(8).Infof("created certificate with cn=%s", cert.Subject.CommonName)
	}

	// TODO: Check correct subject / flags

	if changeStoredFormat {
		// We fetch and reinsert the same keypair, forcing an update to our preferred format
		// TODO: We're assuming that we want to save in the preferred format
		keyset, err := c.Keystore.FindKeyset(name)
		if err != nil {
			return err
		}
		keyset.LegacyFormat = false
		err = c.Keystore.StoreKeyset(name, keyset)
		if err != nil {
			return err
		}

		klog.Infof("updated Keypair %q to new format", name)
	}

	return nil
}

func parsePkixName(s string) (*pkix.Name, error) {
	name := new(pkix.Name)

	tokens := strings.Split(s, ",")
	for _, token := range tokens {
		token = strings.TrimSpace(token)
		kv := strings.SplitN(token, "=", 2)
		if len(kv) != 2 {
			return nil, fmt.Errorf("unrecognized token (expected k=v): %q", token)
		}
		k := strings.ToLower(kv[0])
		v := kv[1]

		switch k {
		case "cn":
			name.CommonName = v
		case "o":
			name.Organization = append(name.Organization, v)
		default:
			return nil, fmt.Errorf("unrecognized key %q in token %q", k, token)
		}
	}

	return name, nil
}

func (e *Keypair) ensureResources() {
	if e.certificates == nil {
		e.certificates = &fi.TaskDependentResource{
			Resource: fi.NewStringResource("<< TO BE GENERATED >>\n"),
			Task:     e,
		}
		e.keyset = &fi.Keyset{
			Primary: &fi.KeysetItem{
				Id: "<< TO BE GENERATED >>",
			},
		}
	}
}

func (e *Keypair) setResources(keyset *fi.Keyset) error {
	e.ensureResources()

	s, err := keyset.ToCertificateBytes()
	if err != nil {
		return err
	}
	e.certificates.Resource = fi.NewBytesResource(s)

	e.keyset = keyset
	return nil
}

func (e *Keypair) Keyset() *fi.Keyset {
	return e.keyset
}

func (e *Keypair) Certificates() *fi.TaskDependentResource {
	e.ensureResources()
	return e.certificates
}
<|endoftext|>