<commit_before>package tengo

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// Vendor distinguishes between different database distributions/forks
type Vendor int

// Constants representing different supported vendors
const (
	VendorUnknown Vendor = iota
	VendorMySQL
	VendorPercona
	VendorMariaDB
)

func (v Vendor) String() string {
	switch v {
	case VendorMySQL:
		return "mysql"
	case VendorPercona:
		return "percona"
	case VendorMariaDB:
		return "mariadb"
	default:
		return "unknown"
	}
}

// ParseVendor takes a version comment string (e.g. @@version_comment MySQL
// variable) and returns the corresponding Vendor constant, defaulting to
// VendorUnknown if the string is not recognized.
func ParseVendor(versionComment string) Vendor {
	versionComment = strings.ToLower(versionComment)
	// The following loop assumes VendorUnknown==0 (and skips it by starting at 1),
	// but otherwise makes no assumptions about the number of vendors; it loops
	// until it hits a positive number that also yields "unknown" by virtue of
	// the default clause in Vendor.String()'s switch statement.
	for n := 1; Vendor(n).String() != VendorUnknown.String(); n++ {
		if strings.Contains(versionComment, Vendor(n).String()) {
			return Vendor(n)
		}
	}
	return VendorUnknown
}

var reVersion = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)`)

// ParseVersion takes a version string (e.g. @@version variable from MySQL)
// and returns a 3-element array of major, minor, and patch numbers. If parsing
// failed, the returned value will be {0, 0, 0}.
func ParseVersion(version string) (result [3]int) {
	matches := reVersion.FindStringSubmatch(version)
	if matches != nil {
		var err error
		for n := range result {
			result[n], err = strconv.Atoi(matches[n+1])
			if err != nil {
				return [3]int{0, 0, 0}
			}
		}
	}
	return
}

// Flavor represents a database server release, including vendor along with
// major and minor version number, and optionally the patch number (or 0 if
// unknown or irrelevant).
type Flavor struct {
	Vendor Vendor
	Major  int
	Minor  int
	Patch  int
}

// FlavorUnknown represents a flavor that cannot be parsed. This is the zero
// value for Flavor.
var FlavorUnknown = Flavor{VendorUnknown, 0, 0, 0}

// FlavorMySQL55 represents MySQL 5.5.x. This constant omits a patch number;
// avoid direct equality comparisons and ideally only use this in tests.
var FlavorMySQL55 = Flavor{VendorMySQL, 5, 5, 0}

// FlavorMySQL56 represents MySQL 5.6.x. This constant omits a patch number;
// avoid direct equality comparisons and ideally only use this in tests.
var FlavorMySQL56 = Flavor{VendorMySQL, 5, 6, 0}

// FlavorMySQL57 represents MySQL 5.7.x. This constant omits a patch number;
// avoid direct equality comparisons and ideally only use this in tests.
var FlavorMySQL57 = Flavor{VendorMySQL, 5, 7, 0}

// FlavorMySQL80 represents MySQL 8.0.x. This constant omits a patch number;
// avoid direct equality comparisons and ideally only use this in tests.
// Patch number is especially relevant in MySQL 8.0.x as functionality now
// changes in patch releases.
var FlavorMySQL80 = Flavor{VendorMySQL, 8, 0, 0}

// FlavorPercona55 represents Percona Server 5.5.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorPercona55 = Flavor{VendorPercona, 5, 5, 0}

// FlavorPercona56 represents Percona Server 5.6.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorPercona56 = Flavor{VendorPercona, 5, 6, 0}

// FlavorPercona57 represents Percona Server 5.7.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorPercona57 = Flavor{VendorPercona, 5, 7, 0}

// FlavorPercona80 represents Percona Server 8.0.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
// Patch number is especially relevant in Percona Server 8.0.x as functionality
// now changes in patch releases.
var FlavorPercona80 = Flavor{VendorPercona, 8, 0, 0}

// FlavorMariaDB101 represents MariaDB 10.1.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB101 = Flavor{VendorMariaDB, 10, 1, 0}

// FlavorMariaDB102 represents MariaDB 10.2.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB102 = Flavor{VendorMariaDB, 10, 2, 0}

// FlavorMariaDB103 represents MariaDB 10.3.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB103 = Flavor{VendorMariaDB, 10, 3, 0}

// FlavorMariaDB104 represents MariaDB 10.4.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB104 = Flavor{VendorMariaDB, 10, 4, 0}

// FlavorMariaDB105 represents MariaDB 10.5.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB105 = Flavor{VendorMariaDB, 10, 5, 0}

// FlavorMariaDB106 represents MariaDB 10.6.x. This constant omits a patch
// number; avoid direct equality comparisons and ideally only use this in tests.
var FlavorMariaDB106 = Flavor{VendorMariaDB, 10, 6, 0}

// NewFlavor returns a Flavor value based on its inputs, which should be
// supplied in one of these forms:
// NewFlavor("vendor", major, minor)
// NewFlavor("vendor", major, minor, patch)
// NewFlavor("vendor:major.minor")
// NewFlavor("vendor:major.minor.patch")
func NewFlavor(base string, versionParts ...int) Flavor {
	if len(versionParts) == 0 {
		versionParts = []int{0, 0, 0}
		tokens := strings.Split(base, ":")
		base = tokens[0]
		if len(tokens) > 1 {
			tokens = strings.Split(tokens[1], ".")
			for n := 0; n < 3 && n < len(tokens); n++ {
				versionParts[n], _ = strconv.Atoi(tokens[n]) // no need to check error, 0 value is fine
			}
		}
	} else if len(versionParts) < 3 {
		// Append enough zeroes for length to be 3
		versionParts = append(versionParts, make([]int, 3-len(versionParts))...)
	}
	return Flavor{ParseVendor(base), versionParts[0], versionParts[1], versionParts[2]}
}

// ParseFlavor returns a Flavor value based on inputs obtained from server vars
// @@global.version and @@global.version_comment. It accounts for how some
// distributions and/or cloud platforms manipulate those values.
func ParseFlavor(versionString, versionComment string) Flavor {
	version := ParseVersion(versionString)
	vendor := VendorUnknown
	versionString = strings.ToLower(versionString)
	versionComment = strings.ToLower(versionComment)
	for _, attempt := range []Vendor{VendorMariaDB, VendorPercona, VendorMySQL} {
		if strings.Contains(versionComment, attempt.String()) || strings.Contains(versionString, attempt.String()) {
			vendor = attempt
			break
		}
	}

	// If the vendor is still unknown after the above checks, it may be because
	// various distribution methods adjust one or both of those strings. Fall
	// back to sane defaults for known major versions.
	// This logic will need to change whenever MySQL 9+ or MariaDB 11+ exists.
	if vendor == VendorUnknown {
		if version[0] == 10 {
			vendor = VendorMariaDB
		} else if version[0] == 5 || version[0] == 8 {
			vendor = VendorMySQL
		}
	}

	return Flavor{
		Vendor: vendor,
		Major:  version[0],
		Minor:  version[1],
		Patch:  version[2],
	}
}

func (fl Flavor) String() string {
	if fl.Patch > 0 {
		return fmt.Sprintf("%s:%d.%d.%d", fl.Vendor, fl.Major, fl.Minor, fl.Patch)
	}
	return fmt.Sprintf("%s:%d.%d", fl.Vendor, fl.Major, fl.Minor)
}

// Family returns a copy of the receiver with a zeroed-out patch version.
func (fl Flavor) Family() Flavor {
	fl.Patch = 0 // receiver is passed by value, so mutation is fine here
	return fl
}

// VendorMinVersion returns true if this flavor matches the supplied vendor,
// and has a version equal to or newer than the specified version.
func (fl Flavor) VendorMinVersion(vendor Vendor, versionParts ...int) bool {
	if fl.Vendor != vendor {
		return false
	}
	if len(versionParts) < 3 {
		// Append enough zeroes for length to be 3
		versionParts = append(versionParts, make([]int, 3-len(versionParts))...)
	}
	other := Flavor{vendor, versionParts[0], versionParts[1], versionParts[2]}
	if fl.Major != other.Major {
		return fl.Major > other.Major
	}
	if fl.Minor != other.Minor {
		return fl.Minor > other.Minor
	}
	return fl.Patch >= other.Patch
}

// MySQLishMinVersion returns true if the vendor isn't VendorMariaDB, and this
// flavor has a version equal to or newer than the specified version. Note that
// this intentionally DOES consider VendorUnknown to be MySQLish.
func (fl Flavor) MySQLishMinVersion(versionParts ...int) bool {
	if fl.Vendor == VendorMariaDB {
		return false
	}
	return fl.VendorMinVersion(fl.Vendor, versionParts...)
}

// Supported returns true if package tengo officially supports this flavor
func (fl Flavor) Supported() bool {
	switch fl.Vendor {
	case VendorMySQL, VendorPercona:
		// Currently support 5.5.0 through 8.0.x
		return fl.MySQLishMinVersion(5, 5) && !fl.MySQLishMinVersion(8, 1)
	case VendorMariaDB:
		// Currently support 10.1.0 through 10.6.x
		return fl.Major == 10 && fl.Minor >= 1 && fl.Minor <= 6
	}
	return false
}

// Known returns true if both the vendor and major version of this flavor were
// parsed properly
func (fl Flavor) Known() bool {
	return fl.Vendor != VendorUnknown && fl.Major > 0
}

// AllowBlobDefaults returns true if the flavor permits blob and text types
// to have literal default values. (Note that MySQL may permit these types to
// have default *expressions* anyway.)
func (fl Flavor) AllowBlobDefaults() bool {
	return fl.VendorMinVersion(VendorMariaDB, 10, 2)
}

// FractionalTimestamps returns true if the flavor supports fractional
// seconds in timestamp and datetime values. Note that this returns true for
// FlavorUnknown as a special-case, since all recent flavors do support this.
func (fl Flavor) FractionalTimestamps() bool {
	if fl == FlavorUnknown {
		return true
	}
	return fl.Major > 5 || (fl.Major == 5 && fl.Minor > 5)
}

// HasDataDictionary returns true if the flavor has a global transactional
// data dictionary instead of using traditional frm files.
func (fl Flavor) HasDataDictionary() bool {
	return fl.MySQLishMinVersion(8, 0)
}

// DefaultUtf8mb4Collation returns the name of the default collation of the
// utf8mb4 character set in this flavor.
func (fl Flavor) DefaultUtf8mb4Collation() string {
	if fl.MySQLishMinVersion(8, 0) {
		return "utf8mb4_0900_ai_ci"
	}
	return "utf8mb4_general_ci"
}

// AlwaysShowTableCollation returns true if this flavor always emits a collation
// clause for the supplied character set, even if the collation is the default
// for the character set
func (fl Flavor) AlwaysShowTableCollation(charSet string) bool {
	if charSet == "utf8mb4" {
		return fl.DefaultUtf8mb4Collation() != "utf8mb4_general_ci"
	}
	return false
}

// GeneratedColumns returns true if the flavor supports generated columns
// using MySQL's native syntax. (Although MariaDB 10.1 has support for generated
// columns, its syntax is borrowed from other DBMS, so false is returned.)
func (fl Flavor) GeneratedColumns() bool {
	return fl.MySQLishMinVersion(5, 7) || fl.VendorMinVersion(VendorMariaDB, 10, 2)
}

// SortedForeignKeys returns true if the flavor sorts foreign keys
// lexicographically in SHOW CREATE TABLE.
func (fl Flavor) SortedForeignKeys() bool {
	// MySQL/Percona 8.0.19+ no longer sort lexicographically
	if fl.MySQLishMinVersion(8, 0, 19) {
		return false
	}

	// 5.5 did not sort lexicographically; other versions do
	return fl.Major > 5 || (fl.Major == 5 && fl.Minor > 5)
}

// OmitIntDisplayWidth returns true if the flavor omits inclusion of display
// widths from column types in the int family, aside from special cases like
// tinyint(1).
func (fl Flavor) OmitIntDisplayWidth() bool {
	return fl.MySQLishMinVersion(8, 0, 19)
}

// HasCheckConstraints returns true if the flavor supports check constraints
// and exposes them in information_schema.
func (fl Flavor) HasCheckConstraints() bool {
	if fl.MySQLishMinVersion(8, 0, 16) || fl.VendorMinVersion(VendorMariaDB, 10, 3, 10) {
		return true
	}
	return fl.Family() == FlavorMariaDB102 && fl.VendorMinVersion(VendorMariaDB, 10, 2, 22)
}
<commit_msg>internal cleanup in tengo: condense the premade Flavor values<commit_after>package tengo

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// Vendor distinguishes between different database distributions/forks
type Vendor int

// Constants representing different supported vendors
const (
	VendorUnknown Vendor = iota
	VendorMySQL
	VendorPercona
	VendorMariaDB
)

func (v Vendor) String() string {
	switch v {
	case VendorMySQL:
		return "mysql"
	case VendorPercona:
		return "percona"
	case VendorMariaDB:
		return "mariadb"
	default:
		return "unknown"
	}
}

// ParseVendor takes a version comment string (e.g. @@version_comment MySQL
// variable) and returns the corresponding Vendor constant, defaulting to
// VendorUnknown if the string is not recognized.
func ParseVendor(versionComment string) Vendor {
	versionComment = strings.ToLower(versionComment)
	// The following loop assumes VendorUnknown==0 (and skips it by starting at 1),
	// but otherwise makes no assumptions about the number of vendors; it loops
	// until it hits a positive number that also yields "unknown" by virtue of
	// the default clause in Vendor.String()'s switch statement.
	for n := 1; Vendor(n).String() != VendorUnknown.String(); n++ {
		if strings.Contains(versionComment, Vendor(n).String()) {
			return Vendor(n)
		}
	}
	return VendorUnknown
}

var reVersion = regexp.MustCompile(`^(\d+)\.(\d+)\.(\d+)`)

// ParseVersion takes a version string (e.g. @@version variable from MySQL)
// and returns a 3-element array of major, minor, and patch numbers. If parsing
// failed, the returned value will be {0, 0, 0}.
func ParseVersion(version string) (result [3]int) {
	matches := reVersion.FindStringSubmatch(version)
	if matches != nil {
		var err error
		for n := range result {
			result[n], err = strconv.Atoi(matches[n+1])
			if err != nil {
				return [3]int{0, 0, 0}
			}
		}
	}
	return
}

// Flavor represents a database server release, including vendor along with
// major and minor version number, and optionally the patch number (or 0 if
// unknown or irrelevant).
type Flavor struct {
	Vendor Vendor
	Major  int
	Minor  int
	Patch  int
}

// FlavorUnknown represents a flavor that cannot be parsed. This is the zero
// value for Flavor.
var FlavorUnknown = Flavor{VendorUnknown, 0, 0, 0}

// Flavor values representing important vendor and major/minor version
// combinations. These all omit patch numbers! Outside of tests, avoid
// direct equality comparison, and instead only compare these to the return
// value of Flavor.Family().
var (
	FlavorMySQL55    = Flavor{VendorMySQL, 5, 5, 0}
	FlavorMySQL56    = Flavor{VendorMySQL, 5, 6, 0}
	FlavorMySQL57    = Flavor{VendorMySQL, 5, 7, 0}
	FlavorMySQL80    = Flavor{VendorMySQL, 8, 0, 0}
	FlavorPercona55  = Flavor{VendorPercona, 5, 5, 0}
	FlavorPercona56  = Flavor{VendorPercona, 5, 6, 0}
	FlavorPercona57  = Flavor{VendorPercona, 5, 7, 0}
	FlavorPercona80  = Flavor{VendorPercona, 8, 0, 0}
	FlavorMariaDB101 = Flavor{VendorMariaDB, 10, 1, 0}
	FlavorMariaDB102 = Flavor{VendorMariaDB, 10, 2, 0}
	FlavorMariaDB103 = Flavor{VendorMariaDB, 10, 3, 0}
	FlavorMariaDB104 = Flavor{VendorMariaDB, 10, 4, 0}
	FlavorMariaDB105 = Flavor{VendorMariaDB, 10, 5, 0}
	FlavorMariaDB106 = Flavor{VendorMariaDB, 10, 6, 0}
)

// NewFlavor returns a Flavor value based on its inputs, which should be
// supplied in one of these forms:
// NewFlavor("vendor", major, minor)
// NewFlavor("vendor", major, minor, patch)
// NewFlavor("vendor:major.minor")
// NewFlavor("vendor:major.minor.patch")
func NewFlavor(base string, versionParts ...int) Flavor {
	if len(versionParts) == 0 {
		versionParts = []int{0, 0, 0}
		tokens := strings.Split(base, ":")
		base = tokens[0]
		if len(tokens) > 1 {
			tokens = strings.Split(tokens[1], ".")
			for n := 0; n < 3 && n < len(tokens); n++ {
				versionParts[n], _ = strconv.Atoi(tokens[n]) // no need to check error, 0 value is fine
			}
		}
	} else if len(versionParts) < 3 {
		// Append enough zeroes for length to be 3
		versionParts = append(versionParts, make([]int, 3-len(versionParts))...)
	}
	return Flavor{ParseVendor(base), versionParts[0], versionParts[1], versionParts[2]}
}

// ParseFlavor returns a Flavor value based on inputs obtained from server vars
// @@global.version and @@global.version_comment. It accounts for how some
// distributions and/or cloud platforms manipulate those values.
func ParseFlavor(versionString, versionComment string) Flavor {
	version := ParseVersion(versionString)
	vendor := VendorUnknown
	versionString = strings.ToLower(versionString)
	versionComment = strings.ToLower(versionComment)
	for _, attempt := range []Vendor{VendorMariaDB, VendorPercona, VendorMySQL} {
		if strings.Contains(versionComment, attempt.String()) || strings.Contains(versionString, attempt.String()) {
			vendor = attempt
			break
		}
	}

	// If the vendor is still unknown after the above checks, it may be because
	// various distribution methods adjust one or both of those strings. Fall
	// back to sane defaults for known major versions.
	// This logic will need to change whenever MySQL 9+ or MariaDB 11+ exists.
	if vendor == VendorUnknown {
		if version[0] == 10 {
			vendor = VendorMariaDB
		} else if version[0] == 5 || version[0] == 8 {
			vendor = VendorMySQL
		}
	}

	return Flavor{
		Vendor: vendor,
		Major:  version[0],
		Minor:  version[1],
		Patch:  version[2],
	}
}

func (fl Flavor) String() string {
	if fl.Patch > 0 {
		return fmt.Sprintf("%s:%d.%d.%d", fl.Vendor, fl.Major, fl.Minor, fl.Patch)
	}
	return fmt.Sprintf("%s:%d.%d", fl.Vendor, fl.Major, fl.Minor)
}

// Family returns a copy of the receiver with a zeroed-out patch version.
func (fl Flavor) Family() Flavor {
	fl.Patch = 0 // receiver is passed by value, so mutation is fine here
	return fl
}

// VendorMinVersion returns true if this flavor matches the supplied vendor,
// and has a version equal to or newer than the specified version.
func (fl Flavor) VendorMinVersion(vendor Vendor, versionParts ...int) bool {
	if fl.Vendor != vendor {
		return false
	}
	if len(versionParts) < 3 {
		// Append enough zeroes for length to be 3
		versionParts = append(versionParts, make([]int, 3-len(versionParts))...)
	}
	other := Flavor{vendor, versionParts[0], versionParts[1], versionParts[2]}
	if fl.Major != other.Major {
		return fl.Major > other.Major
	}
	if fl.Minor != other.Minor {
		return fl.Minor > other.Minor
	}
	return fl.Patch >= other.Patch
}

// MySQLishMinVersion returns true if the vendor isn't VendorMariaDB, and this
// flavor has a version equal to or newer than the specified version. Note that
// this intentionally DOES consider VendorUnknown to be MySQLish.
func (fl Flavor) MySQLishMinVersion(versionParts ...int) bool {
	if fl.Vendor == VendorMariaDB {
		return false
	}
	return fl.VendorMinVersion(fl.Vendor, versionParts...)
}

// Supported returns true if package tengo officially supports this flavor
func (fl Flavor) Supported() bool {
	switch fl.Vendor {
	case VendorMySQL, VendorPercona:
		// Currently support 5.5.0 through 8.0.x
		return fl.MySQLishMinVersion(5, 5) && !fl.MySQLishMinVersion(8, 1)
	case VendorMariaDB:
		// Currently support 10.1.0 through 10.6.x
		return fl.Major == 10 && fl.Minor >= 1 && fl.Minor <= 6
	}
	return false
}

// Known returns true if both the vendor and major version of this flavor were
// parsed properly
func (fl Flavor) Known() bool {
	return fl.Vendor != VendorUnknown && fl.Major > 0
}

// AllowBlobDefaults returns true if the flavor permits blob and text types
// to have literal default values. (Note that MySQL may permit these types to
// have default *expressions* anyway.)
func (fl Flavor) AllowBlobDefaults() bool {
	return fl.VendorMinVersion(VendorMariaDB, 10, 2)
}

// FractionalTimestamps returns true if the flavor supports fractional
// seconds in timestamp and datetime values. Note that this returns true for
// FlavorUnknown as a special-case, since all recent flavors do support this.
func (fl Flavor) FractionalTimestamps() bool {
	if fl == FlavorUnknown {
		return true
	}
	return fl.Major > 5 || (fl.Major == 5 && fl.Minor > 5)
}

// HasDataDictionary returns true if the flavor has a global transactional
// data dictionary instead of using traditional frm files.
func (fl Flavor) HasDataDictionary() bool {
	return fl.MySQLishMinVersion(8, 0)
}

// DefaultUtf8mb4Collation returns the name of the default collation of the
// utf8mb4 character set in this flavor.
func (fl Flavor) DefaultUtf8mb4Collation() string {
	if fl.MySQLishMinVersion(8, 0) {
		return "utf8mb4_0900_ai_ci"
	}
	return "utf8mb4_general_ci"
}

// AlwaysShowTableCollation returns true if this flavor always emits a collation
// clause for the supplied character set, even if the collation is the default
// for the character set
func (fl Flavor) AlwaysShowTableCollation(charSet string) bool {
	if charSet == "utf8mb4" {
		return fl.DefaultUtf8mb4Collation() != "utf8mb4_general_ci"
	}
	return false
}

// GeneratedColumns returns true if the flavor supports generated columns
// using MySQL's native syntax. (Although MariaDB 10.1 has support for generated
// columns, its syntax is borrowed from other DBMS, so false is returned.)
func (fl Flavor) GeneratedColumns() bool {
	return fl.MySQLishMinVersion(5, 7) || fl.VendorMinVersion(VendorMariaDB, 10, 2)
}

// SortedForeignKeys returns true if the flavor sorts foreign keys
// lexicographically in SHOW CREATE TABLE.
func (fl Flavor) SortedForeignKeys() bool {
	// MySQL/Percona 8.0.19+ no longer sort lexicographically
	if fl.MySQLishMinVersion(8, 0, 19) {
		return false
	}

	// 5.5 did not sort lexicographically; other versions do
	return fl.Major > 5 || (fl.Major == 5 && fl.Minor > 5)
}

// OmitIntDisplayWidth returns true if the flavor omits inclusion of display
// widths from column types in the int family, aside from special cases like
// tinyint(1).
func (fl Flavor) OmitIntDisplayWidth() bool {
	return fl.MySQLishMinVersion(8, 0, 19)
}

// HasCheckConstraints returns true if the flavor supports check constraints
// and exposes them in information_schema.
func (fl Flavor) HasCheckConstraints() bool {
	if fl.MySQLishMinVersion(8, 0, 16) || fl.VendorMinVersion(VendorMariaDB, 10, 3, 10) {
		return true
	}
	return fl.Family() == FlavorMariaDB102 && fl.VendorMinVersion(VendorMariaDB, 10, 2, 22)
}
<|endoftext|>


<commit_before>package tracker

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"code.google.com/p/bencode-go"
)

var HTTPTimeout = 30 * time.Second

type httpTracker struct {
	*trackerBase
	client    *http.Client
	trackerID string
}

func newHTTPTracker(b *trackerBase) *httpTracker {
	return &httpTracker{
		trackerBase: b,
		client: &http.Client{
			Timeout: HTTPTimeout,
			Transport: &http.Transport{
				Dial: (&net.Dialer{
					Timeout: HTTPTimeout,
				}).Dial,
				TLSHandshakeTimeout: HTTPTimeout,
				DisableKeepAlives:   true,
			},
		},
	}
}

func (t *httpTracker) Announce(transfer Transfer, cancel <-chan struct{}, event <-chan Event, responseC chan<- *AnnounceResponse) {
	var nextAnnounce time.Duration

	announce := func(e Event) {
		r, err := t.announce(transfer, e)
		if err != nil {
			t.log.Error(err)
			r = &AnnounceResponse{Error: err}
			nextAnnounce = HTTPTimeout
		} else {
			nextAnnounce = r.Interval
		}
		select {
		case responseC <- r:
		case <-cancel:
			return
		}
	}

	announce(None)
	for {
		select {
		case <-time.After(nextAnnounce):
			announce(None)
		case e := <-event:
			announce(e)
		case <-cancel:
			return
		}
	}
}

func (t *httpTracker) announce(transfer Transfer, e Event) (*AnnounceResponse, error) {
	infoHash := transfer.InfoHash()
	q := url.Values{}
	q.Set("info_hash", string(infoHash[:]))
	q.Set("peer_id", string(t.peerID[:]))
	q.Set("port", strconv.FormatUint(uint64(t.port), 10))
	q.Set("uploaded", strconv.FormatInt(transfer.Uploaded(), 10))
	q.Set("downloaded", strconv.FormatInt(transfer.Downloaded(), 10))
	q.Set("left", strconv.FormatInt(transfer.Left(), 10))
	q.Set("compact", "1")
	q.Set("no_peer_id", "1")
	q.Set("numwant", strconv.Itoa(NumWant))
	q.Set("event", e.String())
	if t.trackerID != "" {
		q.Set("trackerid", t.trackerID)
	}
	u := t.url
	u.RawQuery = q.Encode()
	t.log.Debugf("u.String(): %q", u.String())

	resp, err := t.client.Get(u.String())
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		data, _ := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		return nil, fmt.Errorf("status not 200 OK (status: %d body: %q)", resp.StatusCode, string(data))
	}

	var response = new(httpTrackerAnnounceResponse)
	err = bencode.Unmarshal(resp.Body, &response)
	resp.Body.Close()
	if err != nil {
		return nil, err
	}

	if response.WarningMessage != "" {
		t.log.Warning(response.WarningMessage)
	}
	if response.FailureReason != "" {
		return nil, Error(response.FailureReason)
	}

	if response.TrackerId != "" {
		t.trackerID = response.TrackerId
	}

	peers, err := t.parsePeers(bytes.NewReader([]byte(response.Peers)))
	if err != nil {
		return nil, err
	}

	return &AnnounceResponse{
		Interval: time.Duration(response.Interval) * time.Second,
		Leechers: response.Incomplete,
		Seeders:  response.Complete,
		Peers:    peers,
	}, nil
}

type httpTrackerAnnounceResponse struct {
	FailureReason  string `bencode:"failure reason"`
	WarningMessage string `bencode:"warning message"`
	Interval       int32  `bencode:"interval"`
	MinInterval    int32  `bencode:"min interval"`
	TrackerId      string `bencode:"tracker id"`
	Complete       int32  `bencode:"complete"`
	Incomplete     int32  `bencode:"incomplete"`
	Peers          string `bencode:"peers"`
	Peers6         string `bencode:"peers6"`
}
<commit_msg>use new bencode package<commit_after>package tracker

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/zeebo/bencode"
)

var HTTPTimeout = 30 * time.Second

type httpTracker struct {
	*trackerBase
	client    *http.Client
	trackerID string
}

func newHTTPTracker(b *trackerBase) *httpTracker {
	return &httpTracker{
		trackerBase: b,
		client: &http.Client{
			Timeout: HTTPTimeout,
			Transport: &http.Transport{
				Dial: (&net.Dialer{
					Timeout: HTTPTimeout,
				}).Dial,
				TLSHandshakeTimeout: HTTPTimeout,
				DisableKeepAlives:   true,
			},
		},
	}
}

func (t *httpTracker) Announce(transfer Transfer, cancel <-chan struct{}, event <-chan Event, responseC chan<- *AnnounceResponse) {
	var nextAnnounce time.Duration

	announce := func(e Event) {
		r, err := t.announce(transfer, e)
		if err != nil {
			t.log.Error(err)
			r = &AnnounceResponse{Error: err}
			nextAnnounce = HTTPTimeout
		} else {
			nextAnnounce = r.Interval
		}
		select {
		case responseC <- r:
		case <-cancel:
			return
		}
	}

	announce(None)
	for {
		select {
		case <-time.After(nextAnnounce):
			announce(None)
		case e := <-event:
			announce(e)
		case <-cancel:
			return
		}
	}
}

func (t *httpTracker) announce(transfer Transfer, e Event) (*AnnounceResponse, error) {
	infoHash := transfer.InfoHash()
	q := url.Values{}
	q.Set("info_hash", string(infoHash[:]))
	q.Set("peer_id", string(t.peerID[:]))
	q.Set("port", strconv.FormatUint(uint64(t.port), 10))
	q.Set("uploaded", strconv.FormatInt(transfer.Uploaded(), 10))
	q.Set("downloaded", strconv.FormatInt(transfer.Downloaded(), 10))
	q.Set("left", strconv.FormatInt(transfer.Left(), 10))
	q.Set("compact", "1")
	q.Set("no_peer_id", "1")
	q.Set("numwant", strconv.Itoa(NumWant))
	q.Set("event", e.String())
	if t.trackerID != "" {
		q.Set("trackerid", t.trackerID)
	}
	u := t.url
	u.RawQuery = q.Encode()
	t.log.Debugf("u.String(): %q", u.String())

	resp, err := t.client.Get(u.String())
	if err != nil {
		return nil, err
	}

	if resp.StatusCode != 200 {
		data, _ := ioutil.ReadAll(resp.Body)
		resp.Body.Close()
		return nil, fmt.Errorf("status not 200 OK (status: %d body: %q)", resp.StatusCode, string(data))
	}

	var response = new(httpTrackerAnnounceResponse)
	d := bencode.NewDecoder(resp.Body)
	err = d.Decode(&response)
	resp.Body.Close()
	if err != nil {
		return nil, err
	}

	if response.WarningMessage != "" {
		t.log.Warning(response.WarningMessage)
	}
	if response.FailureReason != "" {
		return nil, Error(response.FailureReason)
	}

	if response.TrackerId != "" {
		t.trackerID = response.TrackerId
	}

	peers, err := t.parsePeers(bytes.NewReader([]byte(response.Peers)))
	if err != nil {
		return nil, err
	}

	return &AnnounceResponse{
		Interval: time.Duration(response.Interval) * time.Second,
		Leechers: response.Incomplete,
		Seeders:  response.Complete,
		Peers:    peers,
	}, nil
}

type httpTrackerAnnounceResponse struct {
	FailureReason  string `bencode:"failure reason"`
	WarningMessage string `bencode:"warning message"`
	Interval       int32  `bencode:"interval"`
	MinInterval    int32  `bencode:"min interval"`
	TrackerId      string `bencode:"tracker id"`
	Complete       int32  `bencode:"complete"`
	Incomplete     int32  `bencode:"incomplete"`
	Peers          string `bencode:"peers"`
	Peers6         string `bencode:"peers6"`
}
<|endoftext|>


<commit_before>// Copyright 2016 Hajime Hoshi
//
// 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.

// +build android ios

package ui

import (
	"errors"
	"image"
	"runtime"
	"sync"
	"time"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/internal/devicescale"
	"github.com/hajimehoshi/ebiten/internal/input"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

var (
	glContextCh = make(chan gl.Context)
	renderCh    = make(chan struct{})
	renderChEnd = make(chan struct{})
	currentUI   = &userInterface{}
)

func Render(chError <-chan error) error {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	if chError == nil {
		return errors.New("ui: chError must not be nil")
	}
	// TODO: Check this is called on the rendering thread
	select {
	case renderCh <- struct{}{}:
		return opengl.GetContext().DoWork(chError, renderChEnd)
	case <-time.After(500 * time.Millisecond):
		// This function must not be blocked. We need to break for timeout.
		return nil
	}
}

type userInterface struct {
	width       int
	height      int
	scale       float64
	sizeChanged bool

	// Used for gomobile-build
	fullscreenScale    float64
	fullscreenWidthPx  int
	fullscreenHeightPx int

	m sync.RWMutex
}

// appMain is the main routine for gomobile-build mode.
func appMain(a app.App) {
	var glctx gl.Context
	touches := map[touch.Sequence]*input.Touch{}
	for e := range a.Events() {
		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				glctx = nil
			}
		case size.Event:
			setFullscreen(e.WidthPx, e.HeightPx)
		case paint.Event:
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderChEnd
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := devicescale.DeviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				t := input.NewTouch(int(e.Sequence), int(x), int(y))
				touches[e.Sequence] = t
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			ts := []*input.Touch{}
			for _, t := range touches {
				ts = append(ts, t)
			}
			UpdateTouches(ts)
		}
	}
}

func Run(width, height int, scale float64, title string, g GraphicsContext, mainloop bool) error {
	u := currentUI

	u.m.Lock()
	u.width = width
	u.height = height
	u.scale = scale
	u.sizeChanged = true
	u.m.Unlock()
	// title is ignored?

	if mainloop {
		ctx := <-glContextCh
		opengl.InitWithContext(ctx)
	} else {
		opengl.Init()
	}

	for {
		if err := u.update(g); err != nil {
			return err
		}
	}
}

// RunMainThreadLoop runs the main routine for gomobile-build.
func RunMainThreadLoop(ch <-chan error) error {
	go func() {
		// As mobile apps never ends, RunMainThreadLoop can't return.
		// Just panic here.
		err := <-ch
		panic(err)
	}()
	app.Main(appMain)
	return nil
}

func (u *userInterface) updateGraphicsContext(g GraphicsContext) {
	sizeChanged := false
	width, height := 0, 0
	actualScale := 0.0

	u.m.Lock()
	sizeChanged = u.sizeChanged
	if sizeChanged {
		width = u.width
		height = u.height
		actualScale = u.scaleImpl() * devicescale.DeviceScale()
	}
	u.sizeChanged = false
	u.m.Unlock()

	if sizeChanged {
		// Sizing also calls GL functions
		g.SetSize(width, height, actualScale)
	}
}

func actualScale() float64 {
	return currentUI.actualScale()
}

func (u *userInterface) actualScale() float64 {
	u.m.Lock()
	s := u.scaleImpl() * devicescale.DeviceScale()
	u.m.Unlock()
	return s
}

func (u *userInterface) scaleImpl() float64 {
	scale := u.scale
	if u.fullscreenScale != 0 {
		scale = u.fullscreenScale
	}
	return scale
}

func (u *userInterface) update(g GraphicsContext) error {
	<-renderCh
	defer func() {
		renderChEnd <- struct{}{}
	}()

	u.updateGraphicsContext(g)

	if err := g.Update(func() {
		u.updateGraphicsContext(g)
	}); err != nil {
		return err
	}
	return nil
}

func screenSize() (int, int) {
	return currentUI.screenSize()
}

func (u *userInterface) screenSize() (int, int) {
	u.m.Lock()
	w, h := u.width, u.height
	u.m.Unlock()
	return w, h
}

func MonitorSize() (int, int) {
	// TODO: This function should return fullscreenWidthPx, fullscreenHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

func SetScreenSize(width, height int) bool {
	currentUI.setScreenSize(width, height)
	return true
}

func (u *userInterface) setScreenSize(width, height int) {
	u.m.Lock()
	if u.width != width || u.height != height {
		u.width = width
		u.height = height
		u.updateFullscreenScaleIfNeeded()
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func SetScreenScale(scale float64) bool {
	currentUI.setScreenScale(scale)
	return false
}

func (u *userInterface) setScreenScale(scale float64) {
	u.m.Lock()
	if u.scale != scale {
		u.scale = scale
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func ScreenScale() float64 {
	u := currentUI
	u.m.RLock()
	s := u.scale
	u.m.RUnlock()
	return s
}

func setFullscreen(widthPx, heightPx int) {
	currentUI.setFullscreen(widthPx, heightPx)
}

func (u *userInterface) setFullscreen(widthPx, heightPx int) {
	u.m.Lock()
	u.fullscreenWidthPx = widthPx
	u.fullscreenHeightPx = heightPx
	u.updateFullscreenScaleIfNeeded()
	u.sizeChanged = true
	u.m.Unlock()
}

func (u *userInterface) updateFullscreenScaleIfNeeded() {
	if u.fullscreenWidthPx == 0 || u.fullscreenHeightPx == 0 {
		return
	}
	w, h := u.width, u.height
	scaleX := float64(u.fullscreenWidthPx) / float64(w)
	scaleY := float64(u.fullscreenHeightPx) / float64(h)
	scale := scaleX
	if scale > scaleY {
		scale = scaleY
	}
	u.fullscreenScale = scale / devicescale.DeviceScale()
}

func ScreenPadding() (x0, y0, x1, y1 float64) {
	return currentUI.screenPadding()
}

func (u *userInterface) screenPadding() (x0, y0, x1, y1 float64) {
	u.m.Lock()
	x0, y0, x1, y1 = u.screenPaddingImpl()
	u.m.Unlock()
	return
}

func (u *userInterface) screenPaddingImpl() (x0, y0, x1, y1 float64) {
	if u.fullscreenScale == 0 {
		return 0, 0, 0, 0
	}
	s := u.fullscreenScale * devicescale.DeviceScale()
	ox := (float64(u.fullscreenWidthPx) - float64(u.width)*s) / 2
	oy := (float64(u.fullscreenHeightPx) - float64(u.height)*s) / 2
	return ox, oy, ox, oy
}

func AdjustedCursorPosition() (x, y int) {
	return currentUI.adjustPosition(input.Get().CursorPosition())
}

func AdjustedTouches() []*input.Touch {
	ts := input.Get().Touches()
	adjusted := make([]*input.Touch, len(ts))
	for i, t := range ts {
		x, y := currentUI.adjustPosition(t.Position())
		adjusted[i] = input.NewTouch(t.ID(), x, y)
	}
	return adjusted
}

func (u *userInterface) adjustPosition(x, y int) (int, int) {
	u.m.Lock()
	ox, oy, _, _ := u.screenPaddingImpl()
	s := u.scaleImpl()
	as := s * devicescale.DeviceScale()
	u.m.Unlock()
	return int(float64(x)/s - ox/as), int(float64(y)/s - oy/as)
}

func IsCursorVisible() bool {
	return false
}

func SetCursorVisible(visible bool) {
	// Do nothing
}

func IsFullscreen() bool {
	return false
}

func SetFullscreen(fullscreen bool) {
	// Do nothing
}

func IsRunnableInBackground() bool {
	return false
}

func SetRunnableInBackground(runnableInBackground bool) {
	// Do nothing
}

func SetWindowTitle(title string) {
	// Do nothing
}

func SetWindowIcon(iconImages []image.Image) {
	// Do nothing
}

func IsWindowDecorated() bool {
	return false
}

func SetWindowDecorated(decorated bool) {
	// Do nothing
}

func UpdateTouches(touches []*input.Touch) {
	input.Get().UpdateTouches(touches)
}
<commit_msg>ui: Bug fix: sizeChange must be set after updateFullscreenScaleIfNeeded<commit_after>// Copyright 2016 Hajime Hoshi
//
// 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.

// +build android ios

package ui

import (
	"errors"
	"image"
	"runtime"
	"sync"
	"time"

	"golang.org/x/mobile/app"
	"golang.org/x/mobile/event/lifecycle"
	"golang.org/x/mobile/event/paint"
	"golang.org/x/mobile/event/size"
	"golang.org/x/mobile/event/touch"
	"golang.org/x/mobile/gl"

	"github.com/hajimehoshi/ebiten/internal/devicescale"
	"github.com/hajimehoshi/ebiten/internal/input"
	"github.com/hajimehoshi/ebiten/internal/opengl"
)

var (
	glContextCh = make(chan gl.Context)
	renderCh    = make(chan struct{})
	renderChEnd = make(chan struct{})
	currentUI   = &userInterface{}
)

func Render(chError <-chan error) error {
	runtime.LockOSThread()
	defer runtime.UnlockOSThread()

	if chError == nil {
		return errors.New("ui: chError must not be nil")
	}
	// TODO: Check this is called on the rendering thread
	select {
	case renderCh <- struct{}{}:
		return opengl.GetContext().DoWork(chError, renderChEnd)
	case <-time.After(500 * time.Millisecond):
		// This function must not be blocked. We need to break for timeout.
		return nil
	}
}

type userInterface struct {
	width       int
	height      int
	scale       float64
	sizeChanged bool

	// Used for gomobile-build
	fullscreenScale    float64
	fullscreenWidthPx  int
	fullscreenHeightPx int

	m sync.RWMutex
}

// appMain is the main routine for gomobile-build mode.
func appMain(a app.App) {
	var glctx gl.Context
	touches := map[touch.Sequence]*input.Touch{}
	for e := range a.Events() {
		switch e := a.Filter(e).(type) {
		case lifecycle.Event:
			switch e.Crosses(lifecycle.StageVisible) {
			case lifecycle.CrossOn:
				glctx, _ = e.DrawContext.(gl.Context)
				// Assume that glctx is always a same instance.
				// Then, only once initializing should be enough.
				if glContextCh != nil {
					glContextCh <- glctx
					glContextCh = nil
				}
				a.Send(paint.Event{})
			case lifecycle.CrossOff:
				glctx = nil
			}
		case size.Event:
			setFullscreen(e.WidthPx, e.HeightPx)
		case paint.Event:
			if glctx == nil || e.External {
				continue
			}
			renderCh <- struct{}{}
			<-renderChEnd
			a.Publish()
			a.Send(paint.Event{})
		case touch.Event:
			switch e.Type {
			case touch.TypeBegin, touch.TypeMove:
				s := devicescale.DeviceScale()
				x, y := float64(e.X)/s, float64(e.Y)/s
				// TODO: Is it ok to cast from int64 to int here?
				t := input.NewTouch(int(e.Sequence), int(x), int(y))
				touches[e.Sequence] = t
			case touch.TypeEnd:
				delete(touches, e.Sequence)
			}
			ts := []*input.Touch{}
			for _, t := range touches {
				ts = append(ts, t)
			}
			UpdateTouches(ts)
		}
	}
}

func Run(width, height int, scale float64, title string, g GraphicsContext, mainloop bool) error {
	u := currentUI

	u.m.Lock()
	u.width = width
	u.height = height
	u.scale = scale
	u.sizeChanged = true
	u.m.Unlock()
	// title is ignored?

	if mainloop {
		ctx := <-glContextCh
		opengl.InitWithContext(ctx)
	} else {
		opengl.Init()
	}

	for {
		if err := u.update(g); err != nil {
			return err
		}
	}
}

// RunMainThreadLoop runs the main routine for gomobile-build.
func RunMainThreadLoop(ch <-chan error) error {
	go func() {
		// As mobile apps never ends, RunMainThreadLoop can't return.
		// Just panic here.
		err := <-ch
		panic(err)
	}()
	app.Main(appMain)
	return nil
}

func (u *userInterface) updateGraphicsContext(g GraphicsContext) {
	sizeChanged := false
	width, height := 0, 0
	actualScale := 0.0

	u.m.Lock()
	sizeChanged = u.sizeChanged
	if sizeChanged {
		width = u.width
		height = u.height
		actualScale = u.scaleImpl() * devicescale.DeviceScale()
	}
	u.sizeChanged = false
	u.m.Unlock()

	if sizeChanged {
		// Sizing also calls GL functions
		g.SetSize(width, height, actualScale)
	}
}

func actualScale() float64 {
	return currentUI.actualScale()
}

func (u *userInterface) actualScale() float64 {
	u.m.Lock()
	s := u.scaleImpl() * devicescale.DeviceScale()
	u.m.Unlock()
	return s
}

func (u *userInterface) scaleImpl() float64 {
	scale := u.scale
	if u.fullscreenScale != 0 {
		scale = u.fullscreenScale
	}
	return scale
}

func (u *userInterface) update(g GraphicsContext) error {
	<-renderCh
	defer func() {
		renderChEnd <- struct{}{}
	}()

	u.updateGraphicsContext(g)

	if err := g.Update(func() {
		u.updateGraphicsContext(g)
	}); err != nil {
		return err
	}
	return nil
}

func screenSize() (int, int) {
	return currentUI.screenSize()
}

func (u *userInterface) screenSize() (int, int) {
	u.m.Lock()
	w, h := u.width, u.height
	u.m.Unlock()
	return w, h
}

func MonitorSize() (int, int) {
	// TODO: This function should return fullscreenWidthPx, fullscreenHeightPx,
	// but these values are not initialized until the main loop starts.
	return 0, 0
}

func SetScreenSize(width, height int) bool {
	currentUI.setScreenSize(width, height)
	return true
}

func (u *userInterface) setScreenSize(width, height int) {
	u.m.Lock()
	if u.width != width || u.height != height {
		u.width = width
		u.height = height
		u.updateFullscreenScaleIfNeeded()
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func SetScreenScale(scale float64) bool {
	currentUI.setScreenScale(scale)
	return false
}

func (u *userInterface) setScreenScale(scale float64) {
	u.m.Lock()
	if u.scale != scale {
		u.scale = scale
		u.sizeChanged = true
	}
	u.m.Unlock()
}

func ScreenScale() float64 {
	u := currentUI
	u.m.RLock()
	s := u.scale
	u.m.RUnlock()
	return s
}

func setFullscreen(widthPx, heightPx int) {
	currentUI.setFullscreen(widthPx, heightPx)
}

func (u *userInterface) setFullscreen(widthPx, heightPx int) {
	u.m.Lock()
	u.fullscreenWidthPx = widthPx
	u.fullscreenHeightPx = heightPx
	u.updateFullscreenScaleIfNeeded()
	u.sizeChanged = true
	u.m.Unlock()
}

func (u *userInterface) updateFullscreenScaleIfNeeded() {
	if u.fullscreenWidthPx == 0 || u.fullscreenHeightPx == 0 {
		return
	}
	w, h := u.width, u.height
	scaleX := float64(u.fullscreenWidthPx) / float64(w)
	scaleY := float64(u.fullscreenHeightPx) / float64(h)
	scale := scaleX
	if scale > scaleY {
		scale = scaleY
	}
	u.fullscreenScale = scale / devicescale.DeviceScale()
	u.sizeChanged = true
}

func ScreenPadding() (x0, y0, x1, y1 float64) {
	return currentUI.screenPadding()
}

func (u *userInterface) screenPadding() (x0, y0, x1, y1 float64) {
	u.m.Lock()
	x0, y0, x1, y1 = u.screenPaddingImpl()
	u.m.Unlock()
	return
}

func (u *userInterface) screenPaddingImpl() (x0, y0, x1, y1 float64) {
	if u.fullscreenScale == 0 {
		return 0, 0, 0, 0
	}
	s := u.fullscreenScale * devicescale.DeviceScale()
	ox := (float64(u.fullscreenWidthPx) - float64(u.width)*s) / 2
	oy := (float64(u.fullscreenHeightPx) - float64(u.height)*s) / 2
	return ox, oy, ox, oy
}

func AdjustedCursorPosition() (x, y int) {
	return currentUI.adjustPosition(input.Get().CursorPosition())
}

func AdjustedTouches() []*input.Touch {
	ts := input.Get().Touches()
	adjusted := make([]*input.Touch, len(ts))
	for i, t := range ts {
		x, y := currentUI.adjustPosition(t.Position())
		adjusted[i] = input.NewTouch(t.ID(), x, y)
	}
	return adjusted
}

func (u *userInterface) adjustPosition(x, y int) (int, int) {
	u.m.Lock()
	ox, oy, _, _ := u.screenPaddingImpl()
	s := u.scaleImpl()
	as := s * devicescale.DeviceScale()
	u.m.Unlock()
	return int(float64(x)/s - ox/as), int(float64(y)/s - oy/as)
}

func IsCursorVisible() bool {
	return false
}

func SetCursorVisible(visible bool) {
	// Do nothing
}

func IsFullscreen() bool {
	return false
}

func SetFullscreen(fullscreen bool) {
	// Do nothing
}

func IsRunnableInBackground() bool {
	return false
}

func SetRunnableInBackground(runnableInBackground bool) {
	// Do nothing
}

func SetWindowTitle(title string) {
	// Do nothing
}

func SetWindowIcon(iconImages []image.Image) {
	// Do nothing
}

func IsWindowDecorated() bool {
	return false
}

func SetWindowDecorated(decorated bool) {
	// Do nothing
}

func UpdateTouches(touches []*input.Touch) {
	input.Get().UpdateTouches(touches)
}
<|endoftext|>


<commit_before>package virt

import (
	"github.com/jimmyfrasche/etlite/internal/device"
	"github.com/jimmyfrasche/etlite/internal/device/std"
	"github.com/jimmyfrasche/etlite/internal/driver"
	"github.com/jimmyfrasche/etlite/internal/format"
	"github.com/jimmyfrasche/etlite/internal/format/rawfmt"
	"github.com/jimmyfrasche/etlite/internal/internal/eol"
	"github.com/jimmyfrasche/etlite/internal/internal/errint"
	"github.com/jimmyfrasche/etlite/internal/virt/internal/sysdb"
)

//Machine represents an execution context for a script.
type Machine struct {
	name    string
	conn    *driver.Conn
	output  device.Writer
	input   device.Reader
	encoder format.Encoder
	decoder format.Decoder

	sys                        *sysdb.Sysdb
	savepointStmt, releaseStmt *driver.Stmt

	eframe, derivedTableName string
}

//New creates and prepares an execution context.
func New(db string, args, env []string) (*Machine, error) {
	if db == "" {
		db = ":memory:"
	}
	c, err := driver.Open(db)
	if err != nil {
		return nil, err
	}
	m := &Machine{
		name:   db,
		conn:   c,
		output: std.Out,
		input:  std.In,
		encoder: &rawfmt.Encoder{
			UseCRLF:  eol.Default,
			NoHeader: true,
		},
		decoder: &rawfmt.Decoder{
			UseCRLF:  eol.Default,
			NoHeader: true,
		},
	}
	//Init default dec/enc
	if err := m.decoder.Init(m.input); err != nil {
		return nil, err
	}
	if err := m.encoder.Init(m.output); err != nil {
		return nil, err
	}

	m.sys, err = sysdb.New(m.conn, args, env)
	if err != nil {
		return nil, err
	}

	m.savepointStmt, err = m.conn.Prepare(`SAVEPOINT [1]`)
	if err != nil {
		return nil, errint.Wrap(err)
	}
	m.releaseStmt, err = m.conn.Prepare(`RELEASE SAVEPOINT [1]`)
	if err != nil {
		return nil, errint.Wrap(err)
	}

	return m, nil
}

//Close flushes and closes output and cleans up
//all tracked resources associated with the context.
//It does not track resources allocated by Instructions:
//that is the responsibility of an individual Instruction.
func (m *Machine) Close() (errs []error) {
	err := func(err error) {
		if err != nil {
			errs = append(errs, err)
		}
	}
	o := m.output
	err(o.Flush())
	err(o.Close())
	err(m.input.Close())
	err(m.encoder.Close())
	err(m.decoder.Close())
	err(m.sys.Close())
	err(m.savepointStmt.Close())
	err(m.releaseStmt.Close())
	err(m.conn.Close())
	return
}

//Name reports the name of the main database.
func (m *Machine) Name() string {
	return m.name
}

func (m *Machine) setOutput(o device.Writer) error {
	if o == nil {
		return errint.New("no output device specified")
	}
	if m.output == nil {
		return errint.New("no previous output device")
	}
	if m.encoder == nil {
		return errint.New("no previous decoder")
	}

	if err := m.encoder.Close(); err != nil {
		return err
	}

	if err := m.output.Close(); err != nil {
		return err
	}
	m.output = o

	return m.encoder.Init(m.output)
}

func (m *Machine) setInput(in device.Reader, derivedTableName string) error {
	if in == nil {
		return errint.New("no input device specified")
	}
	if m.input == nil {
		return errint.New("no previous input device")
	}
	if m.decoder == nil {
		return errint.New("no previous decoder")
	}

	if err := m.decoder.Close(); err != nil {
		return err
	}

	if err := m.input.Close(); err != nil {
		return err
	}
	m.input, m.derivedTableName = in, derivedTableName

	return m.decoder.Init(m.input)
}

func (m *Machine) setDecoder(d format.Decoder) error {
	if d == nil {
		return errint.New("no decoder specified")
	}
	if m.decoder == nil {
		return errint.New("no previous decoder")
	}
	if m.input == nil {
		return errint.New("no previous input device")
	}

	if err := m.decoder.Close(); err != nil {
		return err
	}
	m.decoder = d

	return m.decoder.Init(m.input)
}

func (m *Machine) setEncoder(e format.Encoder) error {
	if e == nil {
		return errint.New("no encoder specified")
	}
	if m.encoder == nil {
		return errint.New("no previous encoder")
	}
	if m.output == nil {
		return errint.New("no previous output device")
	}

	if err := m.encoder.Close(); err != nil {
		return err
	}
	m.encoder = e

	return m.encoder.Init(m.output)
}

//exec q.
func (m *Machine) exec(q string) error {
	s, err := m.conn.Prepare(q)
	if err != nil {
		return err
	}
	defer s.Close()

	return s.Exec()
}

func (m *Machine) savepoint() error {
	return m.savepointStmt.Exec()
}

func (m *Machine) release() error {
	return m.releaseStmt.Exec()
}
<commit_msg>needed to set derivedTableName in factory<commit_after>package virt

import (
	"github.com/jimmyfrasche/etlite/internal/device"
	"github.com/jimmyfrasche/etlite/internal/device/std"
	"github.com/jimmyfrasche/etlite/internal/driver"
	"github.com/jimmyfrasche/etlite/internal/format"
	"github.com/jimmyfrasche/etlite/internal/format/rawfmt"
	"github.com/jimmyfrasche/etlite/internal/internal/eol"
	"github.com/jimmyfrasche/etlite/internal/internal/errint"
	"github.com/jimmyfrasche/etlite/internal/virt/internal/sysdb"
)

//Machine represents an execution context for a script.
type Machine struct {
	name    string
	conn    *driver.Conn
	output  device.Writer
	input   device.Reader
	encoder format.Encoder
	decoder format.Decoder

	sys                        *sysdb.Sysdb
	savepointStmt, releaseStmt *driver.Stmt

	eframe, derivedTableName string
}

//New creates and prepares an execution context.
func New(db string, args, env []string) (*Machine, error) {
	if db == "" {
		db = ":memory:"
	}
	c, err := driver.Open(db)
	if err != nil {
		return nil, err
	}
	m := &Machine{
		name:   db,
		conn:   c,
		output: std.Out,
		input:  std.In,
		encoder: &rawfmt.Encoder{
			UseCRLF:  eol.Default,
			NoHeader: true,
		},
		decoder: &rawfmt.Decoder{
			UseCRLF:  eol.Default,
			NoHeader: true,
		},
		derivedTableName: "[-]",
	}
	//Init default dec/enc
	if err := m.decoder.Init(m.input); err != nil {
		return nil, err
	}
	if err := m.encoder.Init(m.output); err != nil {
		return nil, err
	}

	m.sys, err = sysdb.New(m.conn, args, env)
	if err != nil {
		return nil, err
	}

	m.savepointStmt, err = m.conn.Prepare(`SAVEPOINT [1]`)
	if err != nil {
		return nil, errint.Wrap(err)
	}
	m.releaseStmt, err = m.conn.Prepare(`RELEASE SAVEPOINT [1]`)
	if err != nil {
		return nil, errint.Wrap(err)
	}

	return m, nil
}

//Close flushes and closes output and cleans up
//all tracked resources associated with the context.
//It does not track resources allocated by Instructions:
//that is the responsibility of an individual Instruction.
func (m *Machine) Close() (errs []error) {
	err := func(err error) {
		if err != nil {
			errs = append(errs, err)
		}
	}
	o := m.output
	err(o.Flush())
	err(o.Close())
	err(m.input.Close())
	err(m.encoder.Close())
	err(m.decoder.Close())
	err(m.sys.Close())
	err(m.savepointStmt.Close())
	err(m.releaseStmt.Close())
	err(m.conn.Close())
	return
}

//Name reports the name of the main database.
func (m *Machine) Name() string {
	return m.name
}

func (m *Machine) setOutput(o device.Writer) error {
	if o == nil {
		return errint.New("no output device specified")
	}
	if m.output == nil {
		return errint.New("no previous output device")
	}
	if m.encoder == nil {
		return errint.New("no previous decoder")
	}

	if err := m.encoder.Close(); err != nil {
		return err
	}

	if err := m.output.Close(); err != nil {
		return err
	}
	m.output = o

	return m.encoder.Init(m.output)
}

func (m *Machine) setInput(in device.Reader, derivedTableName string) error {
	if in == nil {
		return errint.New("no input device specified")
	}
	if m.input == nil {
		return errint.New("no previous input device")
	}
	if m.decoder == nil {
		return errint.New("no previous decoder")
	}

	if err := m.decoder.Close(); err != nil {
		return err
	}

	if err := m.input.Close(); err != nil {
		return err
	}
	m.input, m.derivedTableName = in, derivedTableName

	return m.decoder.Init(m.input)
}

func (m *Machine) setDecoder(d format.Decoder) error {
	if d == nil {
		return errint.New("no decoder specified")
	}
	if m.decoder == nil {
		return errint.New("no previous decoder")
	}
	if m.input == nil {
		return errint.New("no previous input device")
	}

	if err := m.decoder.Close(); err != nil {
		return err
	}
	m.decoder = d

	return m.decoder.Init(m.input)
}

func (m *Machine) setEncoder(e format.Encoder) error {
	if e == nil {
		return errint.New("no encoder specified")
	}
	if m.encoder == nil {
		return errint.New("no previous encoder")
	}
	if m.output == nil {
		return errint.New("no previous output device")
	}

	if err := m.encoder.Close(); err != nil {
		return err
	}
	m.encoder = e

	return m.encoder.Init(m.output)
}

//exec q.
func (m *Machine) exec(q string) error {
	s, err := m.conn.Prepare(q)
	if err != nil {
		return err
	}
	defer s.Close()

	return s.Exec()
}

func (m *Machine) savepoint() error {
	return m.savepointStmt.Exec()
}

func (m *Machine) release() error {
	return m.releaseStmt.Exec()
}
<|endoftext|>


<commit_before>package eveConsumer

import (
	"errors"
	"fmt"
	"time"

	"github.com/antihax/evedata/models"
	"github.com/antihax/goesi"
	"github.com/garyburd/redigo/redis"
)

func init() {
	addConsumer("entities", entitiesConsumer, "EVEDATA_entityQueue")
	addConsumer("entities", charSearchConsumer, "EVEDATA_charSearchQueue")
	addTrigger("entities", entitiesTrigger)
}

// At the public rate limit, we can obtain 540,000 entities an hour.
// Recursion will be limited to once an day with expiration of entities at five days.

// Check if we need to update any entity information (character, corporation, alliance)
func entitiesTrigger(c *EVEConsumer) (bool, error) {
	err := c.entitiesFromCREST()
	if err != nil {
		return false, err
	}

	err = c.entitiesUpdate()
	return true, err
}

func charSearchConsumer(c *EVEConsumer, redisPtr *redis.Conn) (bool, error) {
	r := *redisPtr
	ret, err := r.Do("SPOP", "EVEDATA_charSearchQueue")
	if err != nil {
		return false, err
	} else if ret == nil {
		return false, nil
	}
	v, err := redis.String(ret, err)
	if err != nil {
		return false, err
	}

	if !goesi.ValidCharacterName(v) {
		return false, errors.New(fmt.Sprintf("Invalid Character Name: %s", v))
	}

	// Figure out if we know this person already
	id, err := models.GetCharacterIDByName(v)
	if err != nil {
		return true, err
	}

	// We don't know this person... lets go looking.

	if id == 0 {
		search, _, err := c.ctx.ESI.V2.SearchApi.GetSearch([]string{"character"}, v, map[string]interface{}{"strict": true})
		if err != nil {
			return true, err
		}
		if len(search.Character) > 0 {
			redis := c.ctx.Cache.Get()
			for _, nid := range search.Character {
				EntityAddToQueue(nid, &redis)
			}
			redis.Close()
		}
	} else { // add the character to the queue so we get latest data.
		redis := c.ctx.Cache.Get()
		EntityAddToQueue((int32)(id), &redis)
		redis.Close()
	}

	return true, err
}

func entitiesConsumer(c *EVEConsumer, redisPtr *redis.Conn) (bool, error) {
	r := *redisPtr
	ret, err := r.Do("SPOP", "EVEDATA_entityQueue")
	if err != nil {
		return false, err
	} else if ret == nil {
		return false, nil
	}
	v, err := redis.Int(ret, err)
	if err != nil {
		return false, err
	}

	// Skip this entity if we have touched it recently
	key := "EVEDATA_entity:" + fmt.Sprintf("%d\n", v)
	i, err := redis.Bool(r.Do("EXISTS", key))
	if err != nil || i == true {
		return false, err
	}

	err = c.entityGetAndSave((int32)(v))
	if err != nil {
		return false, err
	}
	return true, err
}

// update any old entities
func (c *EVEConsumer) entitiesUpdate() error {
	entities, err := c.ctx.Db.Query(
		`SELECT allianceid AS id, crestRef, cacheUntil FROM evedata.alliances A
			INNER JOIN evedata.crestID C ON A.allianceID = C.id
						WHERE cacheUntil < UTC_TIMESTAMP()
			UNION
			SELECT corporationid AS id, crestRef, cacheUntil FROM evedata.corporations A
			INNER JOIN evedata.crestID C ON A.corporationID = C.id
						WHERE cacheUntil < UTC_TIMESTAMP()
			UNION
			(SELECT characterID AS id, crestRef, cacheUntil FROM evedata.characters A
			INNER JOIN evedata.crestID C ON A.characterID = C.id
						WHERE cacheUntil < UTC_TIMESTAMP())

            ORDER BY cacheUntil ASC`)
	if err != nil {
		return err
	}

	r := c.ctx.Cache.Get()
	defer r.Close()

	// Loop the entities
	for entities.Next() {
		var (
			id      int32
			href    string
			nothing string
		)

		err = entities.Scan(&id, &href, &nothing)
		if err != nil {
			return err
		}

		// Recursively update expired information
		if err = EntityAddToQueue(id, &r); err != nil {
			return err
		}

	}
	entities.Close()

	return nil
}

// Collect entity information for new alliances
func (c *EVEConsumer) entitiesFromCREST() error {

	nextCheck, _, err := models.GetServiceState("alliances")
	if err != nil {
		return err
	} else if nextCheck.After(time.Now().UTC()) {
		return nil
	}

	ids, res, err := c.ctx.ESI.V1.AllianceApi.GetAlliances(nil)
	if err != nil {
		return err
	}

	// Update state so we dont have two polling at once.
	err = models.SetServiceState("alliances", goesi.CacheExpires(res).UTC(), 1)
	if err != nil {
		return err
	}

	redis := c.ctx.Cache.Get()
	defer redis.Close()
	// Throw them into the queue
	for _, allianceID := range ids {
		if err = EntityAddToQueue(allianceID, &redis); err != nil {
			return err
		}
	}

	return nil
}

func CharSearchAddToQueue(charList []interface{}, redisPtr *redis.Conn) {
	r := *redisPtr

	for _, name := range charList {
		if goesi.ValidCharacterName(name.(string)) {
			// Add the search to the queue
			r.Send("SADD", "EVEDATA_charSearchQueue", name.(string))
		}
	}
	r.Flush()
}

func EntityAddToQueue(id int32, r *redis.Conn) error {
	red := *r
	// Skip this entity if we have touched it recently
	key := "EVEDATA_entity:" + fmt.Sprintf("%d\n", id)
	i, err := redis.Bool(red.Do("EXISTS", key))
	if err != nil || i == true {
		return err
	}

	// Add the entity to the queue
	_, err = red.Do("SADD", "EVEDATA_entityQueue", id)
	return err
}

// Say we touched the entity and expire after one day
func (c *EVEConsumer) entitySetKnown(id int32) error {
	key := "EVEDATA_entity:" + fmt.Sprintf("%d\n", id)
	r := c.ctx.Cache.Get()
	defer r.Close()
	r.Do("SETEX", key, 3600, true)
	return nil
}

// [TODO] Rewrite this as ESI matures
// [TODO] bulk pull IDs
func (c *EVEConsumer) entityGetAndSave(id int32) error {
	entity, _, err := c.ctx.ESI.V2.UniverseApi.PostUniverseNames([]int32{id}, nil)
	if err != nil {
		return err
	}

	for _, e := range entity {
		h := "https://crest-tq.eveonline.com/" + fmt.Sprintf("%ss/%d/", e.Category, id)
		if e.Category == "alliance" {
			err = c.updateAlliance(e.Id)
		} else if e.Category == "corporation" {
			err = c.updateCorporation(e.Id)
		} else if e.Category == "character" {
			err = c.updateCharacter(e.Id)
		}

		if err != nil {
			return err
		}
		err = models.AddCRESTRef(((int64)(e.Id)), h)
		if err != nil {
			return err
		}
	}
	return err
}

func (c *EVEConsumer) updateAlliance(id int32) error {
	a, _, err := c.ctx.ESI.V2.AllianceApi.GetAlliancesAllianceId(id, nil)
	if err != nil {
		return errors.New(fmt.Sprintf("%s with alliance id %d", err, id))
	}

	corps, _, err := c.ctx.ESI.V1.AllianceApi.GetAlliancesAllianceIdCorporations(id, nil)
	if err != nil {
		return errors.New(fmt.Sprintf("%s with alliance id %d", err, id))
	}

	err = models.UpdateAlliance(id, a.AllianceName, len(corps), a.Ticker, a.ExecutorCorp,
		a.DateFounded, time.Now().UTC().Add(time.Hour*24))
	if err != nil {
		return errors.New(fmt.Sprintf("%s with alliance id %d", err, id))
	}

	redis := c.ctx.Cache.Get()
	defer redis.Close()
	for _, corp := range corps {
		err = EntityAddToQueue(corp, &redis)
		if err != nil {
			return errors.New(fmt.Sprintf("%s with alliance id %d", err, id))
		}
	}

	return nil
}

func (c *EVEConsumer) updateCorporation(id int32) error {
	a, _, err := c.ctx.ESI.V3.CorporationApi.GetCorporationsCorporationId(id, nil)
	if err != nil {
		return errors.New(fmt.Sprintf("%s with corporation id %d", err, id))
	}
	factionID := goesi.FactionNameToID(a.Faction)
	err = models.UpdateCorporation(id, a.CorporationName, a.Ticker, a.CeoId,
		a.CorporationDescription, a.AllianceId, factionID, a.Url, a.MemberCount, time.Now().UTC().Add(time.Hour*24))
	if err != nil {
		return errors.New(fmt.Sprintf("%s with corporation id %d", err, id))
	}
	if a.CeoId > 1 {
		redis := c.ctx.Cache.Get()
		defer redis.Close()
		err = EntityAddToQueue((int32)(a.CeoId), &redis)
		if err != nil {
			return errors.New(fmt.Sprintf("%s with corporation id %d", err, id))
		}
	}
	return nil
}

func (c *EVEConsumer) updateCharacter(id int32) error {
	if id < 90000000 {
		return nil
	}
	a, _, err := c.ctx.ESI.V4.CharacterApi.GetCharactersCharacterId(id, nil)
	if err != nil {
		return errors.New(fmt.Sprintf("%s with character id %d", err, id))
	}
	err = models.UpdateCharacter(id, a.Name, a.BloodlineId, a.AncestryId, a.CorporationId, a.AllianceId, a.RaceId, a.Gender, a.SecurityStatus, time.Now().UTC().Add(time.Hour*24))
	if err != nil {
		return errors.New(fmt.Sprintf("%s with character id %d", err, id))
	}

	return nil
}
<commit_msg>Don't update dead corps and characters<commit_after>package eveConsumer

import (
	"errors"
	"fmt"
	"time"

	"github.com/antihax/evedata/models"
	"github.com/antihax/goesi"
	"github.com/garyburd/redigo/redis"
)

func init() {
	addConsumer("entities", entitiesConsumer, "EVEDATA_entityQueue")
	addConsumer("entities", charSearchConsumer, "EVEDATA_charSearchQueue")
	addTrigger("entities", entitiesTrigger)
}

// At the public rate limit, we can obtain 540,000 entities an hour.
// Recursion will be limited to once an day with expiration of entities at five days.

// Check if we need to update any entity information (character, corporation, alliance)
func entitiesTrigger(c *EVEConsumer) (bool, error) {
	err := c.entitiesFromCREST()
	if err != nil {
		return false, err
	}

	err = c.entitiesUpdate()
	return true, err
}

func charSearchConsumer(c *EVEConsumer, redisPtr *redis.Conn) (bool, error) {
	r := *redisPtr
	ret, err := r.Do("SPOP", "EVEDATA_charSearchQueue")
	if err != nil {
		return false, err
	} else if ret == nil {
		return false, nil
	}
	v, err := redis.String(ret, err)
	if err != nil {
		return false, err
	}

	if !goesi.ValidCharacterName(v) {
		return false, errors.New(fmt.Sprintf("Invalid Character Name: %s", v))
	}

	// Figure out if we know this person already
	id, err := models.GetCharacterIDByName(v)
	if err != nil {
		return true, err
	}

	// We don't know this person... lets go looking.

	if id == 0 {
		search, _, err := c.ctx.ESI.V2.SearchApi.GetSearch([]string{"character"}, v, map[string]interface{}{"strict": true})
		if err != nil {
			return true, err
		}
		if len(search.Character) > 0 {
			redis := c.ctx.Cache.Get()
			for _, nid := range search.Character {
				EntityAddToQueue(nid, &redis)
			}
			redis.Close()
		}
	} else { // add the character to the queue so we get latest data.
		redis := c.ctx.Cache.Get()
		EntityAddToQueue((int32)(id), &redis)
		redis.Close()
	}

	return true, err
}

func entitiesConsumer(c *EVEConsumer, redisPtr *redis.Conn) (bool, error) {
	r := *redisPtr
	ret, err := r.Do("SPOP", "EVEDATA_entityQueue")
	if err != nil {
		return false, err
	} else if ret == nil {
		return false, nil
	}
	v, err := redis.Int(ret, err)
	if err != nil {
		return false, err
	}

	// Skip this entity if we have touched it recently
	key := "EVEDATA_entity:" + fmt.Sprintf("%d\n", v)
	i, err := redis.Bool(r.Do("EXISTS", key))
	if err != nil || i == true {
		return false, err
	}

	err = c.entityGetAndSave((int32)(v))
	if err != nil {
		return false, err
	}
	return true, err
}

// update any old entities
func (c *EVEConsumer) entitiesUpdate() error {
	entities, err := c.ctx.Db.Query(
		`SELECT allianceid AS id, crestRef, cacheUntil FROM evedata.alliances A
			INNER JOIN evedata.crestID C ON A.allianceID = C.id
						WHERE cacheUntil < UTC_TIMESTAMP()
			UNION
			SELECT corporationid AS id, crestRef, cacheUntil FROM evedata.corporations A
			INNER JOIN evedata.crestID C ON A.corporationID = C.id
						WHERE cacheUntil < UTC_TIMESTAMP() AND memberCount > 0
			UNION
			(SELECT characterID AS id, crestRef, cacheUntil FROM evedata.characters A
			INNER JOIN evedata.crestID C ON A.characterID = C.id
						WHERE cacheUntil < UTC_TIMESTAMP() AND corporationID != 1000001)

            ORDER BY cacheUntil ASC`)
	if err != nil {
		return err
	}

	r := c.ctx.Cache.Get()
	defer r.Close()

	// Loop the entities
	for entities.Next() {
		var (
			id      int32
			href    string
			nothing string
		)

		err = entities.Scan(&id, &href, &nothing)
		if err != nil {
			return err
		}

		// Recursively update expired information
		if err = EntityAddToQueue(id, &r); err != nil {
			return err
		}

	}
	entities.Close()

	return nil
}

// Collect entity information for new alliances
func (c *EVEConsumer) entitiesFromCREST() error {

	nextCheck, _, err := models.GetServiceState("alliances")
	if err != nil {
		return err
	} else if nextCheck.After(time.Now().UTC()) {
		return nil
	}

	ids, res, err := c.ctx.ESI.V1.AllianceApi.GetAlliances(nil)
	if err != nil {
		return err
	}

	// Update state so we dont have two polling at once.
	err = models.SetServiceState("alliances", goesi.CacheExpires(res).UTC(), 1)
	if err != nil {
		return err
	}

	redis := c.ctx.Cache.Get()
	defer redis.Close()
	// Throw them into the queue
	for _, allianceID := range ids {
		if err = EntityAddToQueue(allianceID, &redis); err != nil {
			return err
		}
	}

	return nil
}

func CharSearchAddToQueue(charList []interface{}, redisPtr *redis.Conn) {
	r := *redisPtr

	for _, name := range charList {
		if goesi.ValidCharacterName(name.(string)) {
			// Add the search to the queue
			r.Send("SADD", "EVEDATA_charSearchQueue", name.(string))
		}
	}
	r.Flush()
}

func EntityAddToQueue(id int32, r *redis.Conn) error {
	red := *r
	// Skip this entity if we have touched it recently
	key := "EVEDATA_entity:" + fmt.Sprintf("%d\n", id)
	i, err := redis.Bool(red.Do("EXISTS", key))
	if err != nil || i == true {
		return err
	}

	// Add the entity to the queue
	_, err = red.Do("SADD", "EVEDATA_entityQueue", id)
	return err
}

// Say we touched the entity and expire after one day
func (c *EVEConsumer) entitySetKnown(id int32) error {
	key := "EVEDATA_entity:" + fmt.Sprintf("%d\n", id)
	r := c.ctx.Cache.Get()
	defer r.Close()
	r.Do("SETEX", key, 3600, true)
	return nil
}

// [TODO] Rewrite this as ESI matures
// [TODO] bulk pull IDs
func (c *EVEConsumer) entityGetAndSave(id int32) error {
	entity, _, err := c.ctx.ESI.V2.UniverseApi.PostUniverseNames([]int32{id}, nil)
	if err != nil {
		return err
	}

	for _, e := range entity {
		h := "https://crest-tq.eveonline.com/" + fmt.Sprintf("%ss/%d/", e.Category, id)
		if e.Category == "alliance" {
			err = c.updateAlliance(e.Id)
		} else if e.Category == "corporation" {
			err = c.updateCorporation(e.Id)
		} else if e.Category == "character" {
			err = c.updateCharacter(e.Id)
		}

		if err != nil {
			return err
		}
		err = models.AddCRESTRef(((int64)(e.Id)), h)
		if err != nil {
			return err
		}
	}
	return err
}

func (c *EVEConsumer) updateAlliance(id int32) error {
	a, _, err := c.ctx.ESI.V2.AllianceApi.GetAlliancesAllianceId(id, nil)
	if err != nil {
		return errors.New(fmt.Sprintf("%s with alliance id %d", err, id))
	}

	corps, _, err := c.ctx.ESI.V1.AllianceApi.GetAlliancesAllianceIdCorporations(id, nil)
	if err != nil {
		return errors.New(fmt.Sprintf("%s with alliance id %d", err, id))
	}

	err = models.UpdateAlliance(id, a.AllianceName, len(corps), a.Ticker, a.ExecutorCorp,
		a.DateFounded, time.Now().UTC().Add(time.Hour*24))
	if err != nil {
		return errors.New(fmt.Sprintf("%s with alliance id %d", err, id))
	}

	redis := c.ctx.Cache.Get()
	defer redis.Close()
	for _, corp := range corps {
		err = EntityAddToQueue(corp, &redis)
		if err != nil {
			return errors.New(fmt.Sprintf("%s with alliance id %d", err, id))
		}
	}

	return nil
}

func (c *EVEConsumer) updateCorporation(id int32) error {
	a, _, err := c.ctx.ESI.V3.CorporationApi.GetCorporationsCorporationId(id, nil)
	if err != nil {
		return errors.New(fmt.Sprintf("%s with corporation id %d", err, id))
	}
	factionID := goesi.FactionNameToID(a.Faction)
	err = models.UpdateCorporation(id, a.CorporationName, a.Ticker, a.CeoId,
		a.CorporationDescription, a.AllianceId, factionID, a.Url, a.MemberCount, time.Now().UTC().Add(time.Hour*24))
	if err != nil {
		return errors.New(fmt.Sprintf("%s with corporation id %d", err, id))
	}
	if a.CeoId > 1 {
		redis := c.ctx.Cache.Get()
		defer redis.Close()
		err = EntityAddToQueue((int32)(a.CeoId), &redis)
		if err != nil {
			return errors.New(fmt.Sprintf("%s with corporation id %d", err, id))
		}
	}
	return nil
}

func (c *EVEConsumer) updateCharacter(id int32) error {
	if id < 90000000 {
		return nil
	}
	a, _, err := c.ctx.ESI.V4.CharacterApi.GetCharactersCharacterId(id, nil)
	if err != nil {
		return errors.New(fmt.Sprintf("%s with character id %d", err, id))
	}
	err = models.UpdateCharacter(id, a.Name, a.BloodlineId, a.AncestryId, a.CorporationId, a.AllianceId, a.RaceId, a.Gender, a.SecurityStatus, time.Now().UTC().Add(time.Hour*24))
	if err != nil {
		return errors.New(fmt.Sprintf("%s with character id %d", err, id))
	}

	return nil
}
<|endoftext|>


<commit_before>package cobra

import (
	"bytes"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/spf13/pflag"
)

const (
	BashCompFilenameExt     = "cobra_annotation_bash_completion_filename_extentions"
	BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
	BashCompSubdirsInDir    = "cobra_annotation_bash_completion_subdirs_in_dir"
)

func preamble(out *bytes.Buffer) {
	fmt.Fprintf(out, `#!/bin/bash

__debug()
{
    if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
        echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
    fi
}

# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function.
__my_init_completion()
{
    COMPREPLY=()
    _get_comp_words_by_ref cur prev words cword
}

__index_of_word()
{
    local w word=$1
    shift
    index=0
    for w in "$@"; do
        [[ $w = "$word" ]] && return
        index=$((index+1))
    done
    index=-1
}

__contains_word()
{
    local w word=$1; shift
    for w in "$@"; do
        [[ $w = "$word" ]] && return
    done
    return 1
}

__handle_reply()
{
    __debug "${FUNCNAME}"
    case $cur in
        -*)
            if builtin compopt > /dev/null 2>&1; then
              compopt -o nospace
            fi
            local allflags
            if [ ${#must_have_one_flag[@]} -ne 0 ]; then
                allflags=("${must_have_one_flag[@]}")
            else
                allflags=("${flags[*]} ${two_word_flags[*]}")
            fi
            COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
            if builtin compopt > /dev/null 2>&1; then
              [[ $COMPREPLY == *= ]] || compopt +o nospace
            fi
            return 0;
            ;;
    esac

    # check if we are handling a flag with special work handling
    local index
    __index_of_word "${prev}" "${flags_with_completion[@]}"
    if [[ ${index} -ge 0 ]]; then
        ${flags_completion[${index}]}
        return
    fi

    # we are parsing a flag and don't have a special handler, no completion
    if [[ ${cur} != "${words[cword]}" ]]; then
        return
    fi

    local completions
    if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
        completions=("${must_have_one_flag[@]}")
    elif [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
        completions=("${must_have_one_noun[@]}")
    else
        completions=("${commands[@]}")
    fi
    COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )

    if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
        declare -F __custom_func >/dev/null && __custom_func
    fi
}

# The arguments should be in the form "ext1|ext2|extn"
__handle_filename_extension_flag()
{
    local ext="$1"
    _filedir "@(${ext})"
}

__handle_subdirs_in_dir_flag()
{
    local dir="$1"
    pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
}

__handle_flag()
{
    __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"

    # if a command required a flag, and we found it, unset must_have_one_flag()
    local flagname=${words[c]}
    # if the word contained an =
    if [[ ${words[c]} == *"="* ]]; then
        flagname=${flagname%%=*} # strip everything after the =
        flagname="${flagname}=" # but put the = back
    fi
    __debug "${FUNCNAME}: looking for ${flagname}"
    if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then
        must_have_one_flag=()
    fi

    # skip the argument to a two word flag
    if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
        c=$((c+1))
        # if we are looking for a flags value, don't show commands
        if [[ $c -eq $cword ]]; then
            commands=()
        fi
    fi

    # skip the flag itself
    c=$((c+1))

}

__handle_noun()
{
    __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"

    if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
        must_have_one_noun=()
    fi

    nouns+=("${words[c]}")
    c=$((c+1))
}

__handle_command()
{
    __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"

    local next_command
    if [[ -n ${last_command} ]]; then
        next_command="_${last_command}_${words[c]}"
    else
        next_command="_${words[c]}"
    fi
    c=$((c+1))
    __debug "${FUNCNAME}: looking for ${next_command}"
    declare -F $next_command >/dev/null && $next_command
}

__handle_word()
{
    if [[ $c -ge $cword ]]; then
        __handle_reply
	return
    fi
    __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"
    if [[ "${words[c]}" == -* ]]; then
	__handle_flag
    elif __contains_word "${words[c]}" "${commands[@]}"; then
        __handle_command
    else
        __handle_noun
    fi
    __handle_word
}

`)
}

func postscript(out *bytes.Buffer, name string) {
	fmt.Fprintf(out, "__start_%s()\n", name)
	fmt.Fprintf(out, `{
    local cur prev words cword
    if declare -F _init_completion >/dev/null 2>&1; then
        _init_completion -s || return
    else
        __my_init_completion || return
    fi

    local c=0
    local flags=()
    local two_word_flags=()
    local flags_with_completion=()
    local flags_completion=()
    local commands=("%s")
    local must_have_one_flag=()
    local must_have_one_noun=()
    local last_command
    local nouns=()

    __handle_word
}

`, name)
	fmt.Fprintf(out, `
		if builtin compopt > /dev/null 2>&1; then
			complete -F __start_%s %s
		else
			complete -o nospace -F __start_%s %s
		fi
		`, name, name, name, name)
	fmt.Fprintf(out, "# ex: ts=4 sw=4 et filetype=sh\n")
}

func writeCommands(cmd *Command, out *bytes.Buffer) {
	fmt.Fprintf(out, "    commands=()\n")
	for _, c := range cmd.Commands() {
		if !c.IsAvailableCommand() || c == cmd.helpCommand {
			continue
		}
		fmt.Fprintf(out, "    commands+=(%q)\n", c.Name())
	}
	fmt.Fprintf(out, "\n")
}

func writeFlagHandler(name string, annotations map[string][]string, out *bytes.Buffer) {
	for key, value := range annotations {
		switch key {
		case BashCompFilenameExt:
			fmt.Fprintf(out, "    flags_with_completion+=(%q)\n", name)

			if len(value) > 0 {
				ext := "__handle_filename_extension_flag " + strings.Join(value, "|")
				fmt.Fprintf(out, "    flags_completion+=(%q)\n", ext)
			} else {
				ext := "_filedir"
				fmt.Fprintf(out, "    flags_completion+=(%q)\n", ext)
			}
		case BashCompSubdirsInDir:
			fmt.Fprintf(out, "    flags_with_completion+=(%q)\n", name)

			if len(value) == 1 {
				ext := "__handle_subdirs_in_dir_flag " + value[0]
				fmt.Fprintf(out, "    flags_completion+=(%q)\n", ext)
			} else {
				ext := "_filedir -d"
				fmt.Fprintf(out, "    flags_completion+=(%q)\n", ext)
			}
		}
	}
}

func writeShortFlag(flag *pflag.Flag, out *bytes.Buffer) {
	b := (flag.Value.Type() == "bool")
	name := flag.Shorthand
	format := "    "
	if !b {
		format += "two_word_"
	}
	format += "flags+=(\"-%s\")\n"
	fmt.Fprintf(out, format, name)
	writeFlagHandler("-"+name, flag.Annotations, out)
}

func writeFlag(flag *pflag.Flag, out *bytes.Buffer) {
	b := (flag.Value.Type() == "bool")
	name := flag.Name
	format := "    flags+=(\"--%s"
	if !b {
		format += "="
	}
	format += "\")\n"
	fmt.Fprintf(out, format, name)
	writeFlagHandler("--"+name, flag.Annotations, out)
}

func writeFlags(cmd *Command, out *bytes.Buffer) {
	fmt.Fprintf(out, `    flags=()
    two_word_flags=()
    flags_with_completion=()
    flags_completion=()

`)
	cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
		writeFlag(flag, out)
		if len(flag.Shorthand) > 0 {
			writeShortFlag(flag, out)
		}
	})

	fmt.Fprintf(out, "\n")
}

func writeRequiredFlag(cmd *Command, out *bytes.Buffer) {
	fmt.Fprintf(out, "    must_have_one_flag=()\n")
	flags := cmd.NonInheritedFlags()
	flags.VisitAll(func(flag *pflag.Flag) {
		for key := range flag.Annotations {
			switch key {
			case BashCompOneRequiredFlag:
				format := "    must_have_one_flag+=(\"--%s"
				b := (flag.Value.Type() == "bool")
				if !b {
					format += "="
				}
				format += "\")\n"
				fmt.Fprintf(out, format, flag.Name)

				if len(flag.Shorthand) > 0 {
					fmt.Fprintf(out, "    must_have_one_flag+=(\"-%s\")\n", flag.Shorthand)
				}
			}
		}
	})
}

func writeRequiredNoun(cmd *Command, out *bytes.Buffer) {
	fmt.Fprintf(out, "    must_have_one_noun=()\n")
	sort.Sort(sort.StringSlice(cmd.ValidArgs))
	for _, value := range cmd.ValidArgs {
		fmt.Fprintf(out, "    must_have_one_noun+=(%q)\n", value)
	}
}

func gen(cmd *Command, out *bytes.Buffer) {
	for _, c := range cmd.Commands() {
		if !c.IsAvailableCommand() || c == cmd.helpCommand {
			continue
		}
		gen(c, out)
	}
	commandName := cmd.CommandPath()
	commandName = strings.Replace(commandName, " ", "_", -1)
	fmt.Fprintf(out, "_%s()\n{\n", commandName)
	fmt.Fprintf(out, "    last_command=%q\n", commandName)
	writeCommands(cmd, out)
	writeFlags(cmd, out)
	writeRequiredFlag(cmd, out)
	writeRequiredNoun(cmd, out)
	fmt.Fprintf(out, "}\n\n")
}

func (cmd *Command) GenBashCompletion(out *bytes.Buffer) {
	preamble(out)
	if len(cmd.BashCompletionFunction) > 0 {
		fmt.Fprintf(out, "%s\n", cmd.BashCompletionFunction)
	}
	gen(cmd, out)
	postscript(out, cmd.Name())
}

func (cmd *Command) GenBashCompletionFile(filename string) error {
	out := new(bytes.Buffer)

	cmd.GenBashCompletion(out)

	outFile, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer outFile.Close()

	_, err = outFile.Write(out.Bytes())
	if err != nil {
		return err
	}
	return nil
}

// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
func (cmd *Command) MarkFlagRequired(name string) error {
	return MarkFlagRequired(cmd.Flags(), name)
}

// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists.
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
	return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
}

// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
	return MarkFlagFilename(cmd.Flags(), name, extensions...)
}

// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists.
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
	return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
}
<commit_msg>Persistent flags should also be used in completions<commit_after>package cobra

import (
	"bytes"
	"fmt"
	"os"
	"sort"
	"strings"

	"github.com/spf13/pflag"
)

const (
	BashCompFilenameExt     = "cobra_annotation_bash_completion_filename_extentions"
	BashCompOneRequiredFlag = "cobra_annotation_bash_completion_one_required_flag"
	BashCompSubdirsInDir    = "cobra_annotation_bash_completion_subdirs_in_dir"
)

func preamble(out *bytes.Buffer) {
	fmt.Fprintf(out, `#!/bin/bash

__debug()
{
    if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then
        echo "$*" >> "${BASH_COMP_DEBUG_FILE}"
    fi
}

# Homebrew on Macs have version 1.3 of bash-completion which doesn't include
# _init_completion. This is a very minimal version of that function.
__my_init_completion()
{
    COMPREPLY=()
    _get_comp_words_by_ref cur prev words cword
}

__index_of_word()
{
    local w word=$1
    shift
    index=0
    for w in "$@"; do
        [[ $w = "$word" ]] && return
        index=$((index+1))
    done
    index=-1
}

__contains_word()
{
    local w word=$1; shift
    for w in "$@"; do
        [[ $w = "$word" ]] && return
    done
    return 1
}

__handle_reply()
{
    __debug "${FUNCNAME}"
    case $cur in
        -*)
            if builtin compopt > /dev/null 2>&1; then
              compopt -o nospace
            fi
            local allflags
            if [ ${#must_have_one_flag[@]} -ne 0 ]; then
                allflags=("${must_have_one_flag[@]}")
            else
                allflags=("${flags[*]} ${two_word_flags[*]}")
            fi
            COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") )
            if builtin compopt > /dev/null 2>&1; then
              [[ $COMPREPLY == *= ]] || compopt +o nospace
            fi
            return 0;
            ;;
    esac

    # check if we are handling a flag with special work handling
    local index
    __index_of_word "${prev}" "${flags_with_completion[@]}"
    if [[ ${index} -ge 0 ]]; then
        ${flags_completion[${index}]}
        return
    fi

    # we are parsing a flag and don't have a special handler, no completion
    if [[ ${cur} != "${words[cword]}" ]]; then
        return
    fi

    local completions
    if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then
        completions=("${must_have_one_flag[@]}")
    elif [[ ${#must_have_one_noun[@]} -ne 0 ]]; then
        completions=("${must_have_one_noun[@]}")
    else
        completions=("${commands[@]}")
    fi
    COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") )

    if [[ ${#COMPREPLY[@]} -eq 0 ]]; then
        declare -F __custom_func >/dev/null && __custom_func
    fi
}

# The arguments should be in the form "ext1|ext2|extn"
__handle_filename_extension_flag()
{
    local ext="$1"
    _filedir "@(${ext})"
}

__handle_subdirs_in_dir_flag()
{
    local dir="$1"
    pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1
}

__handle_flag()
{
    __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"

    # if a command required a flag, and we found it, unset must_have_one_flag()
    local flagname=${words[c]}
    # if the word contained an =
    if [[ ${words[c]} == *"="* ]]; then
        flagname=${flagname%%=*} # strip everything after the =
        flagname="${flagname}=" # but put the = back
    fi
    __debug "${FUNCNAME}: looking for ${flagname}"
    if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then
        must_have_one_flag=()
    fi

    # skip the argument to a two word flag
    if __contains_word "${words[c]}" "${two_word_flags[@]}"; then
        c=$((c+1))
        # if we are looking for a flags value, don't show commands
        if [[ $c -eq $cword ]]; then
            commands=()
        fi
    fi

    # skip the flag itself
    c=$((c+1))

}

__handle_noun()
{
    __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"

    if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then
        must_have_one_noun=()
    fi

    nouns+=("${words[c]}")
    c=$((c+1))
}

__handle_command()
{
    __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"

    local next_command
    if [[ -n ${last_command} ]]; then
        next_command="_${last_command}_${words[c]}"
    else
        next_command="_${words[c]}"
    fi
    c=$((c+1))
    __debug "${FUNCNAME}: looking for ${next_command}"
    declare -F $next_command >/dev/null && $next_command
}

__handle_word()
{
    if [[ $c -ge $cword ]]; then
        __handle_reply
	return
    fi
    __debug "${FUNCNAME}: c is $c words[c] is ${words[c]}"
    if [[ "${words[c]}" == -* ]]; then
	__handle_flag
    elif __contains_word "${words[c]}" "${commands[@]}"; then
        __handle_command
    else
        __handle_noun
    fi
    __handle_word
}

`)
}

func postscript(out *bytes.Buffer, name string) {
	fmt.Fprintf(out, "__start_%s()\n", name)
	fmt.Fprintf(out, `{
    local cur prev words cword
    if declare -F _init_completion >/dev/null 2>&1; then
        _init_completion -s || return
    else
        __my_init_completion || return
    fi

    local c=0
    local flags=()
    local two_word_flags=()
    local flags_with_completion=()
    local flags_completion=()
    local commands=("%s")
    local must_have_one_flag=()
    local must_have_one_noun=()
    local last_command
    local nouns=()

    __handle_word
}

`, name)
	fmt.Fprintf(out, `
		if builtin compopt > /dev/null 2>&1; then
			complete -F __start_%s %s
		else
			complete -o nospace -F __start_%s %s
		fi
		`, name, name, name, name)
	fmt.Fprintf(out, "# ex: ts=4 sw=4 et filetype=sh\n")
}

func writeCommands(cmd *Command, out *bytes.Buffer) {
	fmt.Fprintf(out, "    commands=()\n")
	for _, c := range cmd.Commands() {
		if !c.IsAvailableCommand() || c == cmd.helpCommand {
			continue
		}
		fmt.Fprintf(out, "    commands+=(%q)\n", c.Name())
	}
	fmt.Fprintf(out, "\n")
}

func writeFlagHandler(name string, annotations map[string][]string, out *bytes.Buffer) {
	for key, value := range annotations {
		switch key {
		case BashCompFilenameExt:
			fmt.Fprintf(out, "    flags_with_completion+=(%q)\n", name)

			if len(value) > 0 {
				ext := "__handle_filename_extension_flag " + strings.Join(value, "|")
				fmt.Fprintf(out, "    flags_completion+=(%q)\n", ext)
			} else {
				ext := "_filedir"
				fmt.Fprintf(out, "    flags_completion+=(%q)\n", ext)
			}
		case BashCompSubdirsInDir:
			fmt.Fprintf(out, "    flags_with_completion+=(%q)\n", name)

			if len(value) == 1 {
				ext := "__handle_subdirs_in_dir_flag " + value[0]
				fmt.Fprintf(out, "    flags_completion+=(%q)\n", ext)
			} else {
				ext := "_filedir -d"
				fmt.Fprintf(out, "    flags_completion+=(%q)\n", ext)
			}
		}
	}
}

func writeShortFlag(flag *pflag.Flag, out *bytes.Buffer) {
	b := (flag.Value.Type() == "bool")
	name := flag.Shorthand
	format := "    "
	if !b {
		format += "two_word_"
	}
	format += "flags+=(\"-%s\")\n"
	fmt.Fprintf(out, format, name)
	writeFlagHandler("-"+name, flag.Annotations, out)
}

func writeFlag(flag *pflag.Flag, out *bytes.Buffer) {
	b := (flag.Value.Type() == "bool")
	name := flag.Name
	format := "    flags+=(\"--%s"
	if !b {
		format += "="
	}
	format += "\")\n"
	fmt.Fprintf(out, format, name)
	writeFlagHandler("--"+name, flag.Annotations, out)
}

func writeFlags(cmd *Command, out *bytes.Buffer) {
	fmt.Fprintf(out, `    flags=()
    two_word_flags=()
    flags_with_completion=()
    flags_completion=()

`)
	cmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) {
		writeFlag(flag, out)
		if len(flag.Shorthand) > 0 {
			writeShortFlag(flag, out)
		}
	})
	cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
		writeFlag(flag, out)
		if len(flag.Shorthand) > 0 {
			writeShortFlag(flag, out)
		}
	})

	fmt.Fprintf(out, "\n")
}

func writeRequiredFlag(cmd *Command, out *bytes.Buffer) {
	fmt.Fprintf(out, "    must_have_one_flag=()\n")
	flags := cmd.NonInheritedFlags()
	flags.VisitAll(func(flag *pflag.Flag) {
		for key := range flag.Annotations {
			switch key {
			case BashCompOneRequiredFlag:
				format := "    must_have_one_flag+=(\"--%s"
				b := (flag.Value.Type() == "bool")
				if !b {
					format += "="
				}
				format += "\")\n"
				fmt.Fprintf(out, format, flag.Name)

				if len(flag.Shorthand) > 0 {
					fmt.Fprintf(out, "    must_have_one_flag+=(\"-%s\")\n", flag.Shorthand)
				}
			}
		}
	})
}

func writeRequiredNoun(cmd *Command, out *bytes.Buffer) {
	fmt.Fprintf(out, "    must_have_one_noun=()\n")
	sort.Sort(sort.StringSlice(cmd.ValidArgs))
	for _, value := range cmd.ValidArgs {
		fmt.Fprintf(out, "    must_have_one_noun+=(%q)\n", value)
	}
}

func gen(cmd *Command, out *bytes.Buffer) {
	for _, c := range cmd.Commands() {
		if !c.IsAvailableCommand() || c == cmd.helpCommand {
			continue
		}
		gen(c, out)
	}
	commandName := cmd.CommandPath()
	commandName = strings.Replace(commandName, " ", "_", -1)
	fmt.Fprintf(out, "_%s()\n{\n", commandName)
	fmt.Fprintf(out, "    last_command=%q\n", commandName)
	writeCommands(cmd, out)
	writeFlags(cmd, out)
	writeRequiredFlag(cmd, out)
	writeRequiredNoun(cmd, out)
	fmt.Fprintf(out, "}\n\n")
}

func (cmd *Command) GenBashCompletion(out *bytes.Buffer) {
	preamble(out)
	if len(cmd.BashCompletionFunction) > 0 {
		fmt.Fprintf(out, "%s\n", cmd.BashCompletionFunction)
	}
	gen(cmd, out)
	postscript(out, cmd.Name())
}

func (cmd *Command) GenBashCompletionFile(filename string) error {
	out := new(bytes.Buffer)

	cmd.GenBashCompletion(out)

	outFile, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer outFile.Close()

	_, err = outFile.Write(out.Bytes())
	if err != nil {
		return err
	}
	return nil
}

// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag, if it exists.
func (cmd *Command) MarkFlagRequired(name string) error {
	return MarkFlagRequired(cmd.Flags(), name)
}

// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag in the flag set, if it exists.
func MarkFlagRequired(flags *pflag.FlagSet, name string) error {
	return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"})
}

// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists.
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func (cmd *Command) MarkFlagFilename(name string, extensions ...string) error {
	return MarkFlagFilename(cmd.Flags(), name, extensions...)
}

// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists.
// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided.
func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error {
	return flags.SetAnnotation(name, BashCompFilenameExt, extensions)
}
<|endoftext|>