<commit_before>package gopheragent

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

// Browsers
const (
	Konqueror   = "konqueror"
	Chrome      = "chrome"
	Safari      = "safari"
	Opera       = "opera"
	PS3         = "ps3"
	PSP         = "psp"
	Firefox     = "firefox"
	Lotus       = "lotus"
	Netscape    = "netscape"
	SeaMonkey   = "seamonkey"
	Thunderbird = "thunderbird"
	Outlook     = "outlook"
	Evolution   = "evolution"
	IEMobile    = "iemobile"
	IE          = "ie"
)

// Engines
const (
	Webkit = "webkit"
	Khtml  = "khtml"
	Presto = "presto"
	Gecko  = "gecko"
	Msie   = "msie"
)

// Platforms
const (
	Windows      = "windows"
	Mac          = "macintosh"
	Linux        = "linux"
	Wii          = "wii"
	Playstation  = "playstation"
	Ipad         = "ipad"
	Ipod         = "ipod"
	Iphone       = "iphone"
	Android      = "android"
	Blackberry   = "blackberry"
	WindowsPhone = "windows_phone"
	Symbian      = "symbian"
)

// Unknown is returned when a result cannot be extracted
const Unknown = "unknown"

type regexpTest struct {
	Result  string
	Pattern *regexp.Regexp
	Expand  bool
}
type regexpTestChain struct {
	tests    []*regexpTest
	fallback string
}

var browsers,
	engines,
	oses,
	platforms regexpTestChain
var browserVersions map[string]*regexp.Regexp
var mobilePlatforms []string

// UserAgent provides methods for extracting UA details
type UserAgent struct {
	s,
	browser,
	engine,
	os,
	platform string
}

// New returns a UserAgent for the given UA string
func New(ua string) *UserAgent {

	result := UserAgent{
		s: strings.TrimSpace(ua),
	}

	return &result
}

// BrowserName returns the name of the browser from the user agent
func (ua *UserAgent) BrowserName() string {

	if ua.browser == "" {
		ua.browser = matchFirst(browsers, ua.s)
	}

	return ua.browser

}

// BrowserVersion returns the version of the browser from the user agent
func (ua *UserAgent) BrowserVersion() string {

	r, err := browserVersionRegexp(ua.BrowserName())
	if err == nil {
		matches := r.FindStringSubmatch(ua.s)

		if len(matches) > 1 {
			return matches[1]
		}
	}

	return ""
}

// Engine returns the rendering engine from the user agent
func (ua *UserAgent) Engine() string {

	if ua.engine == "" {
		ua.engine = matchFirst(engines, ua.s)
	}

	return ua.engine

}

// EngineVersion returns the version of the rendering engine used
func (ua *UserAgent) EngineVersion() string {

	engineVersion, err := regexp.Compile(`(?i:` + ua.Engine() + `[\/ ]([\d\w\.\-]+))`)

	if err == nil {
		matches := engineVersion.FindStringSubmatch(ua.s)

		if len(matches) > 1 {
			return matches[1]
		}

	}

	return ""

}

// OS returns the operating system from the user agent
func (ua *UserAgent) OS() string {

	if ua.os == "" {
		ua.os = matchFirst(oses, ua.s)
	}

	return ua.os

}

// Platform returns the platform from the user agent
func (ua *UserAgent) Platform() string {

	if ua.platform == "" {
		ua.platform = matchFirst(platforms, ua.s)
	}

	return ua.platform

}

// Mobile returns true if the user agent represents a mobile client
func (ua *UserAgent) Mobile() bool {

	platform := ua.Platform()

	for _, t := range mobilePlatforms {
		if t == platform {
			return true
		}
	}

	return ua.BrowserName() == PSP

}

func browserVersionRegexp(b string) (r *regexp.Regexp, err error) {

	r, ok := browserVersions[b]
	if !ok {
		r, err = regexp.Compile(
			`(?i:` + b + `[\/ ]([\d\w\.\-]+)`,
		)
	}

	return r, err
}

func matchFirst(tt regexpTestChain, ua string) string {

	for _, test := range tt.tests {
		if m := test.Pattern.FindStringSubmatch(ua); m != nil {

			// see if we need to expand the result
			if test.Expand && len(m) > 1 {
				submatches := m[1:]
				args := make([]interface{}, len(submatches))

				for i, v := range submatches {
					args[i] = interface{}(v)
				}

				return fmt.Sprintf(test.Result, args...)
			}

			return test.Result
		}
	}

	return tt.fallback
}

func newRegexpTest(result, pattern string, expand bool) *regexpTest {
	return &regexpTest{
		Result:  result,
		Pattern: regexp.MustCompile(pattern),
		Expand:  expand,
	}
}

func newSimpleTest(result, pattern string) *regexpTest {
	return newRegexpTest(result, pattern, false)
}

func init() {

	browsers = regexpTestChain{
		tests: []*regexpTest{
			newSimpleTest(Konqueror, `(?i:konqueror)`),
			newSimpleTest(Chrome, `(?i:chrome)`),
			newSimpleTest(Safari, `(?i:safari)`),
			newSimpleTest(IE, `(?i:msie)`),
			newSimpleTest(Opera, `(?i:opera)`),
			newSimpleTest(PS3, `(?i:playstation 3)`),
			newSimpleTest(PSP, `(?i:playstation portable)`),
			newSimpleTest(Firefox, `(?i:firefox)`),
			newSimpleTest(Lotus, `(?i:lotus.notes)`),
			newSimpleTest(Netscape, `(?i:netscape)`),
			newSimpleTest(SeaMonkey, `(?i:seamonkey)`),
			newSimpleTest(Thunderbird, `(?i:thunderbird)`),
			newSimpleTest(Outlook, `(?i:microsoft.outlook)`),
			newSimpleTest(Evolution, `(?i:evolution)`),
			newSimpleTest(IEMobile, `(?i:iemobile|windows phone)`),
		},
		fallback: Unknown,
	}

	browserVersions = map[string]*regexp.Regexp{
		Chrome: regexp.MustCompile(`(?i:chrome\/([\d\w\.\-]+))`),
		Safari: regexp.MustCompile(`(?i:version\/([\d\w\.\-]+))`),
		PS3:    regexp.MustCompile(`(?i:([\d\w\.\-]+)\)\s*$)`),
		PSP:    regexp.MustCompile(`(?i:([\d\w\.\-]+)\)?\s*$)`),
		Lotus:  regexp.MustCompile(`(?i:Lotus-Notes\/([\w.]+))`),
	}

	engines = regexpTestChain{
		tests: []*regexpTest{
			newSimpleTest(Webkit, `(?i:webkit)`),
			newSimpleTest(Khtml, `(?i:khtml)`),
			newSimpleTest(Konqueror, `(?i:konqueror)`),
			newSimpleTest(Chrome, `(?i:chrome)`),
			newSimpleTest(Presto, `(?i:presto)`),
			newSimpleTest(Gecko, `(?i:gecko)`),
			newSimpleTest(Unknown, `(?i:opera)`),
			newSimpleTest(Msie, `(?i:msie)`),
		},
		fallback: Unknown,
	}

	oses = regexpTestChain{
		tests: []*regexpTest{
			newSimpleTest("Windows Phone", `(?i:windows (ce|phone|mobile)( os)?)`),
			newSimpleTest("Windows Vista", `(?i:windows nt 6\.0)`),
			newSimpleTest("Windows 7", `(?i:windows nt 6\.\d+)`),
			newSimpleTest("Windows 2003", `(?i:windows nt 5\.2)`),
			newSimpleTest("Windows XP", `(?i:windows nt 5\.1)`),
			newSimpleTest("Windows 2000", `(?i:windows nt 5\.0)`),
			newSimpleTest("Windows", `(?i:windows)`),
			newRegexpTest("OS X %s.%s", `(?i:os x (\d+)[._](\d+))`, true),
			newSimpleTest("Linux", `(?i:linux)`),
			newSimpleTest("Wii", `(?i:wii)`),
			newSimpleTest("Playstation", `(?i:playstation 3)`),
			newSimpleTest("Playstation", `(?i:playstation portable)`),
			newRegexpTest("iPad OS %s.%s", `(?i:\(iPad.*os (\d+)[._](\d+))`, true),
			newRegexpTest("iPhone OS %s.%s", `(?i:\(iPhone.*os (\d+)[._](\d+))`, true),
			newSimpleTest("Symbian OS", `(?i:symbian(os)?)`),
		},
		fallback: "Unknown",
	}

	platforms = regexpTestChain{
		tests: []*regexpTest{
			newSimpleTest(WindowsPhone, `(?i:windows (ce|phone|mobile)( os)?)`),
			newSimpleTest(Windows, `(?i:windows)`),
			newSimpleTest(Mac, `(?i:macintosh)`),
			newSimpleTest(Android, `(?i:android)`),
			newSimpleTest(Blackberry, `(?i:blackberry)`),
			newSimpleTest(Linux, `(?i:linux)`),
			newSimpleTest(Wii, `(?i:wii)`),
			newSimpleTest(Playstation, `(?i:playstation)`),
			newSimpleTest(Ipad, `(?i:ipad)`),
			newSimpleTest(Ipod, `(?i:ipod)`),
			newSimpleTest(Iphone, `(?i:iphone)`),
			newSimpleTest(Symbian, `(?i:symbian(os)?)`),
		},
		fallback: Unknown,
	}

	mobilePlatforms = []string{
		Android,
		Blackberry,
		Ipad,
		Ipod,
		Iphone,
		Symbian,
		WindowsPhone,
	}
}
<commit_msg>Fix fallback browser version regexp<commit_after>package gopheragent

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

// Browsers
const (
	Konqueror   = "konqueror"
	Chrome      = "chrome"
	Safari      = "safari"
	Opera       = "opera"
	PS3         = "ps3"
	PSP         = "psp"
	Firefox     = "firefox"
	Lotus       = "lotus"
	Netscape    = "netscape"
	SeaMonkey   = "seamonkey"
	Thunderbird = "thunderbird"
	Outlook     = "outlook"
	Evolution   = "evolution"
	IEMobile    = "iemobile"
	IE          = "ie"
)

// Engines
const (
	Webkit = "webkit"
	Khtml  = "khtml"
	Presto = "presto"
	Gecko  = "gecko"
	Msie   = "msie"
)

// Platforms
const (
	Windows      = "windows"
	Mac          = "macintosh"
	Linux        = "linux"
	Wii          = "wii"
	Playstation  = "playstation"
	Ipad         = "ipad"
	Ipod         = "ipod"
	Iphone       = "iphone"
	Android      = "android"
	Blackberry   = "blackberry"
	WindowsPhone = "windows_phone"
	Symbian      = "symbian"
)

// Unknown is returned when a result cannot be extracted
const Unknown = "unknown"

type regexpTest struct {
	Result  string
	Pattern *regexp.Regexp
	Expand  bool
}
type regexpTestChain struct {
	tests    []*regexpTest
	fallback string
}

var browsers,
	engines,
	oses,
	platforms regexpTestChain
var browserVersions map[string]*regexp.Regexp
var mobilePlatforms []string

// UserAgent provides methods for extracting UA details
type UserAgent struct {
	s,
	browser,
	engine,
	os,
	platform string
}

// New returns a UserAgent for the given UA string
func New(ua string) *UserAgent {

	result := UserAgent{
		s: strings.TrimSpace(ua),
	}

	return &result
}

// BrowserName returns the name of the browser from the user agent
func (ua *UserAgent) BrowserName() string {

	if ua.browser == "" {
		ua.browser = matchFirst(browsers, ua.s)
	}

	return ua.browser

}

// BrowserVersion returns the version of the browser from the user agent
func (ua *UserAgent) BrowserVersion() string {

	r, err := browserVersionRegexp(ua.BrowserName())
	if err == nil {
		matches := r.FindStringSubmatch(ua.s)

		if len(matches) > 1 {
			return matches[1]
		}
	}

	return ""
}

// Engine returns the rendering engine from the user agent
func (ua *UserAgent) Engine() string {

	if ua.engine == "" {
		ua.engine = matchFirst(engines, ua.s)
	}

	return ua.engine

}

// EngineVersion returns the version of the rendering engine used
func (ua *UserAgent) EngineVersion() string {

	engineVersion, err := regexp.Compile(`(?i:` + ua.Engine() + `[\/ ]([\d\w\.\-]+))`)

	if err == nil {
		matches := engineVersion.FindStringSubmatch(ua.s)

		if len(matches) > 1 {
			return matches[1]
		}

	}

	return ""

}

// OS returns the operating system from the user agent
func (ua *UserAgent) OS() string {

	if ua.os == "" {
		ua.os = matchFirst(oses, ua.s)
	}

	return ua.os

}

// Platform returns the platform from the user agent
func (ua *UserAgent) Platform() string {

	if ua.platform == "" {
		ua.platform = matchFirst(platforms, ua.s)
	}

	return ua.platform

}

// Mobile returns true if the user agent represents a mobile client
func (ua *UserAgent) Mobile() bool {

	platform := ua.Platform()

	for _, t := range mobilePlatforms {
		if t == platform {
			return true
		}
	}

	return ua.BrowserName() == PSP

}

func browserVersionRegexp(b string) (r *regexp.Regexp, err error) {

	r, ok := browserVersions[b]
	if !ok {
		r, err = regexp.Compile(
			`(?i:` + b + `[\/ ]([\d\w\.\-]+))`,
		)
	}

	return r, err
}

func matchFirst(tt regexpTestChain, ua string) string {

	for _, test := range tt.tests {
		if m := test.Pattern.FindStringSubmatch(ua); m != nil {

			// see if we need to expand the result
			if test.Expand && len(m) > 1 {
				submatches := m[1:]
				args := make([]interface{}, len(submatches))

				for i, v := range submatches {
					args[i] = interface{}(v)
				}

				return fmt.Sprintf(test.Result, args...)
			}

			return test.Result
		}
	}

	return tt.fallback
}

func newRegexpTest(result, pattern string, expand bool) *regexpTest {
	return &regexpTest{
		Result:  result,
		Pattern: regexp.MustCompile(pattern),
		Expand:  expand,
	}
}

func newSimpleTest(result, pattern string) *regexpTest {
	return newRegexpTest(result, pattern, false)
}

func init() {

	browsers = regexpTestChain{
		tests: []*regexpTest{
			newSimpleTest(Konqueror, `(?i:konqueror)`),
			newSimpleTest(Chrome, `(?i:chrome)`),
			newSimpleTest(Safari, `(?i:safari)`),
			newSimpleTest(IE, `(?i:msie)`),
			newSimpleTest(Opera, `(?i:opera)`),
			newSimpleTest(PS3, `(?i:playstation 3)`),
			newSimpleTest(PSP, `(?i:playstation portable)`),
			newSimpleTest(Firefox, `(?i:firefox)`),
			newSimpleTest(Lotus, `(?i:lotus.notes)`),
			newSimpleTest(Netscape, `(?i:netscape)`),
			newSimpleTest(SeaMonkey, `(?i:seamonkey)`),
			newSimpleTest(Thunderbird, `(?i:thunderbird)`),
			newSimpleTest(Outlook, `(?i:microsoft.outlook)`),
			newSimpleTest(Evolution, `(?i:evolution)`),
			newSimpleTest(IEMobile, `(?i:iemobile|windows phone)`),
		},
		fallback: Unknown,
	}

	browserVersions = map[string]*regexp.Regexp{
		Chrome: regexp.MustCompile(`(?i:chrome\/([\d\w\.\-]+))`),
		Safari: regexp.MustCompile(`(?i:version\/([\d\w\.\-]+))`),
		PS3:    regexp.MustCompile(`(?i:([\d\w\.\-]+)\)\s*$)`),
		PSP:    regexp.MustCompile(`(?i:([\d\w\.\-]+)\)?\s*$)`),
		Lotus:  regexp.MustCompile(`(?i:Lotus-Notes\/([\w.]+))`),
	}

	engines = regexpTestChain{
		tests: []*regexpTest{
			newSimpleTest(Webkit, `(?i:webkit)`),
			newSimpleTest(Khtml, `(?i:khtml)`),
			newSimpleTest(Konqueror, `(?i:konqueror)`),
			newSimpleTest(Chrome, `(?i:chrome)`),
			newSimpleTest(Presto, `(?i:presto)`),
			newSimpleTest(Gecko, `(?i:gecko)`),
			newSimpleTest(Unknown, `(?i:opera)`),
			newSimpleTest(Msie, `(?i:msie)`),
		},
		fallback: Unknown,
	}

	oses = regexpTestChain{
		tests: []*regexpTest{
			newSimpleTest("Windows Phone", `(?i:windows (ce|phone|mobile)( os)?)`),
			newSimpleTest("Windows Vista", `(?i:windows nt 6\.0)`),
			newSimpleTest("Windows 7", `(?i:windows nt 6\.\d+)`),
			newSimpleTest("Windows 2003", `(?i:windows nt 5\.2)`),
			newSimpleTest("Windows XP", `(?i:windows nt 5\.1)`),
			newSimpleTest("Windows 2000", `(?i:windows nt 5\.0)`),
			newSimpleTest("Windows", `(?i:windows)`),
			newRegexpTest("OS X %s.%s", `(?i:os x (\d+)[._](\d+))`, true),
			newSimpleTest("Linux", `(?i:linux)`),
			newSimpleTest("Wii", `(?i:wii)`),
			newSimpleTest("Playstation", `(?i:playstation 3)`),
			newSimpleTest("Playstation", `(?i:playstation portable)`),
			newRegexpTest("iPad OS %s.%s", `(?i:\(iPad.*os (\d+)[._](\d+))`, true),
			newRegexpTest("iPhone OS %s.%s", `(?i:\(iPhone.*os (\d+)[._](\d+))`, true),
			newSimpleTest("Symbian OS", `(?i:symbian(os)?)`),
		},
		fallback: "Unknown",
	}

	platforms = regexpTestChain{
		tests: []*regexpTest{
			newSimpleTest(WindowsPhone, `(?i:windows (ce|phone|mobile)( os)?)`),
			newSimpleTest(Windows, `(?i:windows)`),
			newSimpleTest(Mac, `(?i:macintosh)`),
			newSimpleTest(Android, `(?i:android)`),
			newSimpleTest(Blackberry, `(?i:blackberry)`),
			newSimpleTest(Linux, `(?i:linux)`),
			newSimpleTest(Wii, `(?i:wii)`),
			newSimpleTest(Playstation, `(?i:playstation)`),
			newSimpleTest(Ipad, `(?i:ipad)`),
			newSimpleTest(Ipod, `(?i:ipod)`),
			newSimpleTest(Iphone, `(?i:iphone)`),
			newSimpleTest(Symbian, `(?i:symbian(os)?)`),
		},
		fallback: Unknown,
	}

	mobilePlatforms = []string{
		Android,
		Blackberry,
		Ipad,
		Ipod,
		Iphone,
		Symbian,
		WindowsPhone,
	}
}
<|endoftext|>


<commit_before>package main

// A Backlog represents a backlog of file paths that may be received while we're still running a command.
// There are a couple of different policies for how to handle this. If there are no {} (substitution
// sequences) in the command, then we only need to preserve one of the paths. If there is a {}, then we need
// to preserve each unique path in the backlog.
type Backlog interface {
	// Add a path to the backlog
	Add(path string)
	// Show what path should be processed next (without removing it from the backlog).
	Next() string
	// Remove the next path from the backlog and returns whether the backlog is now empty.
	RemoveOne() (empty bool)
}

type UnifiedBacklog string

func (b *UnifiedBacklog) Add(path string) {
	if b == nil {
		*b = UnifiedBacklog(path)
	}
}

func (b *UnifiedBacklog) Next() string {
	if b == nil {
		panic("Next() called on empty backlog")
	}
	return string(*b)
}

func (b *UnifiedBacklog) RemoveOne() bool {
	if b == nil {
		panic("RemoveOne() called on empty backlog")
	}
	b = nil
	return true
}

type UniqueFilesBacklog struct {
	empty bool
	next  string
	rest  map[string]struct{}
}

func (b *UniqueFilesBacklog) Add(path string) {
	if path == b.next {
		return
	}
	b.rest[path] = struct{}{}
	b.empty = false
}

func (b *UniqueFilesBacklog) Next() string {
	if b.empty {
		panic("Next() called on empty backlog")
	}
	return b.next
}

func (b *UniqueFilesBacklog) RemoveOne() bool {
	if b.empty {
		panic("RemoveOne() called on empty backlog")
	}
	if len(b.rest) == 0 {
		b.next = ""
		b.empty = true
		return true
	}
	for next := range b.rest {
		b.next = next
		break
	}
	delete(b.rest, b.next)
	return false
}
<commit_msg>Bugfix for backlog.<commit_after>package main

// A Backlog represents a backlog of file paths that may be received while we're still running a command.
// There are a couple of different policies for how to handle this. If there are no {} (substitution
// sequences) in the command, then we only need to preserve one of the paths. If there is a {}, then we need
// to preserve each unique path in the backlog.
type Backlog interface {
	// Add a path to the backlog
	Add(path string)
	// Show what path should be processed next (without removing it from the backlog).
	Next() string
	// Remove the next path from the backlog and returns whether the backlog is now empty.
	RemoveOne() (empty bool)
}

type UnifiedBacklog string

func (b *UnifiedBacklog) Add(path string) {
	if b == nil {
		*b = UnifiedBacklog(path)
	}
}

func (b *UnifiedBacklog) Next() string {
	if b == nil {
		panic("Next() called on empty backlog")
	}
	return string(*b)
}

func (b *UnifiedBacklog) RemoveOne() bool {
	if b == nil {
		panic("RemoveOne() called on empty backlog")
	}
	b = nil
	return true
}

type UniqueFilesBacklog struct {
	empty bool
	next  string
	rest  map[string]struct{}
}

func (b *UniqueFilesBacklog) Add(path string) {
	defer func() { b.empty = false }()
	if b.empty {
		b.next = path
		return
	}
	if path == b.next {
		return
	}
	b.rest[path] = struct{}{}
}

func (b *UniqueFilesBacklog) Next() string {
	if b.empty {
		panic("Next() called on empty backlog")
	}
	return b.next
}

func (b *UniqueFilesBacklog) RemoveOne() bool {
	if b.empty {
		panic("RemoveOne() called on empty backlog")
	}
	if len(b.rest) == 0 {
		b.next = ""
		b.empty = true
		return true
	}
	for next := range b.rest {
		b.next = next
		break
	}
	delete(b.rest, b.next)
	return false
}
<|endoftext|>


<commit_before>// Copyright (c) 2014, Kevin Walsh.  All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package util

import (
	"bytes"
	"fmt"
	"io"
	"reflect"
)

// PrintAsConfig prints s, a struct, using git-style config file syntax. Only
// the exported fields of s are printed, and only those fields that are either
// builtin types (int, string, etc.) or structs, or aliases of those. For nested
// structs, only builtin types are printed, all grouped under a "[section]"
// header.
func PrintAsGitConfig(out io.Writer, s interface{}, comment string) error {
	if reflect.ValueOf(s).Kind() != reflect.Struct {
		return fmt.Errorf("not a struct: %s", reflect.TypeOf(s))
	}
	fmt.Fprintf(out, "# %s\n", comment)
	return gitConfigDump(out, 0, "", s)
}

func gitConfigDump(out io.Writer, depth int, name string, s interface{}) error {
	v := reflect.ValueOf(s)
	if s == reflect.Zero(v.Type()).Interface() {
		return nil
	}
	switch v.Kind() {
	case reflect.Bool, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
		reflect.Float32, reflect.Float64, reflect.String:
		fmt.Fprintf(out, "%s = %v\n", name, v)
	case reflect.Struct:
		if depth > 1 {
			return fmt.Errorf("double-nested struct: %s", name)
		}
		section := new(bytes.Buffer)
		for i := 0; i < v.NumField(); i++ {
			typ := v.Type()
			err := gitConfigDump(section, depth+1, typ.Field(i).Name, v.Field(i).Interface())
			if err != nil {
				return err
			}
		}
		contents := section.String()
		if depth > 0 && len(contents) > 0 {
			fmt.Fprintf(out, "\n[%s]\n", name)
		}
		fmt.Fprint(out, contents)
	default:
		return fmt.Errorf("incompatable type: %v", v.Kind())
	}
	return nil
}
<commit_msg>Remove the final gcfg-based code.<commit_after><|endoftext|>


<commit_before>package socket

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/qiniu/log"

	"github.com/qiniu/logkit/conf"
	"github.com/qiniu/logkit/reader"
)

type setReadBufferer interface {
	SetReadBuffer(bytes int) error
}

type streamSocketReader struct {
	Listener net.Listener
	*Reader

	connections    map[string]net.Conn
	connectionsMtx sync.Mutex
}

func (ssr *streamSocketReader) listen() {
	ssr.connections = map[string]net.Conn{}

	defer func() {
		if atomic.CompareAndSwapInt32(&ssr.status, reader.StatusStopping, reader.StatusStopped) {
			close(ssr.ReadChan)
			close(ssr.errChan)
		}
	}()
	for {
		c, err := ssr.Listener.Accept()
		if err != nil {
			if !strings.HasSuffix(err.Error(), ": use of closed network connection") {
				log.Error(err)
			}
			break
		}

		ssr.connectionsMtx.Lock()
		if ssr.MaxConnections > 0 && len(ssr.connections) >= ssr.MaxConnections {
			ssr.connectionsMtx.Unlock()
			c.Close()
			continue
		}
		ssr.connections[c.RemoteAddr().String()] = c
		ssr.connectionsMtx.Unlock()

		if ssr.netproto == "tcp" || ssr.netproto == "tcp4" || ssr.netproto == "tcp6" {
			if err := ssr.setKeepAlive(c); err != nil {
				log.Error(fmt.Errorf("unable to configure keep alive (%s): %s", ssr.ServiceAddress, err))
			}
		}

		go ssr.read(c)
	}

	ssr.connectionsMtx.Lock()
	for _, c := range ssr.connections {
		c.Close()
	}
	ssr.connectionsMtx.Unlock()
}

func (ssr *streamSocketReader) setKeepAlive(c net.Conn) error {
	tcpc, ok := c.(*net.TCPConn)
	if !ok {
		return fmt.Errorf("cannot set keep alive on a %s socket", strings.SplitN(ssr.ServiceAddress, "://", 2)[0])
	}
	if ssr.KeepAlivePeriod == 0 {
		return tcpc.SetKeepAlive(false)
	}
	if err := tcpc.SetKeepAlive(true); err != nil {
		return err
	}
	return tcpc.SetKeepAlivePeriod(ssr.KeepAlivePeriod)
}

func (ssr *streamSocketReader) removeConnection(c net.Conn) {
	ssr.connectionsMtx.Lock()
	delete(ssr.connections, c.RemoteAddr().String())
	ssr.connectionsMtx.Unlock()
}

func (ssr *streamSocketReader) read(c net.Conn) {
	defer ssr.removeConnection(c)
	defer c.Close()

	scnr := bufio.NewScanner(c)
	for {
		if atomic.LoadInt32(&ssr.status) == reader.StatusStopped || atomic.LoadInt32(&ssr.status) == reader.StatusStopping {
			return
		}
		if ssr.ReadTimeout != 0 && ssr.ReadTimeout > 0 {
			c.SetReadDeadline(time.Now().Add(ssr.ReadTimeout))
		}
		if !scnr.Scan() {
			break
		}

		//double check
		if atomic.LoadInt32(&ssr.status) == reader.StatusStopped || atomic.LoadInt32(&ssr.status) == reader.StatusStopping {
			return
		}
		ssr.ReadChan <- scnr.Bytes()
	}

	if err := scnr.Err(); err != nil {
		if nErr, ok := err.(net.Error); ok && nErr.Timeout() {
			log.Errorf("streamSocketReader Timeout : %s", nErr)
		}
		if !strings.HasSuffix(err.Error(), ": use of closed network connection") {
			log.Error(err)
			//可能reader都已经close了，channel也关了，直接return
			return
		}
		if atomic.LoadInt32(&ssr.status) == reader.StatusStopped || atomic.LoadInt32(&ssr.status) == reader.StatusStopping {
			return
		}
		ssr.sendError(err)
	}
}

type packetSocketReader struct {
	PacketConn net.PacketConn
	*Reader
}

func (psr *packetSocketReader) listen() {
	buf := make([]byte, 64*1024) // 64kb - maximum size of IP packet

	defer func() {
		if atomic.CompareAndSwapInt32(&psr.status, reader.StatusStopping, reader.StatusStopped) {
			close(psr.ReadChan)
			close(psr.errChan)
		}
	}()

	for {
		if atomic.LoadInt32(&psr.status) == reader.StatusStopped || atomic.LoadInt32(&psr.status) == reader.StatusStopping {
			return
		}
		n, _, err := psr.PacketConn.ReadFrom(buf)
		if err != nil {
			if !strings.HasSuffix(err.Error(), ": use of closed network connection") {
				log.Error(err)
			}
			psr.sendError(err)
			break
		}
		// double check
		if atomic.LoadInt32(&psr.status) == reader.StatusStopped || atomic.LoadInt32(&psr.status) == reader.StatusStopping {
			return
		}
		psr.ReadChan <- buf[:n]
	}
}

func init() {
	reader.RegisterConstructor(reader.ModeSocket, NewReader)
}

type Reader struct {
	netproto        string
	ServiceAddress  string
	MaxConnections  int
	ReadBufferSize  int
	ReadTimeout     time.Duration
	KeepAlivePeriod time.Duration
	status          int32
	meta            *reader.Meta // 记录offset的元数据

	// resource need  close
	ReadChan chan []byte
	errChan  chan error
	Closer   io.Closer
}

func (sr *Reader) sendError(err error) {
	if err == nil {
		return
	}
	defer func() {
		if rec := recover(); rec != nil {
			log.Errorf("Reader %s panic, recovered from %v", sr.Name(), rec)
		}
	}()
	sr.errChan <- err
}

func (sr *Reader) Name() string {
	return "SocketReader<" + sr.ServiceAddress + ">"
}

func (sr *Reader) Source() string {
	return sr.ServiceAddress
}

func (sr *Reader) SetMode(mode string, v interface{}) error {
	return errors.New("SocketReader not support readmode")
}

func (sr *Reader) SyncMeta() {
	//FIXME 网络监听存在丢包可能性，无法保证不丢包
	return
}

func (sr *Reader) ReadLine() (data string, err error) {
	if atomic.LoadInt32(&sr.status) == reader.StatusInit {
		err = sr.Start()
		if err != nil {
			log.Error(err)
		}
	}
	timer := time.NewTimer(time.Second)
	select {
	case dat := <-sr.ReadChan:
		data = string(dat)
	case err = <-sr.errChan:
	case <-timer.C:
	}
	timer.Stop()
	return
}

func (sr *Reader) Start() error {

	if !atomic.CompareAndSwapInt32(&sr.status, reader.StatusInit, reader.StatusRunning) {
		return errors.New("socketReader aleady started")
	}

	spl := strings.SplitN(sr.ServiceAddress, "://", 2)
	if len(spl) != 2 {
		return fmt.Errorf("invalid service address: %s", sr.ServiceAddress)
	}
	sr.netproto = spl[0]
	if spl[0] == "unix" || spl[0] == "unixpacket" || spl[0] == "unixgram" {
		// 通过remove来检测套接字文件是否存在
		os.Remove(spl[1])
	}

	switch spl[0] {
	case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
		l, err := net.Listen(spl[0], spl[1])
		if err != nil {
			return err
		}

		if sr.ReadBufferSize > 0 {
			if srb, ok := l.(setReadBufferer); ok {
				srb.SetReadBuffer(sr.ReadBufferSize)
			} else {
				log.Warnf("Unable to set read buffer on a %s socket", spl[0])
			}
		}

		ssr := &streamSocketReader{
			Listener: l,
			Reader:   sr,
		}

		sr.Closer = l
		go ssr.listen()
	case "udp", "udp4", "udp6", "ip", "ip4", "ip6", "unixgram":
		pc, err := net.ListenPacket(spl[0], spl[1])
		if err != nil {
			return err
		}

		if sr.ReadBufferSize > 0 {
			if srb, ok := pc.(setReadBufferer); ok {
				srb.SetReadBuffer(sr.ReadBufferSize)
			} else {
				log.Warnf("Unable to set read buffer on a %s socket", spl[0])
			}
		}

		psr := &packetSocketReader{
			PacketConn: pc,
			Reader:     sr,
		}

		sr.Closer = pc
		go psr.listen()
	default:
		return fmt.Errorf("unknown protocol '%s' in '%s'", spl[0], sr.ServiceAddress)
	}

	if spl[0] == "unix" || spl[0] == "unixpacket" || spl[0] == "unixgram" {
		sr.Closer = unixCloser{path: spl[1], closer: sr.Closer}
	}

	return nil
}

func (sr *Reader) Close() error {
	if atomic.CompareAndSwapInt32(&sr.status, reader.StatusRunning, reader.StatusStopping) {
		log.Infof("Runner[%v] Reader[%v] stopping", sr.meta.RunnerName, sr.Name())
	} else {
		atomic.CompareAndSwapInt32(&sr.status, reader.StatusInit, reader.StatusStopped)
		close(sr.ReadChan)
		close(sr.errChan)
	}

	var err error
	if sr.Closer != nil {
		err = sr.Closer.Close()
		sr.Closer = nil

		// Make a connection meant to fail but unblock and release the port
		net.Dial(sr.netproto, sr.ServiceAddress)
	}
	log.Infof("Runner[%v] Reader[%v] stopped ", sr.meta.RunnerName, sr.Name())
	return err
}

func NewReader(meta *reader.Meta, conf conf.MapConf) (reader.Reader, error) {
	ServiceAddress, err := conf.GetString(reader.KeySocketServiceAddress)
	if err != nil {
		return nil, err
	}

	MaxConnections, _ := conf.GetIntOr(reader.KeySocketMaxConnections, 0)
	ReadTimeout, _ := conf.GetStringOr(reader.KeySocketReadTimeout, "0")
	ReadTimeoutdur, err := time.ParseDuration(ReadTimeout)
	if err != nil {
		return nil, err
	}
	ReadBufferSize, _ := conf.GetIntOr(reader.KeySocketReadBufferSize, 65535)

	KeepAlivePeriod, _ := conf.GetStringOr(reader.KeySocketKeepAlivePeriod, "5m")
	KeepAlivePeriodDur, err := time.ParseDuration(KeepAlivePeriod)
	if err != nil {
		return nil, err
	}
	return &Reader{
		ServiceAddress:  ServiceAddress,
		MaxConnections:  MaxConnections,
		ReadBufferSize:  ReadBufferSize,
		ReadTimeout:     ReadTimeoutdur,
		KeepAlivePeriod: KeepAlivePeriodDur,
		ReadChan:        make(chan []byte),
		errChan:         make(chan error),
		status:          reader.StatusInit,
		meta:            meta,
	}, nil
}

type unixCloser struct {
	path   string
	closer io.Closer
}

func (uc unixCloser) Close() error {
	err := uc.closer.Close()
	os.Remove(uc.path) // ignore error
	return err
}
<commit_msg>reader/socket: improve status check and implement DaemonReader<commit_after>package socket

import (
	"bufio"
	"errors"
	"fmt"
	"io"
	"net"
	"os"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/qiniu/log"

	"github.com/qiniu/logkit/conf"
	"github.com/qiniu/logkit/reader"
)

var (
	_ reader.DaemonReader = &Reader{}
	_ reader.Reader       = &Reader{}
)

type setReadBufferer interface {
	SetReadBuffer(bytes int) error
}

type streamSocketReader struct {
	Listener net.Listener
	*Reader

	connections    map[string]net.Conn
	connectionsMtx sync.Mutex
}

func (ssr *streamSocketReader) listen() {
	ssr.connections = map[string]net.Conn{}

	defer func() {
		if atomic.CompareAndSwapInt32(&ssr.status, reader.StatusStopping, reader.StatusStopped) {
			close(ssr.readChan)
			close(ssr.errChan)
		}
	}()
	for {
		c, err := ssr.Listener.Accept()
		if err != nil {
			if !strings.HasSuffix(err.Error(), ": use of closed network connection") {
				log.Error(err)
			}
			break
		}

		ssr.connectionsMtx.Lock()
		if ssr.MaxConnections > 0 && len(ssr.connections) >= ssr.MaxConnections {
			ssr.connectionsMtx.Unlock()
			c.Close()
			continue
		}
		ssr.connections[c.RemoteAddr().String()] = c
		ssr.connectionsMtx.Unlock()

		if ssr.netproto == "tcp" || ssr.netproto == "tcp4" || ssr.netproto == "tcp6" {
			if err := ssr.setKeepAlive(c); err != nil {
				log.Error(fmt.Errorf("unable to configure keep alive (%s): %s", ssr.ServiceAddress, err))
			}
		}

		go ssr.read(c)
	}

	ssr.connectionsMtx.Lock()
	for _, c := range ssr.connections {
		c.Close()
	}
	ssr.connectionsMtx.Unlock()
}

func (ssr *streamSocketReader) setKeepAlive(c net.Conn) error {
	tcpc, ok := c.(*net.TCPConn)
	if !ok {
		return fmt.Errorf("cannot set keep alive on a %s socket", strings.SplitN(ssr.ServiceAddress, "://", 2)[0])
	}
	if ssr.KeepAlivePeriod == 0 {
		return tcpc.SetKeepAlive(false)
	}
	if err := tcpc.SetKeepAlive(true); err != nil {
		return err
	}
	return tcpc.SetKeepAlivePeriod(ssr.KeepAlivePeriod)
}

func (ssr *streamSocketReader) removeConnection(c net.Conn) {
	ssr.connectionsMtx.Lock()
	delete(ssr.connections, c.RemoteAddr().String())
	ssr.connectionsMtx.Unlock()
}

func (ssr *streamSocketReader) read(c net.Conn) {
	defer ssr.removeConnection(c)
	defer c.Close()

	scnr := bufio.NewScanner(c)
	for {
		if atomic.LoadInt32(&ssr.status) == reader.StatusStopped || atomic.LoadInt32(&ssr.status) == reader.StatusStopping {
			return
		}
		if ssr.ReadTimeout != 0 && ssr.ReadTimeout > 0 {
			c.SetReadDeadline(time.Now().Add(ssr.ReadTimeout))
		}
		if !scnr.Scan() {
			break
		}

		//double check
		if atomic.LoadInt32(&ssr.status) == reader.StatusStopped || atomic.LoadInt32(&ssr.status) == reader.StatusStopping {
			return
		}
		ssr.readChan <- scnr.Bytes()
	}

	if err := scnr.Err(); err != nil {
		if nErr, ok := err.(net.Error); ok && nErr.Timeout() {
			log.Errorf("streamSocketReader Timeout : %s", nErr)
		}
		if !strings.HasSuffix(err.Error(), ": use of closed network connection") {
			log.Error(err)
			//可能reader都已经close了，channel也关了，直接return
			return
		}
		if atomic.LoadInt32(&ssr.status) == reader.StatusStopped || atomic.LoadInt32(&ssr.status) == reader.StatusStopping {
			return
		}
		ssr.sendError(err)
	}
}

type packetSocketReader struct {
	PacketConn net.PacketConn
	*Reader
}

func (psr *packetSocketReader) listen() {
	buf := make([]byte, 64*1024) // 64kb - maximum size of IP packet

	defer func() {
		if atomic.CompareAndSwapInt32(&psr.status, reader.StatusStopping, reader.StatusStopped) {
			close(psr.readChan)
			close(psr.errChan)
		}
	}()

	for {
		if atomic.LoadInt32(&psr.status) == reader.StatusStopped || atomic.LoadInt32(&psr.status) == reader.StatusStopping {
			return
		}
		n, _, err := psr.PacketConn.ReadFrom(buf)
		if err != nil {
			if !strings.HasSuffix(err.Error(), ": use of closed network connection") {
				log.Error(err)
			}
			psr.sendError(err)
			break
		}
		// double check
		if atomic.LoadInt32(&psr.status) == reader.StatusStopped || atomic.LoadInt32(&psr.status) == reader.StatusStopping {
			return
		}
		psr.readChan <- buf[:n]
	}
}

func init() {
	reader.RegisterConstructor(reader.ModeSocket, NewReader)
}

type Reader struct {
	meta *reader.Meta
	// Note: 原子操作，用于表示 reader 整体的运行状态
	status int32

	readChan chan []byte
	errChan  chan error

	netproto        string
	ServiceAddress  string
	MaxConnections  int
	ReadBufferSize  int
	ReadTimeout     time.Duration
	KeepAlivePeriod time.Duration

	closer io.Closer
}

func NewReader(meta *reader.Meta, conf conf.MapConf) (reader.Reader, error) {
	ServiceAddress, err := conf.GetString(reader.KeySocketServiceAddress)
	if err != nil {
		return nil, err
	}

	MaxConnections, _ := conf.GetIntOr(reader.KeySocketMaxConnections, 0)
	ReadTimeout, _ := conf.GetStringOr(reader.KeySocketReadTimeout, "0")
	ReadTimeoutdur, err := time.ParseDuration(ReadTimeout)
	if err != nil {
		return nil, err
	}
	ReadBufferSize, _ := conf.GetIntOr(reader.KeySocketReadBufferSize, 65535)

	KeepAlivePeriod, _ := conf.GetStringOr(reader.KeySocketKeepAlivePeriod, "5m")
	KeepAlivePeriodDur, err := time.ParseDuration(KeepAlivePeriod)
	if err != nil {
		return nil, err
	}
	return &Reader{
		meta:            meta,
		status:          reader.StatusInit,
		readChan:        make(chan []byte),
		errChan:         make(chan error),
		ServiceAddress:  ServiceAddress,
		MaxConnections:  MaxConnections,
		ReadBufferSize:  ReadBufferSize,
		ReadTimeout:     ReadTimeoutdur,
		KeepAlivePeriod: KeepAlivePeriodDur,
	}, nil
}

func (r *Reader) isStopping() bool {
	return atomic.LoadInt32(&r.status) == reader.StatusStopping
}

func (r *Reader) hasStopped() bool {
	return atomic.LoadInt32(&r.status) == reader.StatusStopped
}

func (r *Reader) Name() string {
	return "SocketReader<" + r.ServiceAddress + ">"
}

func (_ *Reader) SetMode(_ string, _ interface{}) error {
	return errors.New("socket reader does not support read mode")
}

func (r *Reader) sendError(err error) {
	if err == nil {
		return
	}
	defer func() {
		if rec := recover(); rec != nil {
			log.Errorf("Reader %q was panicked and recovered from %v", r.Name(), rec)
		}
	}()
	r.errChan <- err
}

func (r *Reader) Start() error {
	if r.isStopping() || r.hasStopped() {
		return errors.New("reader is stopping or has stopped")
	} else if !atomic.CompareAndSwapInt32(&r.status, reader.StatusInit, reader.StatusRunning) {
		log.Warnf("Runner[%v] %q daemon has already started and is running", r.meta.RunnerName, r.Name())
		return nil
	}

	spl := strings.SplitN(r.ServiceAddress, "://", 2)
	if len(spl) != 2 {
		return fmt.Errorf("invalid service address: %s", r.ServiceAddress)
	}
	r.netproto = spl[0]
	if spl[0] == "unix" || spl[0] == "unixpacket" || spl[0] == "unixgram" {
		// 通过remove来检测套接字文件是否存在
		os.Remove(spl[1])
	}

	switch spl[0] {
	case "tcp", "tcp4", "tcp6", "unix", "unixpacket":
		l, err := net.Listen(spl[0], spl[1])
		if err != nil {
			return err
		}

		if r.ReadBufferSize > 0 {
			if srb, ok := l.(setReadBufferer); ok {
				srb.SetReadBuffer(r.ReadBufferSize)
			} else {
				log.Warnf("Unable to set read buffer on a %s socket", spl[0])
			}
		}

		ssr := &streamSocketReader{
			Listener: l,
			Reader:   r,
		}

		r.closer = l
		go ssr.listen()
	case "udp", "udp4", "udp6", "ip", "ip4", "ip6", "unixgram":
		pc, err := net.ListenPacket(spl[0], spl[1])
		if err != nil {
			return err
		}

		if r.ReadBufferSize > 0 {
			if srb, ok := pc.(setReadBufferer); ok {
				srb.SetReadBuffer(r.ReadBufferSize)
			} else {
				log.Warnf("Unable to set read buffer on a %s socket", spl[0])
			}
		}

		psr := &packetSocketReader{
			PacketConn: pc,
			Reader:     r,
		}

		r.closer = pc
		go psr.listen()
	default:
		return fmt.Errorf("unknown protocol '%s' in '%s'", spl[0], r.ServiceAddress)
	}

	if spl[0] == "unix" || spl[0] == "unixpacket" || spl[0] == "unixgram" {
		r.closer = unixCloser{path: spl[1], closer: r.closer}
	}

	return nil
}

func (r *Reader) Source() string {
	return r.ServiceAddress
}

func (r *Reader) ReadLine() (string, error) {
	timer := time.NewTimer(time.Second)
	defer timer.Stop()
	select {
	case data := <-r.readChan:
		return string(data), nil
	case <-timer.C:
	}

	return "", nil
}

func (r *Reader) SyncMeta() {
	//FIXME 网络监听存在丢包可能性，无法保证不丢包
}

func (r *Reader) Close() error {
	if !atomic.CompareAndSwapInt32(&r.status, reader.StatusRunning, reader.StatusStopping) {
		log.Warnf("Runner[%v] reader %q is not running, close operation ignored", r.meta.RunnerName, r.Name())
		return nil
	}
	log.Debugf("Runner[%v] %q daemon is stopping", r.meta.RunnerName, r.Name())

	var err error
	if r.closer != nil {
		err = r.closer.Close()
		r.closer = nil

		// Make a connection meant to fail but unblock and release the port
		net.Dial(r.netproto, r.ServiceAddress)
	}
	atomic.StoreInt32(&r.status, reader.StatusStopped)
	log.Infof("Runner[%v] %q daemon has stopped from running", r.meta.RunnerName, r.Name())
	return err
}

type unixCloser struct {
	path   string
	closer io.Closer
}

func (uc unixCloser) Close() error {
	err := uc.closer.Close()
	os.Remove(uc.path) // ignore error
	return err
}
<|endoftext|>


<commit_before>package utils

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"regexp"
	"strings"
)

// Signer interface describes facility implementing signing of files
type Signer interface {
	Init() error
	SetKey(keyRef string)
	DetachedSign(source string, destination string) error
	ClearSign(source string, destination string) error
}

// Verifier interface describes signature verification factility
type Verifier interface {
	InitKeyring() error
	AddKeyring(keyring string)
	VerifyDetachedSignature(signature, cleartext io.Reader) error
	VerifyClearsigned(clearsigned io.Reader) (text *os.File, err error)
}

// Test interface
var (
	_ Signer   = &GpgSigner{}
	_ Verifier = &GpgVerifier{}
)

// GpgSigner is implementation of Signer interface using gpg
type GpgSigner struct {
	keyRef string
}

// SetKey sets key ID to use when signing files
func (g *GpgSigner) SetKey(keyRef string) {
	g.keyRef = keyRef
}

// Init verifies availability of gpg & presence of keys
func (g *GpgSigner) Init() error {
	output, err := exec.Command("gpg", "--list-keys").Output()
	if err != nil {
		return fmt.Errorf("unable to execute gpg: %s (is gpg installed?)", err)
	}

	if len(output) == 0 {
		return fmt.Errorf("looks like there are no keys in gpg, please create one (official manual: http://www.gnupg.org/gph/en/manual.html)")
	}

	return err
}

// DetachedSign signs file with detached signature in ASCII format
func (g *GpgSigner) DetachedSign(source string, destination string) error {
	fmt.Printf("Signing file '%s' with gpg, please enter your passphrase when prompted:\n", source)

	args := []string{"-o", destination, "--armor", "--yes"}
	if g.keyRef != "" {
		args = append(args, "-u", g.keyRef)
	}
	args = append(args, "--detach-sign", source)
	cmd := exec.Command("gpg", args...)
	return cmd.Run()
}

// ClearSign clear-signs the file
func (g *GpgSigner) ClearSign(source string, destination string) error {
	fmt.Printf("Clearsigning file '%s' with gpg, please enter your passphrase when prompted:\n", source)
	args := []string{"-o", destination, "--yes"}
	if g.keyRef != "" {
		args = append(args, "-u", g.keyRef)
	}
	args = append(args, "--clearsign", source)
	cmd := exec.Command("gpg", args...)
	return cmd.Run()
}

// GpgVerifier is implementation of Verifier interface using gpgv
type GpgVerifier struct {
	keyRings []string
}

func (g *GpgVerifier) InitKeyring() error {
	err := exec.Command("gpgv", "--version").Run()
	if err != nil {
		return fmt.Errorf("unable to execute gpgv: %s (is gpg installed?)", err)
	}

	if len(g.keyRings) == 0 {
		// using default keyring
		output, err := exec.Command("gpg", "--no-default-keyring", "--keyring", "trustedkeys.gpg", "--list-keys").Output()
		if err == nil && len(output) == 0 {
			fmt.Printf("\nLooks like your keyring with trusted keys is empty. You might consider importing some keys.\n")
			fmt.Printf("If you're running Debian or Ubuntu, it's a good idea to import current archive keys by running:\n\n")
			fmt.Printf("  gpg --keyring /usr/share/keyrings/debian-archive-keyring.gpg --export | gpg --no-default-keyring --keyring trustedkeys.gpg --import\n")
			fmt.Printf("\n(for Ubuntu, use /usr/share/keyrings/ubuntu-archive-keyring.gpg)\n\n")
		}
	}

	return nil
}

func (g *GpgVerifier) AddKeyring(keyring string) {
	g.keyRings = append(g.keyRings, keyring)
}

func (g *GpgVerifier) argsKeyrings() (args []string) {
	if len(g.keyRings) > 0 {
		args = make([]string, 0, 2*len(g.keyRings))
		for _, keyring := range g.keyRings {
			args = append(args, "--keyring", keyring)
		}
	} else {
		args = []string{"--keyring", "trustedkeys.gpg"}
	}
	return
}

func (g *GpgVerifier) VerifyDetachedSignature(signature, cleartext io.Reader) error {
	args := g.argsKeyrings()

	sigf, err := ioutil.TempFile("", "aptly-gpg")
	if err != nil {
		return err
	}
	defer os.Remove(sigf.Name())
	defer sigf.Close()

	_, err = io.Copy(sigf, signature)
	if err != nil {
		return err
	}

	clearf, err := ioutil.TempFile("", "aptly-gpg")
	if err != nil {
		return err
	}
	defer os.Remove(clearf.Name())
	defer clearf.Close()

	_, err = io.Copy(clearf, cleartext)
	if err != nil {
		return err
	}

	args = append(args, sigf.Name(), clearf.Name())
	cmd := exec.Command("gpgv", args...)

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}
	defer stderr.Close()

	err = cmd.Start()
	if err != nil {
		return err
	}

	buffer := &bytes.Buffer{}

	_, err = io.Copy(io.MultiWriter(os.Stderr, buffer), stderr)
	if err != nil {
		return err
	}

	matches := regexp.MustCompile("ID ([0-9A-F]{8})").FindAllStringSubmatch(buffer.String(), -1)

	err = cmd.Wait()
	if err != nil {
		if len(g.keyRings) == 0 && len(matches) > 0 {
			fmt.Printf("\nLooks like some keys are missing in your trusted keyring, you may consider importing them from keyserver:\n\n")

			keyIDs := []string{}
			for _, match := range matches {
				keyIDs = append(keyIDs, match[1])
			}
			fmt.Printf("gpg --no-default-keyring --keyring trustedkeys.gpg --keyserver keys.gnupg.net --recv-keys %s\n\n",
				strings.Join(keyIDs, " "))
		}
		return fmt.Errorf("GnuPG verification of detached signature failed: %s", err)
	}
	return nil
}

func (g *GpgVerifier) VerifyClearsigned(clearsigned io.Reader) (text *os.File, err error) {
	args := g.argsKeyrings()

	clearf, err := ioutil.TempFile("", "aptly-gpg")
	if err != nil {
		return
	}
	defer os.Remove(clearf.Name())
	defer clearf.Close()

	_, err = io.Copy(clearf, clearsigned)
	if err != nil {
		return
	}

	args = append(args, clearf.Name())
	cmd := exec.Command("gpgv", args...)

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}
	defer stderr.Close()

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	buffer := &bytes.Buffer{}

	_, err = io.Copy(io.MultiWriter(os.Stderr, buffer), stderr)
	if err != nil {
		return nil, err
	}

	matches := regexp.MustCompile("ID ([0-9A-F]{8})").FindAllStringSubmatch(buffer.String(), -1)

	err = cmd.Wait()
	if err != nil {
		if len(g.keyRings) == 0 && len(matches) > 0 {
			fmt.Printf("\nLooks like some keys are missing in your trusted keyring, you may consider importing them from keyserver:\n\n")

			keyIDs := []string{}
			for _, match := range matches {
				keyIDs = append(keyIDs, match[1])
			}
			fmt.Printf("gpg --no-default-keyring --keyring trustedkeys.gpg --keyserver keys.gnupg.net --recv-keys %s\n\n",
				strings.Join(keyIDs, " "))
		}
		return nil, fmt.Errorf("GnuPG verification of clearsigned file failed: %s", err)
	}

	text, err = ioutil.TempFile("", "aptly-gpg")
	if err != nil {
		return
	}
	defer os.Remove(text.Name())

	args = []string{"--no-default-keyring"}
	args = append(args, g.argsKeyrings()...)
	args = append(args, "--decrypt", "--batch", "--output", "-", clearf.Name())

	cmd = exec.Command("gpg", args...)
	cmd.Stderr = os.Stderr
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer stdout.Close()

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	_, err = io.Copy(text, stdout)
	if err != nil {
		return nil, err
	}

	err = cmd.Wait()

	if err != nil {
		return nil, fmt.Errorf("GnuPG extraction of clearsigned file failed: %s", err)
	}

	_, err = text.Seek(0, 0)
	if err != nil {
		return nil, err
	}

	return
}
<commit_msg>Make gpg be less chattly about untrusted keys.<commit_after>package utils

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"regexp"
	"strings"
)

// Signer interface describes facility implementing signing of files
type Signer interface {
	Init() error
	SetKey(keyRef string)
	DetachedSign(source string, destination string) error
	ClearSign(source string, destination string) error
}

// Verifier interface describes signature verification factility
type Verifier interface {
	InitKeyring() error
	AddKeyring(keyring string)
	VerifyDetachedSignature(signature, cleartext io.Reader) error
	VerifyClearsigned(clearsigned io.Reader) (text *os.File, err error)
}

// Test interface
var (
	_ Signer   = &GpgSigner{}
	_ Verifier = &GpgVerifier{}
)

// GpgSigner is implementation of Signer interface using gpg
type GpgSigner struct {
	keyRef string
}

// SetKey sets key ID to use when signing files
func (g *GpgSigner) SetKey(keyRef string) {
	g.keyRef = keyRef
}

// Init verifies availability of gpg & presence of keys
func (g *GpgSigner) Init() error {
	output, err := exec.Command("gpg", "--list-keys").Output()
	if err != nil {
		return fmt.Errorf("unable to execute gpg: %s (is gpg installed?)", err)
	}

	if len(output) == 0 {
		return fmt.Errorf("looks like there are no keys in gpg, please create one (official manual: http://www.gnupg.org/gph/en/manual.html)")
	}

	return err
}

// DetachedSign signs file with detached signature in ASCII format
func (g *GpgSigner) DetachedSign(source string, destination string) error {
	fmt.Printf("Signing file '%s' with gpg, please enter your passphrase when prompted:\n", source)

	args := []string{"-o", destination, "--armor", "--yes"}
	if g.keyRef != "" {
		args = append(args, "-u", g.keyRef)
	}
	args = append(args, "--detach-sign", source)
	cmd := exec.Command("gpg", args...)
	return cmd.Run()
}

// ClearSign clear-signs the file
func (g *GpgSigner) ClearSign(source string, destination string) error {
	fmt.Printf("Clearsigning file '%s' with gpg, please enter your passphrase when prompted:\n", source)
	args := []string{"-o", destination, "--yes"}
	if g.keyRef != "" {
		args = append(args, "-u", g.keyRef)
	}
	args = append(args, "--clearsign", source)
	cmd := exec.Command("gpg", args...)
	return cmd.Run()
}

// GpgVerifier is implementation of Verifier interface using gpgv
type GpgVerifier struct {
	keyRings []string
}

func (g *GpgVerifier) InitKeyring() error {
	err := exec.Command("gpgv", "--version").Run()
	if err != nil {
		return fmt.Errorf("unable to execute gpgv: %s (is gpg installed?)", err)
	}

	if len(g.keyRings) == 0 {
		// using default keyring
		output, err := exec.Command("gpg", "--no-default-keyring", "--keyring", "trustedkeys.gpg", "--list-keys").Output()
		if err == nil && len(output) == 0 {
			fmt.Printf("\nLooks like your keyring with trusted keys is empty. You might consider importing some keys.\n")
			fmt.Printf("If you're running Debian or Ubuntu, it's a good idea to import current archive keys by running:\n\n")
			fmt.Printf("  gpg --keyring /usr/share/keyrings/debian-archive-keyring.gpg --export | gpg --no-default-keyring --keyring trustedkeys.gpg --import\n")
			fmt.Printf("\n(for Ubuntu, use /usr/share/keyrings/ubuntu-archive-keyring.gpg)\n\n")
		}
	}

	return nil
}

func (g *GpgVerifier) AddKeyring(keyring string) {
	g.keyRings = append(g.keyRings, keyring)
}

func (g *GpgVerifier) argsKeyrings() (args []string) {
	if len(g.keyRings) > 0 {
		args = make([]string, 0, 2*len(g.keyRings))
		for _, keyring := range g.keyRings {
			args = append(args, "--keyring", keyring)
		}
	} else {
		args = []string{"--keyring", "trustedkeys.gpg"}
	}
	return
}

func (g *GpgVerifier) VerifyDetachedSignature(signature, cleartext io.Reader) error {
	args := g.argsKeyrings()

	sigf, err := ioutil.TempFile("", "aptly-gpg")
	if err != nil {
		return err
	}
	defer os.Remove(sigf.Name())
	defer sigf.Close()

	_, err = io.Copy(sigf, signature)
	if err != nil {
		return err
	}

	clearf, err := ioutil.TempFile("", "aptly-gpg")
	if err != nil {
		return err
	}
	defer os.Remove(clearf.Name())
	defer clearf.Close()

	_, err = io.Copy(clearf, cleartext)
	if err != nil {
		return err
	}

	args = append(args, sigf.Name(), clearf.Name())
	cmd := exec.Command("gpgv", args...)

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return err
	}
	defer stderr.Close()

	err = cmd.Start()
	if err != nil {
		return err
	}

	buffer := &bytes.Buffer{}

	_, err = io.Copy(io.MultiWriter(os.Stderr, buffer), stderr)
	if err != nil {
		return err
	}

	matches := regexp.MustCompile("ID ([0-9A-F]{8})").FindAllStringSubmatch(buffer.String(), -1)

	err = cmd.Wait()
	if err != nil {
		if len(g.keyRings) == 0 && len(matches) > 0 {
			fmt.Printf("\nLooks like some keys are missing in your trusted keyring, you may consider importing them from keyserver:\n\n")

			keyIDs := []string{}
			for _, match := range matches {
				keyIDs = append(keyIDs, match[1])
			}
			fmt.Printf("gpg --no-default-keyring --keyring trustedkeys.gpg --keyserver keys.gnupg.net --recv-keys %s\n\n",
				strings.Join(keyIDs, " "))
		}
		return fmt.Errorf("GnuPG verification of detached signature failed: %s", err)
	}
	return nil
}

func (g *GpgVerifier) VerifyClearsigned(clearsigned io.Reader) (text *os.File, err error) {
	args := g.argsKeyrings()

	clearf, err := ioutil.TempFile("", "aptly-gpg")
	if err != nil {
		return
	}
	defer os.Remove(clearf.Name())
	defer clearf.Close()

	_, err = io.Copy(clearf, clearsigned)
	if err != nil {
		return
	}

	args = append(args, clearf.Name())
	cmd := exec.Command("gpgv", args...)

	stderr, err := cmd.StderrPipe()
	if err != nil {
		return nil, err
	}
	defer stderr.Close()

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	buffer := &bytes.Buffer{}

	_, err = io.Copy(io.MultiWriter(os.Stderr, buffer), stderr)
	if err != nil {
		return nil, err
	}

	matches := regexp.MustCompile("ID ([0-9A-F]{8})").FindAllStringSubmatch(buffer.String(), -1)

	err = cmd.Wait()
	if err != nil {
		if len(g.keyRings) == 0 && len(matches) > 0 {
			fmt.Printf("\nLooks like some keys are missing in your trusted keyring, you may consider importing them from keyserver:\n\n")

			keyIDs := []string{}
			for _, match := range matches {
				keyIDs = append(keyIDs, match[1])
			}
			fmt.Printf("gpg --no-default-keyring --keyring trustedkeys.gpg --keyserver keys.gnupg.net --recv-keys %s\n\n",
				strings.Join(keyIDs, " "))
		}
		return nil, fmt.Errorf("GnuPG verification of clearsigned file failed: %s", err)
	}

	text, err = ioutil.TempFile("", "aptly-gpg")
	if err != nil {
		return
	}
	defer os.Remove(text.Name())

	args = []string{"--no-default-keyring"}
	args = append(args, g.argsKeyrings()...)
	args = append(args, "--decrypt", "--batch", "--trust-model", "always", "--output", "-", clearf.Name())

	cmd = exec.Command("gpg", args...)
	cmd.Stderr = os.Stderr
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		return nil, err
	}
	defer stdout.Close()

	err = cmd.Start()
	if err != nil {
		return nil, err
	}

	_, err = io.Copy(text, stdout)
	if err != nil {
		return nil, err
	}

	err = cmd.Wait()

	if err != nil {
		return nil, fmt.Errorf("GnuPG extraction of clearsigned file failed: %s", err)
	}

	_, err = text.Seek(0, 0)
	if err != nil {
		return nil, err
	}

	return
}
<|endoftext|>


<commit_before>package task

import (
	"fmt"
	"neon/build"
	"os"
	"reflect"
)

func init() {
	build.AddTask(build.TaskDesc {
		Name: "write",
		Func: Write,
		Args: reflect.TypeOf(WriteArgs{}),
		Help: `Write text into a given file.

Arguments:

- write: the file to write into as a string.
- text: the text to write into the file.
- append: tells if we should append content to file (defaults to false).

Examples:

    # write 'Hello World!' in file greetings.txt
    - write: "greetings.txt"
      text: "Hello World!"`,
	})
}

type WriteArgs struct {
	Write  string `file`
	Text   string `optional`
	Append bool
}

func Write(context *build.Context, args interface{}) error {
	params := args.(WriteArgs)
	var mode int
	if params.Append {
		mode = os.O_CREATE | os.O_WRONLY | os.O_APPEND
	} else {
		mode = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
	}
	file, err := os.OpenFile(params.Write, mode, FILE_MODE)
	if err != nil {
		return fmt.Errorf("opening file '%s': %v", params.Write, err)
	}
	defer file.Close()
	_, err = file.WriteString(params.Text)
	if err != nil {
		return fmt.Errorf("writing content to file '%s': %v", params.Write, err)
	}
	err = file.Sync()
	if err != nil {
		return fmt.Errorf("syncing content to file '%s': %v", params.Write, err)
	}
	return nil
}
<commit_msg>Fixed write task<commit_after>package task

import (
	"fmt"
	"neon/build"
	"os"
	"reflect"
)

func init() {
	build.AddTask(build.TaskDesc {
		Name: "write",
		Func: Write,
		Args: reflect.TypeOf(WriteArgs{}),
		Help: `Write text into a given file.

Arguments:

- write: the file to write into as a string.
- text: the text to write into the file.
- append: tells if we should append content to file (defaults to false).

Examples:

    # write 'Hello World!' in file greetings.txt
    - write: "greetings.txt"
      text: "Hello World!"`,
	})
}

type WriteArgs struct {
	Write  string `file`
	Text   string `optional`
	Append bool   `optional`
}

func Write(context *build.Context, args interface{}) error {
	params := args.(WriteArgs)
	var mode int
	if params.Append {
		mode = os.O_CREATE | os.O_WRONLY | os.O_APPEND
	} else {
		mode = os.O_CREATE | os.O_WRONLY | os.O_TRUNC
	}
	file, err := os.OpenFile(params.Write, mode, FILE_MODE)
	if err != nil {
		return fmt.Errorf("opening file '%s': %v", params.Write, err)
	}
	defer file.Close()
	_, err = file.WriteString(params.Text)
	if err != nil {
		return fmt.Errorf("writing content to file '%s': %v", params.Write, err)
	}
	err = file.Sync()
	if err != nil {
		return fmt.Errorf("syncing content to file '%s': %v", params.Write, err)
	}
	return nil
}
<|endoftext|>


<commit_before>/*
   package catena implements the catena memory-consuming password
   scrambler.

   The package currently supports the basic scrambling functions,
   with a focus on password hashing. Further releases will see more
   support for the extended parts of Catena, including key derivation,
   server relief, and client-independent updates.

   The tweak can be nil, but it represents an ability to inject
   additional information (include the purpose, hash size, and salt
   length) in the final output.

   The garlic is a parameter that controls the amount of memory
   used.  This implementation restricts the garlic to at most 31.
   The documentation contains performance notes that may be of
   interest to the user. The Catena paper recommends setting the
   initial garlic value to the actual garlic value as a balance
   between memory required and performance.

   The paper recommends a salt length of 16 bytes, but this parameter
   is left up to the user to determine.
*/
package catena
<commit_msg>Update docs.<commit_after>/*
   package catena implements the catena memory-consuming password
   scrambler.

   The package currently supports the basic scrambling functions,
   with a focus on password hashing. Further releases will see more
   support for the extended parts of Catena, including key derivation,
   server relief, and client-independent updates.

   The tweak can be nil, but it represents an ability to inject
   additional information (include the purpose, hash size, and salt
   length) in the final output.

   The garlic is a parameter that controls the amount of memory
   used.  This implementation restricts the garlic to at most 31.
   The documentation contains performance notes that may be of
   interest to the user. The Catena paper recommends setting the
   initial garlic value to the actual garlic value as a balance
   between memory required and performance.

   The paper recommends a salt length of 16 bytes, but this parameter
   is left up to the user to determine.

   The MatchPassword function properly determines whether a password
   matches the output of the function. This function uses a constant
   time comparison to mitigate timing attacks.
*/
package catena
<|endoftext|>


<commit_before>// Package dnsdisco is a DNS service discovery library with health check and
// load balancer features.
//
// The library is very flexible and uses interfaces everywhere to make it
// possible for the library user to replace any part with a custom algorithm. A
// basic use would be:
//
//    package main
//
//    import (
//      "fmt"
//
//      "github.com/rafaeljusto/dnsdisco"
//    )
//
//    func main() {
//      target, port, err := dnsdisco.Discover("jabber", "tcp", "registro.br")
//      if err != nil {
//        fmt.Println(err)
//        return
//      }
//
//      fmt.Printf("Target: %s\nPort: %d\n", target, port)
//    }
//
// Now you want to use your own resolver with specific timeouts to retrieve the
// servers:
//
//    package main
//
//    import (
//      "fmt"
//      "net"
//      "strings"
//      "time"
//
//      "github.com/miekg/dns"
//      "github.com/rafaeljusto/dnsdisco"
//    )
//
//    func main() {
//      discovery := dnsdisco.NewDiscovery("jabber", "tcp", "registro.br")
//      discovery.Retriever = dnsdisco.RetrieverFunc(func(service, proto, name string) (servers []*net.SRV, err error) {
//        client := dns.Client{
//          ReadTimeout: 2*time.Second,
//          WriteTimeout: 2*time.Second,
//        }
//
//        name = strings.TrimRight(name, ".")
//        z := fmt.Sprintf("_%s._%s.%s.", service, proto, name)
//
//        var request dns.Msg
//        request.SetQuestion(z, dns.TypeSRV)
//        request.RecursionDesired = true
//
//        response, _, err := client.Exchange(&request, "8.8.8.8:53")
//        if err != nil {
//          return nil, err
//        }
//
//        for _, rr := range response.Answer {
//          if srv, ok := rr.(*dns.SRV); ok {
//            servers = append(servers, &net.SRV{
//              Target: srv.Target,
//              Port: srv.Port,
//              Priority: srv.Priority,
//              Weight: srv.Weight,
//            })
//          }
//        }
//
//        return
//      })
//
//      // Retrieve the servers
//      if err := discovery.Refresh(); err != nil {
//        fmt.Println(err)
//        return
//      }
//
//      target, port := discovery.Choose()
//      fmt.Printf("Target: %s\nPort: %d\n", target, port)
//    }
package dnsdisco
<commit_msg>Remove duplicated example from documentation<commit_after>// Package dnsdisco is a DNS service discovery library with health check and
// load balancer features.
//
// The library is very flexible and uses interfaces everywhere to make it
// possible for the library user to replace any part with a custom algorithm. Check the examples for
// more details.
package dnsdisco
<|endoftext|>


<commit_before>package gol

import (
	"github.com/ianremmler/chipmunk"
	"github.com/ianremmler/gordian"

	"fmt"
	"math"
	"math/rand"
	"sync"
	"time"
)

const (
	simTime       = time.Second / 60
	updateTime    = time.Second / 24
	pauseTime     = time.Second
	headStartTime = time.Second
	maxScore      = 10
	fieldWidth    = 1000
	fieldHeight   = 500
	edgeRadius    = 8
	goalSize      = 200
	playerRadius  = 10
	playerMass    = 1
	ballRadius    = 10
	ballMass      = 0.1
)

const (
	normLayer = 1 << iota
	goalLayer
)

type player struct {
	id          gordian.ClientId
	team        int
	body        chipmunk.Body
	shape       chipmunk.Shape
	cursorBody  chipmunk.Body
	cursorJoint chipmunk.Constraint
}

func (p *player) place() {
	hfw, hfh := 0.5*fieldWidth-playerRadius, 0.5*fieldHeight-playerRadius
	pos := chipmunk.Vect{rand.Float64() * hfw, rand.Float64()*(2*hfh) - hfh}
	if p.team == 0 {
		pos.X = -pos.X
	}
	minDist := 0.25*fieldHeight + playerRadius
	len := pos.Length()
	if len < minDist {
		pos = pos.Div(len).Mul(minDist)
	}
	p.body.SetPosition(pos)
}

type ball struct {
	body  chipmunk.Body
	shape chipmunk.Shape
}

type Player struct {
	Pos  chipmunk.Vect
	Team int
}

type Ball struct {
	Pos chipmunk.Vect
}

type configMsg struct {
	Id           string
	FieldWidth   float64
	FieldHeight  float64
	GoalSize     float64
	PlayerRadius float64
	BallRadius   float64
}

type stateMsg struct {
	Players map[string]Player
	Ball    Ball
	Score   []int
}

type Gol struct {
	players     map[gordian.ClientId]*player
	ball        ball
	score       []int
	pauseTicks  []int
	simTimer    <-chan time.Time
	updateTimer <-chan time.Time
	curId       int
	space       *chipmunk.Space
	mu          sync.Mutex
	*gordian.Gordian
}

func New() *Gol {
	g := &Gol{
		players:     map[gordian.ClientId]*player{},
		score:       []int{0, 0},
		pauseTicks:  []int{0, 0},
		simTimer:    time.Tick(simTime),
		updateTimer: time.Tick(updateTime),
		Gordian:     gordian.New(24),
	}
	g.setup()
	return g
}

func (g *Gol) setup() {
	g.space = chipmunk.SpaceNew()
	g.space.SetDamping(0.1)
	hfw, hfh, hgs := 0.5*fieldWidth, 0.5*fieldHeight, 0.5*goalSize
	sidePts := []chipmunk.Vect{{-hfw, hgs}, {-hfw, hfh}, {hfw, hfh}, {hfw, hgs}}
	numSideSegs := len(sidePts) - 1
	for i := 0; i < 2; i++ {
		sign := 2*float64(i) - 1
		for j := 0; j < numSideSegs; j++ {
			p0, p1 := sidePts[j], sidePts[j+1]
			p0.Y *= sign
			p1.Y *= sign
			fieldSeg := chipmunk.SegmentShapeNew(g.space.StaticBody(), p0, p1, edgeRadius)
			fieldSeg.SetLayers(normLayer)
			fieldSeg.SetElasticity(1.0)
			fieldSeg.SetFriction(1.0)
			g.space.AddShape(fieldSeg)
		}
		p0, p1 := chipmunk.Vect{sign * hfw, -hgs}, chipmunk.Vect{sign * hfw, hgs}
		goal := chipmunk.SegmentShapeNew(g.space.StaticBody(), p0, p1, edgeRadius)
		goal.SetLayers(goalLayer)
		goal.SetElasticity(1.0)
		goal.SetFriction(1.0)
		g.space.AddShape(goal)
	}
	moment := chipmunk.MomentForCircle(ballMass, 0, ballRadius, chipmunk.Origin())
	g.ball.body = chipmunk.BodyNew(ballMass, moment)
	g.space.AddBody(g.ball.body)
	g.ball.shape = chipmunk.CircleShapeNew(g.ball.body, ballRadius, chipmunk.Origin())
	g.ball.shape.SetLayers(normLayer)
	g.ball.shape.SetElasticity(0.9)
	g.ball.shape.SetFriction(0.1)
	g.space.AddShape(g.ball.shape)
}

func (g *Gol) Run() {
	go g.run()
	go g.sim()
	g.Gordian.Run()
}

func (g *Gol) run() {
	for {
		select {
		case client := <-g.Control:
			g.clientCtrl(client)
		case msg := <-g.InBox:
			g.handleMessage(&msg)
		case <-g.updateTimer:
			g.update()
		}
	}
}

func (g *Gol) sim() {
	for {
		<-g.simTimer

		g.mu.Lock()

		g.space.Step(simTime.Seconds())
		g.handlePauses()
		g.handleGoals()

		g.mu.Unlock()
	}
}

func (g *Gol) handlePauses() {
	// enable control if pause is ending
	for _, player := range g.players {
		if g.pauseTicks[player.team] == 1 && player.cursorJoint.Space() != g.space {
			g.space.AddConstraint(player.cursorJoint)
		}
	}
	// update pause countdown
	for i := range g.pauseTicks {
		if g.pauseTicks[i] > 0 {
			g.pauseTicks[i]--
		}
	}
}

func (g *Gol) handleGoals() {
	ballX := g.ball.body.Position().X
	if math.Abs(ballX) > fieldWidth/2 { // GOL!
		team := 0
		if ballX < 0 {
			team = 1
		}
		g.score[team]++
		if g.score[0] >= maxScore || g.score[1] >= maxScore {
			g.score[0], g.score[1] = 0, 0
		}
		g.kickoff(team)
	}
}

func (g *Gol) kickoff(team int) {
	otherTeam := 1 - team

	g.ball.body.SetPosition(chipmunk.Vect{})
	g.ball.body.SetVelocity(chipmunk.Vect{})
	for _, player := range g.players {
		player.place()
		player.body.SetVelocity(chipmunk.Vect{})
		if g.pauseTicks[player.team] == 0 && player.cursorJoint.Space() == g.space {
			// disable control for a bit
			g.space.RemoveConstraint(player.cursorJoint)
		}
	}
	// give the team that was scored on a little head start for "kickoff"
	g.pauseTicks[team] = int((pauseTime + headStartTime) / simTime)
	g.pauseTicks[otherTeam] = int(pauseTime / simTime)
}

func (g *Gol) clientCtrl(client *gordian.Client) {
	switch client.Ctrl {
	case gordian.Connect:
		g.connect(client)
	case gordian.Close:
		g.close(client)
	}
}

func (g *Gol) nextTeam() int {
	teamSize := []int{0, 0}
	for _, player := range g.players {
		teamSize[player.team]++
	}
	switch {
	case teamSize[0] < teamSize[1]:
		return 0
	case teamSize[0] > teamSize[1]:
		return 1
	default:
		return rand.Intn(2)
	}
}

func (g *Gol) addPlayer(id gordian.ClientId) *player {
	player := &player{id: id, team: g.nextTeam()}

	moment := chipmunk.MomentForCircle(playerMass, 0, playerRadius, chipmunk.Origin())
	player.body = chipmunk.BodyNew(playerMass, moment)
	g.space.AddBody(player.body)

	player.shape = chipmunk.CircleShapeNew(player.body, playerRadius, chipmunk.Origin())
	player.shape.SetLayers(normLayer | goalLayer)
	player.shape.SetElasticity(0.9)
	player.shape.SetFriction(0.1)
	g.space.AddShape(player.shape)

	player.cursorBody = chipmunk.BodyNew(math.Inf(0), math.Inf(0))
	player.cursorJoint = chipmunk.PivotJointNew2(player.cursorBody, player.body,
		chipmunk.Vect{}, chipmunk.Vect{})
	player.cursorJoint.SetMaxForce(1000.0)
	g.space.AddConstraint(player.cursorJoint)

	g.players[player.id] = player

	return player
}

func (g *Gol) removePlayer(id gordian.ClientId) {
	player, ok := g.players[id]
	if !ok {
		return
	}
	if player.cursorJoint.Space() == g.space {
		g.space.RemoveConstraint(player.cursorJoint)
	}
	player.cursorJoint.Free()
	g.space.RemoveBody(player.body)
	g.space.RemoveShape(player.shape)
	player.body.Free()
	player.shape.Free()
	player.cursorBody.Free()

	delete(g.players, id)
}

func (g *Gol) connect(client *gordian.Client) {
	g.curId++

	client.Id = g.curId
	client.Ctrl = gordian.Register
	g.Control <- client
	client = <-g.Control
	if client.Ctrl != gordian.Establish {
		return
	}

	g.mu.Lock()

	player := g.addPlayer(client.Id)
	player.place()

	g.mu.Unlock()

	data := configMsg{
		FieldWidth:   fieldWidth,
		FieldHeight:  fieldHeight,
		GoalSize:     goalSize,
		PlayerRadius: playerRadius,
		BallRadius:   ballRadius,
		Id:           fmt.Sprintf("%d", client.Id),
	}
	msg := gordian.Message{
		To:   client.Id,
		Type: "config",
		Data: data,
	}
	g.OutBox <- msg
}

func (g *Gol) close(client *gordian.Client) {
	g.mu.Lock()
	defer g.mu.Unlock()

	g.removePlayer(client.Id)
}

func (g *Gol) handleMessage(msg *gordian.Message) {
	g.mu.Lock()
	defer g.mu.Unlock()

	id := msg.From
	player, ok := g.players[id]
	if !ok {
		return
	}
	switch msg.Type {
	case "player":
		state := &Player{}
		err := msg.Unmarshal(state)
		if err != nil {
			return
		}
		player.cursorBody.SetPosition(state.Pos)
	}
}

func (g *Gol) update() {
	g.mu.Lock()

	state := stateMsg{
		Players: map[string]Player{},
		Ball:    Ball{g.ball.body.Position()},
		Score:   g.score,
	}
	for i, player := range g.players {
		state.Players[fmt.Sprintf("%d", i)] = Player{
			Pos:  player.body.Position(),
			Team: player.team,
		}
	}

	g.mu.Unlock()

	msg := gordian.Message{
		Type: "state",
		Data: state,
	}
	for id := range g.players {
		msg.To = id
		g.OutBox <- msg
	}
}
<commit_msg>Improved player placement code.<commit_after>package gol

import (
	"github.com/ianremmler/chipmunk"
	"github.com/ianremmler/gordian"

	"fmt"
	"math"
	"math/rand"
	"sync"
	"time"
)

const (
	simTime       = time.Second / 60
	updateTime    = time.Second / 24
	pauseTime     = time.Second
	headStartTime = time.Second
	maxScore      = 10
	fieldWidth    = 1000
	fieldHeight   = 500
	edgeRadius    = 8
	goalSize      = 200
	playerRadius  = 10
	playerMass    = 1
	ballRadius    = 10
	ballMass      = 0.1
)

const (
	normLayer = 1 << iota
	goalLayer
)

type player struct {
	id          gordian.ClientId
	team        int
	body        chipmunk.Body
	shape       chipmunk.Shape
	cursorBody  chipmunk.Body
	cursorJoint chipmunk.Constraint
}

func (p *player) place() {
	widthRange := 0.5*fieldWidth - 2*playerRadius
	heightRange := 0.5*fieldHeight - playerRadius
	sign := float64(2*p.team - 1)
	pos := chipmunk.Vect{
		(widthRange*rand.Float64() + playerRadius) * sign,
		heightRange * (2*rand.Float64() - 1),
	}
	minDist := 0.25*fieldHeight + playerRadius
	dist := pos.Length()
	if dist < minDist {
		pos = pos.Mul(minDist / dist)
	}
	p.body.SetPosition(pos)
}

type ball struct {
	body  chipmunk.Body
	shape chipmunk.Shape
}

type Player struct {
	Pos  chipmunk.Vect
	Team int
}

type Ball struct {
	Pos chipmunk.Vect
}

type configMsg struct {
	Id           string
	FieldWidth   float64
	FieldHeight  float64
	GoalSize     float64
	PlayerRadius float64
	BallRadius   float64
}

type stateMsg struct {
	Players map[string]Player
	Ball    Ball
	Score   []int
}

type Gol struct {
	players     map[gordian.ClientId]*player
	ball        ball
	score       []int
	pauseTicks  []int
	simTimer    <-chan time.Time
	updateTimer <-chan time.Time
	curId       int
	space       *chipmunk.Space
	mu          sync.Mutex
	*gordian.Gordian
}

func New() *Gol {
	g := &Gol{
		players:     map[gordian.ClientId]*player{},
		score:       []int{0, 0},
		pauseTicks:  []int{0, 0},
		simTimer:    time.Tick(simTime),
		updateTimer: time.Tick(updateTime),
		Gordian:     gordian.New(24),
	}
	g.setup()
	return g
}

func (g *Gol) setup() {
	g.space = chipmunk.SpaceNew()
	g.space.SetDamping(0.1)
	hfw, hfh, hgs := 0.5*fieldWidth, 0.5*fieldHeight, 0.5*goalSize
	sidePts := []chipmunk.Vect{{-hfw, hgs}, {-hfw, hfh}, {hfw, hfh}, {hfw, hgs}}
	numSideSegs := len(sidePts) - 1
	for i := 0; i < 2; i++ {
		sign := float64(2*i - 1)
		for j := 0; j < numSideSegs; j++ {
			p0, p1 := sidePts[j], sidePts[j+1]
			p0.Y *= sign
			p1.Y *= sign
			fieldSeg := chipmunk.SegmentShapeNew(g.space.StaticBody(), p0, p1, edgeRadius)
			fieldSeg.SetLayers(normLayer)
			fieldSeg.SetElasticity(1.0)
			fieldSeg.SetFriction(1.0)
			g.space.AddShape(fieldSeg)
		}
		p0, p1 := chipmunk.Vect{sign * hfw, -hgs}, chipmunk.Vect{sign * hfw, hgs}
		goal := chipmunk.SegmentShapeNew(g.space.StaticBody(), p0, p1, edgeRadius)
		goal.SetLayers(goalLayer)
		goal.SetElasticity(1.0)
		goal.SetFriction(1.0)
		g.space.AddShape(goal)
	}
	moment := chipmunk.MomentForCircle(ballMass, 0, ballRadius, chipmunk.Origin())
	g.ball.body = chipmunk.BodyNew(ballMass, moment)
	g.space.AddBody(g.ball.body)
	g.ball.shape = chipmunk.CircleShapeNew(g.ball.body, ballRadius, chipmunk.Origin())
	g.ball.shape.SetLayers(normLayer)
	g.ball.shape.SetElasticity(0.9)
	g.ball.shape.SetFriction(0.1)
	g.space.AddShape(g.ball.shape)
}

func (g *Gol) Run() {
	go g.run()
	go g.sim()
	g.Gordian.Run()
}

func (g *Gol) run() {
	for {
		select {
		case client := <-g.Control:
			g.clientCtrl(client)
		case msg := <-g.InBox:
			g.handleMessage(&msg)
		case <-g.updateTimer:
			g.update()
		}
	}
}

func (g *Gol) sim() {
	for {
		<-g.simTimer

		g.mu.Lock()

		g.space.Step(simTime.Seconds())
		g.handlePauses()
		g.handleGoals()

		g.mu.Unlock()
	}
}

func (g *Gol) handlePauses() {
	// enable control if pause is ending
	for _, player := range g.players {
		if g.pauseTicks[player.team] == 1 && player.cursorJoint.Space() != g.space {
			g.space.AddConstraint(player.cursorJoint)
		}
	}
	// update pause countdown
	for i := range g.pauseTicks {
		if g.pauseTicks[i] > 0 {
			g.pauseTicks[i]--
		}
	}
}

func (g *Gol) handleGoals() {
	ballX := g.ball.body.Position().X
	if math.Abs(ballX) > fieldWidth/2 { // GOL!
		team := 0
		if ballX < 0 {
			team = 1
		}
		g.score[team]++
		if g.score[0] >= maxScore || g.score[1] >= maxScore {
			g.score[0], g.score[1] = 0, 0
		}
		g.kickoff(team)
	}
}

func (g *Gol) kickoff(team int) {
	otherTeam := 1 - team

	g.ball.body.SetPosition(chipmunk.Vect{})
	g.ball.body.SetVelocity(chipmunk.Vect{})
	for _, player := range g.players {
		player.place()
		player.body.SetVelocity(chipmunk.Vect{})
		if g.pauseTicks[player.team] == 0 && player.cursorJoint.Space() == g.space {
			// disable control for a bit
			g.space.RemoveConstraint(player.cursorJoint)
		}
	}
	// give the team that was scored on a little head start for "kickoff"
	g.pauseTicks[team] = int((pauseTime + headStartTime) / simTime)
	g.pauseTicks[otherTeam] = int(pauseTime / simTime)
}

func (g *Gol) clientCtrl(client *gordian.Client) {
	switch client.Ctrl {
	case gordian.Connect:
		g.connect(client)
	case gordian.Close:
		g.close(client)
	}
}

func (g *Gol) nextTeam() int {
	teamSize := []int{0, 0}
	for _, player := range g.players {
		teamSize[player.team]++
	}
	switch {
	case teamSize[0] < teamSize[1]:
		return 0
	case teamSize[0] > teamSize[1]:
		return 1
	default:
		return rand.Intn(2)
	}
}

func (g *Gol) addPlayer(id gordian.ClientId) *player {
	player := &player{id: id, team: g.nextTeam()}

	moment := chipmunk.MomentForCircle(playerMass, 0, playerRadius, chipmunk.Origin())
	player.body = chipmunk.BodyNew(playerMass, moment)
	g.space.AddBody(player.body)

	player.shape = chipmunk.CircleShapeNew(player.body, playerRadius, chipmunk.Origin())
	player.shape.SetLayers(normLayer | goalLayer)
	player.shape.SetElasticity(0.9)
	player.shape.SetFriction(0.1)
	g.space.AddShape(player.shape)

	player.cursorBody = chipmunk.BodyNew(math.Inf(0), math.Inf(0))
	player.cursorJoint = chipmunk.PivotJointNew2(player.cursorBody, player.body,
		chipmunk.Vect{}, chipmunk.Vect{})
	player.cursorJoint.SetMaxForce(1000.0)
	g.space.AddConstraint(player.cursorJoint)

	g.players[player.id] = player

	return player
}

func (g *Gol) removePlayer(id gordian.ClientId) {
	player, ok := g.players[id]
	if !ok {
		return
	}
	if player.cursorJoint.Space() == g.space {
		g.space.RemoveConstraint(player.cursorJoint)
	}
	player.cursorJoint.Free()
	g.space.RemoveBody(player.body)
	g.space.RemoveShape(player.shape)
	player.body.Free()
	player.shape.Free()
	player.cursorBody.Free()

	delete(g.players, id)
}

func (g *Gol) connect(client *gordian.Client) {
	g.curId++

	client.Id = g.curId
	client.Ctrl = gordian.Register
	g.Control <- client
	client = <-g.Control
	if client.Ctrl != gordian.Establish {
		return
	}

	g.mu.Lock()

	player := g.addPlayer(client.Id)
	player.place()

	g.mu.Unlock()

	data := configMsg{
		FieldWidth:   fieldWidth,
		FieldHeight:  fieldHeight,
		GoalSize:     goalSize,
		PlayerRadius: playerRadius,
		BallRadius:   ballRadius,
		Id:           fmt.Sprintf("%d", client.Id),
	}
	msg := gordian.Message{
		To:   client.Id,
		Type: "config",
		Data: data,
	}
	g.OutBox <- msg
}

func (g *Gol) close(client *gordian.Client) {
	g.mu.Lock()
	defer g.mu.Unlock()

	g.removePlayer(client.Id)
}

func (g *Gol) handleMessage(msg *gordian.Message) {
	g.mu.Lock()
	defer g.mu.Unlock()

	id := msg.From
	player, ok := g.players[id]
	if !ok {
		return
	}
	switch msg.Type {
	case "player":
		state := &Player{}
		err := msg.Unmarshal(state)
		if err != nil {
			return
		}
		player.cursorBody.SetPosition(state.Pos)
	}
}

func (g *Gol) update() {
	g.mu.Lock()

	state := stateMsg{
		Players: map[string]Player{},
		Ball:    Ball{g.ball.body.Position()},
		Score:   g.score,
	}
	for i, player := range g.players {
		state.Players[fmt.Sprintf("%d", i)] = Player{
			Pos:  player.body.Position(),
			Team: player.team,
		}
	}

	g.mu.Unlock()

	msg := gordian.Message{
		Type: "state",
		Data: state,
	}
	for id := range g.players {
		msg.To = id
		g.OutBox <- msg
	}
}
<|endoftext|>


<commit_before>package cfitsio

// #include <stdio.h>
// #include "go-cfitsio.h"
import "C"
import (
	"fmt"
	"io"
	"io/ioutil"
	"unsafe"
)

type HduType int

const (
	IMAGE_HDU  HduType = C.IMAGE_HDU  // Primary Array or IMAGE HDU
	ASCII_TBL  HduType = C.ASCII_TBL  // ASCII table HDU
	BINARY_TBL HduType = C.BINARY_TBL // Binary table HDU
	ANY_HDU    HduType = C.ANY_HDU    // matches any HDU type
)

type Hdu struct {
	Id   int     // id of this HDU (position in the File)
	Type HduType // type of this HDU (Image, ASCII, Binary, ...)
	File *File   // pointer to File hosting this HDU
}

// MovAbsHdu moves to a different HDU in the file
func (f *File) MovAbsHdu(hdu int) (HduType, error) {
	c_hdu := C.int(hdu)
	c_htype := C.int(0)
	c_status := C.int(0)

	C.fits_movabs_hdu(f.c, c_hdu, &c_htype, &c_status)
	if c_status > 0 {
		return HduType(c_htype), to_err(c_status)
	}

	return HduType(c_htype), nil
}

// MovRelHdu moves to a different HDU in the file
func (f *File) MovRelHdu(n int) (HduType, error) {
	c_n := C.int(n)
	c_htype := C.int(0)
	c_status := C.int(0)

	C.fits_movrel_hdu(f.c, c_n, &c_htype, &c_status)
	if c_status > 0 {
		return HduType(c_htype), to_err(c_status)
	}

	return HduType(c_htype), nil
}

// MovNamHdu moves to a different HDU in the file
func (f *File) MovNamHdu(hdu HduType, extname string, extvers int) error {
	c_hdu := C.int(hdu)
	c_name := C.CString(extname)
	defer C.free(unsafe.Pointer(c_name))
	c_vers := C.int(extvers)
	c_status := C.int(0)

	C.fits_movnam_hdu(f.c, c_hdu, c_name, c_vers, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}

	return nil
}

// NumHdus returns the total number of HDUs in the FITS file.
// This returns the number of completely defined HDUs in the file. If a new HDU has just been added to the FITS file, then that last HDU will only be counted if it has been closed, or if data has been written to the HDU. The current HDU remains unchanged by this routine.
func (f *File) NumHdus() (int, error) {
	c_n := C.int(0)
	c_status := C.int(0)
	C.fits_get_num_hdus(f.c, &c_n, &c_status)
	if c_status > 0 {
		return 0, to_err(c_status)
	}

	return int(c_n), nil
}

// HduNum returns the number of the current HDU (CHDU) in the FITS file (where the primary array = 1). This function returns the HDU number rather than a status value.
func (f *File) HduNum() int {

	c_n := C.int(0)
	C.fits_get_hdu_num(f.c, &c_n)
	return int(c_n)
}

// HduType returns the type of the current HDU in the FITS file. The possible values for hdutype are: IMAGE_HDU, ASCII_TBL, or BINARY_TBL.
func (f *File) HduType() (HduType, error) {
	c_hdu := C.int(0)
	c_status := C.int(0)
	C.fits_get_hdu_type(f.c, &c_hdu, &c_status)
	if c_status > 0 {
		return 0, to_err(c_status)
	}

	return HduType(c_hdu), nil
}

// Copy all or part of the HDUs in the FITS file associated with infptr and append them to the end of the FITS file associated with outfptr. If 'previous' is true, then any HDUs preceding the current HDU in the input file will be copied to the output file. Similarly, 'current' and 'following' determine whether the current HDU, and/or any following HDUs in the input file will be copied to the output file. Thus, if all 3 parameters are true, then the entire input file will be copied. On exit, the current HDU in the input file will be unchanged, and the last HDU in the output file will be the current HDU.
func (f *File) Copy(out *File, previous, current, following bool) error {
	c_previous := C.int(0)
	if previous {
		c_previous = C.int(1)
	}
	c_current := C.int(0)
	if current {
		c_current = C.int(1)
	}
	c_following := C.int(0)
	if following {
		c_following = C.int(1)
	}
	c_status := C.int(0)
	C.fits_copy_file(f.c, out.c, c_previous, c_current, c_following, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}
	return nil
}

// Copy the current HDU from the FITS file associated with infptr and append it to the end of the FITS file associated with outfptr. Space may be reserved for MOREKEYS additional keywords in the output header.
func (f *File) CopyHdu(out *File, morekeys int) error {
	c_morekeys := C.int(morekeys)
	c_status := C.int(0)
	C.fits_copy_hdu(f.c, out.c, c_morekeys, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}
	return nil
}

// Write the current HDU in the input FITS file to the output FILE stream (e.g., to stdout).
func (f *File) WriteHdu(w io.Writer) error {
	tmp, err := ioutil.TempFile("", "go-cfitsio-")
	if err != nil {
		return err
	}
	defer tmp.Close()

	c_mode := C.CString("rw+")
	defer C.free(unsafe.Pointer(c_mode))
	fstream := C.fdopen(C.int(tmp.Fd()), c_mode)
	c_status := C.int(0)
	C.fits_write_hdu(f.c, fstream, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}
	C.fflush(fstream)

	_, err = tmp.Seek(0, 0)
	if err != nil {
		return err
	}

	_, err = io.Copy(w, tmp)
	return err
}

// Copy the header (and not the data) from the CHDU associated with infptr to the CHDU associated with outfptr. If the current output HDU is not completely empty, then the CHDU will be closed and a new HDU will be appended to the output file. An empty output data unit will be created with all values initially = 0).
func (f *File) CopyHeader(out *File) error {
	c_status := C.int(0)
	C.fits_copy_header(f.c, out.c, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}
	return nil
}

// Delete the CHDU in the FITS file. Any following HDUs will be shifted forward in the file, to fill in the gap created by the deleted HDU. In the case of deleting the primary array (the first HDU in the file) then the current primary array will be replace by a null primary array containing the minimum set of required keywords and no data. If there are more extensions in the file following the one that is deleted, then the the CHDU will be redefined to point to the following extension. If there are no following extensions then the CHDU will be redefined to point to the previous HDU. The output hdutype parameter returns the type of the new CHDU. A null pointer may be given for hdutype if the returned value is not needed.
func (f *File) DeleteHdu() (HduType, error) {
	c_hdu := C.int(0)
	c_status := C.int(0)
	C.fits_delete_hdu(f.c, &c_hdu, &c_status)
	if c_status > 0 {
		return 0, to_err(c_status)
	}
	return HduType(c_hdu), nil
}

// EOF
<commit_msg>hdu: impl Stringer for HduType<commit_after>package cfitsio

// #include <stdio.h>
// #include "go-cfitsio.h"
import "C"
import (
	"fmt"
	"io"
	"io/ioutil"
	"unsafe"
)

type HduType int

const (
	IMAGE_HDU  HduType = C.IMAGE_HDU  // Primary Array or IMAGE HDU
	ASCII_TBL  HduType = C.ASCII_TBL  // ASCII table HDU
	BINARY_TBL HduType = C.BINARY_TBL // Binary table HDU
	ANY_HDU    HduType = C.ANY_HDU    // matches any HDU type
)

func (hdu HduType) String() string {
	switch hdu {
	case IMAGE_HDU:
		return "IMAGE_HDU"
	case ASCII_TBL:
		return "ASCII_TBL"
	case BINARY_TBL:
		return "BINARY_TBL"
	case ANY_HDU:
		return "ANY_HDU"
	default:
		panic(fmt.Errorf("invalid HduType value (%v)", int(hdu)))
	}
}

type Hdu struct {
	Id   int     // id of this HDU (position in the File)
	Type HduType // type of this HDU (Image, ASCII, Binary, ...)
	File *File   // pointer to File hosting this HDU
}

// MovAbsHdu moves to a different HDU in the file
func (f *File) MovAbsHdu(hdu int) (HduType, error) {
	c_hdu := C.int(hdu)
	c_htype := C.int(0)
	c_status := C.int(0)

	C.fits_movabs_hdu(f.c, c_hdu, &c_htype, &c_status)
	if c_status > 0 {
		return HduType(c_htype), to_err(c_status)
	}

	return HduType(c_htype), nil
}

// MovRelHdu moves to a different HDU in the file
func (f *File) MovRelHdu(n int) (HduType, error) {
	c_n := C.int(n)
	c_htype := C.int(0)
	c_status := C.int(0)

	C.fits_movrel_hdu(f.c, c_n, &c_htype, &c_status)
	if c_status > 0 {
		return HduType(c_htype), to_err(c_status)
	}

	return HduType(c_htype), nil
}

// MovNamHdu moves to a different HDU in the file
func (f *File) MovNamHdu(hdu HduType, extname string, extvers int) error {
	c_hdu := C.int(hdu)
	c_name := C.CString(extname)
	defer C.free(unsafe.Pointer(c_name))
	c_vers := C.int(extvers)
	c_status := C.int(0)

	C.fits_movnam_hdu(f.c, c_hdu, c_name, c_vers, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}

	return nil
}

// NumHdus returns the total number of HDUs in the FITS file.
// This returns the number of completely defined HDUs in the file. If a new HDU has just been added to the FITS file, then that last HDU will only be counted if it has been closed, or if data has been written to the HDU. The current HDU remains unchanged by this routine.
func (f *File) NumHdus() (int, error) {
	c_n := C.int(0)
	c_status := C.int(0)
	C.fits_get_num_hdus(f.c, &c_n, &c_status)
	if c_status > 0 {
		return 0, to_err(c_status)
	}

	return int(c_n), nil
}

// HduNum returns the number of the current HDU (CHDU) in the FITS file (where the primary array = 1). This function returns the HDU number rather than a status value.
func (f *File) HduNum() int {

	c_n := C.int(0)
	C.fits_get_hdu_num(f.c, &c_n)
	return int(c_n)
}

// HduType returns the type of the current HDU in the FITS file. The possible values for hdutype are: IMAGE_HDU, ASCII_TBL, or BINARY_TBL.
func (f *File) HduType() (HduType, error) {
	c_hdu := C.int(0)
	c_status := C.int(0)
	C.fits_get_hdu_type(f.c, &c_hdu, &c_status)
	if c_status > 0 {
		return 0, to_err(c_status)
	}

	return HduType(c_hdu), nil
}

// Copy all or part of the HDUs in the FITS file associated with infptr and append them to the end of the FITS file associated with outfptr. If 'previous' is true, then any HDUs preceding the current HDU in the input file will be copied to the output file. Similarly, 'current' and 'following' determine whether the current HDU, and/or any following HDUs in the input file will be copied to the output file. Thus, if all 3 parameters are true, then the entire input file will be copied. On exit, the current HDU in the input file will be unchanged, and the last HDU in the output file will be the current HDU.
func (f *File) Copy(out *File, previous, current, following bool) error {
	c_previous := C.int(0)
	if previous {
		c_previous = C.int(1)
	}
	c_current := C.int(0)
	if current {
		c_current = C.int(1)
	}
	c_following := C.int(0)
	if following {
		c_following = C.int(1)
	}
	c_status := C.int(0)
	C.fits_copy_file(f.c, out.c, c_previous, c_current, c_following, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}
	return nil
}

// Copy the current HDU from the FITS file associated with infptr and append it to the end of the FITS file associated with outfptr. Space may be reserved for MOREKEYS additional keywords in the output header.
func (f *File) CopyHdu(out *File, morekeys int) error {
	c_morekeys := C.int(morekeys)
	c_status := C.int(0)
	C.fits_copy_hdu(f.c, out.c, c_morekeys, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}
	return nil
}

// Write the current HDU in the input FITS file to the output FILE stream (e.g., to stdout).
func (f *File) WriteHdu(w io.Writer) error {
	tmp, err := ioutil.TempFile("", "go-cfitsio-")
	if err != nil {
		return err
	}
	defer tmp.Close()

	c_mode := C.CString("rw+")
	defer C.free(unsafe.Pointer(c_mode))
	fstream := C.fdopen(C.int(tmp.Fd()), c_mode)
	c_status := C.int(0)
	C.fits_write_hdu(f.c, fstream, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}
	C.fflush(fstream)

	_, err = tmp.Seek(0, 0)
	if err != nil {
		return err
	}

	_, err = io.Copy(w, tmp)
	return err
}

// Copy the header (and not the data) from the CHDU associated with infptr to the CHDU associated with outfptr. If the current output HDU is not completely empty, then the CHDU will be closed and a new HDU will be appended to the output file. An empty output data unit will be created with all values initially = 0).
func (f *File) CopyHeader(out *File) error {
	c_status := C.int(0)
	C.fits_copy_header(f.c, out.c, &c_status)
	if c_status > 0 {
		return to_err(c_status)
	}
	return nil
}

// Delete the CHDU in the FITS file. Any following HDUs will be shifted forward in the file, to fill in the gap created by the deleted HDU. In the case of deleting the primary array (the first HDU in the file) then the current primary array will be replace by a null primary array containing the minimum set of required keywords and no data. If there are more extensions in the file following the one that is deleted, then the the CHDU will be redefined to point to the following extension. If there are no following extensions then the CHDU will be redefined to point to the previous HDU. The output hdutype parameter returns the type of the new CHDU. A null pointer may be given for hdutype if the returned value is not needed.
func (f *File) DeleteHdu() (HduType, error) {
	c_hdu := C.int(0)
	c_status := C.int(0)
	C.fits_delete_hdu(f.c, &c_hdu, &c_status)
	if c_status > 0 {
		return 0, to_err(c_status)
	}
	return HduType(c_hdu), nil
}

// EOF
<|endoftext|>