<commit_before>package docker

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"syscall"
)

type ChangeType int

const (
	ChangeModify = iota
	ChangeAdd
	ChangeDelete
)

type Change struct {
	Path string
	Kind ChangeType
}

func (change *Change) String() string {
	var kind string
	switch change.Kind {
	case ChangeModify:
		kind = "C"
	case ChangeAdd:
		kind = "A"
	case ChangeDelete:
		kind = "D"
	}
	return fmt.Sprintf("%s %s", kind, change.Path)
}

func ChangesAUFS(layers []string, rw string) ([]Change, error) {
	var changes []Change
	err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Rebase path
		path, err = filepath.Rel(rw, path)
		if err != nil {
			return err
		}
		path = filepath.Join("/", path)

		// Skip root
		if path == "/" {
			return nil
		}

		// Skip AUFS metadata
		if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
			return err
		}

		change := Change{
			Path: path,
		}

		// Find out what kind of modification happened
		file := filepath.Base(path)
		// If there is a whiteout, then the file was removed
		if strings.HasPrefix(file, ".wh.") {
			originalFile := file[len(".wh."):]
			change.Path = filepath.Join(filepath.Dir(path), originalFile)
			change.Kind = ChangeDelete
		} else {
			// Otherwise, the file was added
			change.Kind = ChangeAdd

			// ...Unless it already existed in a top layer, in which case, it's a modification
			for _, layer := range layers {
				stat, err := os.Stat(filepath.Join(layer, path))
				if err != nil && !os.IsNotExist(err) {
					return err
				}
				if err == nil {
					// The file existed in the top layer, so that's a modification

					// However, if it's a directory, maybe it wasn't actually modified.
					// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
					if stat.IsDir() && f.IsDir() {
						if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
							// Both directories are the same, don't record the change
							return nil
						}
					}
					change.Kind = ChangeModify
					break
				}
			}
		}

		// Record change
		changes = append(changes, change)
		return nil
	})
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}
	return changes, nil
}

type FileInfo struct {
	parent *FileInfo
	name string
	stat syscall.Stat_t
	children map[string]*FileInfo
}

func (root *FileInfo) LookUp(path string) *FileInfo {
	parent := root
	if path == "/" {
		return root
	}

	pathElements := strings.Split(path, "/")
	for _, elem := range pathElements {
		if elem != "" {
			child := parent.children[elem]
			if child == nil {
				return nil
			}
			parent = child
		}
	}
	return parent
}

func (info *FileInfo)path() string {
	if info.parent == nil {
		return "/"
	}
	return filepath.Join(info.parent.path(), info.name)
}

func (info *FileInfo)unlink() {
	if info.parent != nil {
		delete(info.parent.children, info.name)
	}
}

func (info *FileInfo)Remove(path string) bool {
	child := info.LookUp(path)
	if child != nil {
		child.unlink()
		return true
	}
	return false
}

func (info *FileInfo)isDir() bool {
	return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
}

func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) {
	if oldInfo == nil {
		// add
		change := Change{
			Path: info.path(),
			Kind: ChangeAdd,
		}
		*changes = append(*changes, change)
	}

	// We make a copy so we can modify it to detect additions
	// also, we only recurse on the old dir if the new info is a directory
	// otherwise any previous delete/change is considered recursive
	oldChildren := make(map[string]*FileInfo)
	if oldInfo != nil && info.isDir() {
		for k, v := range oldInfo.children {
			oldChildren[k] = v
		}
	}

	for name, newChild := range info.children {
		oldChild, _ := oldChildren[name]
		if oldChild != nil {
			// change?
			oldStat := &oldChild.stat
			newStat := &newChild.stat
			// Note: We can't compare inode or ctime or blocksize here, because these change
			// when copying a file into a container. However, that is not generally a problem
			// because any content change will change mtime, and any status change should
			// be visible when actually comparing the stat fields. The only time this
			// breaks down is if some code intentionally hides a change by setting
			// back mtime
			oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
			newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
			if oldStat.Mode != newStat.Mode ||
				oldStat.Uid != newStat.Uid ||
				oldStat.Gid != newStat.Gid ||
				oldStat.Rdev != newStat.Rdev ||
				// Don't look at size for dirs, its not a good measure of change
				(oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) ||
				oldMtime.Sec != newMtime.Sec ||
				oldMtime.Usec != newMtime.Usec {
				change := Change{
					Path: newChild.path(),
					Kind: ChangeModify,
				}
				*changes = append(*changes, change)
			}

			// Remove from copy so we can detect deletions
			delete(oldChildren, name)
		}

		newChild.addChanges(oldChild, changes)
	}
	for _, oldChild := range oldChildren {
		// delete
		change := Change{
			Path: oldChild.path(),
			Kind: ChangeDelete,
		}
		*changes = append(*changes, change)
	}

}

func (info *FileInfo)Changes(oldInfo *FileInfo) []Change {
	var changes []Change

	info.addChanges(oldInfo, &changes)

	return changes
}

func collectFileInfo(sourceDir string) (*FileInfo, error) {
	root := &FileInfo {
		name: "/",
		children: make(map[string]*FileInfo),
	}

	err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Rebase path
		relPath, err := filepath.Rel(sourceDir, path)
		if err != nil {
			return err
		}
		relPath = filepath.Join("/", relPath)

		if relPath == "/" {
			return nil
		}

		parent := root.LookUp(filepath.Dir(relPath))
		if parent == nil {
			return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
		}

		info := &FileInfo {
			name: filepath.Base(relPath),
			children: make(map[string]*FileInfo),
			parent: parent,
		}

		if err := syscall.Lstat(path, &info.stat); err != nil {
			return err
		}

		parent.children[info.name] = info

		return nil
	})
	if err != nil {
		return nil, err
	}
	return root, nil
}

func ChangesDirs(newDir, oldDir string) ([]Change, error) {
	oldRoot, err := collectFileInfo(oldDir)
	if err != nil {
		return nil, err
	}
	newRoot, err := collectFileInfo(newDir)
	if err != nil {
		return nil, err
	}

	// Ignore changes in .docker-id
	_ = newRoot.Remove("/.docker-id")
	_ = oldRoot.Remove("/.docker-id")

	return newRoot.Changes(oldRoot), nil
}
<commit_msg>Add Changes.ChangesLayers()<commit_after>package docker

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"syscall"
)

type ChangeType int

const (
	ChangeModify = iota
	ChangeAdd
	ChangeDelete
)

type Change struct {
	Path string
	Kind ChangeType
}

func (change *Change) String() string {
	var kind string
	switch change.Kind {
	case ChangeModify:
		kind = "C"
	case ChangeAdd:
		kind = "A"
	case ChangeDelete:
		kind = "D"
	}
	return fmt.Sprintf("%s %s", kind, change.Path)
}

func ChangesAUFS(layers []string, rw string) ([]Change, error) {
	var changes []Change
	err := filepath.Walk(rw, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Rebase path
		path, err = filepath.Rel(rw, path)
		if err != nil {
			return err
		}
		path = filepath.Join("/", path)

		// Skip root
		if path == "/" {
			return nil
		}

		// Skip AUFS metadata
		if matched, err := filepath.Match("/.wh..wh.*", path); err != nil || matched {
			return err
		}

		change := Change{
			Path: path,
		}

		// Find out what kind of modification happened
		file := filepath.Base(path)
		// If there is a whiteout, then the file was removed
		if strings.HasPrefix(file, ".wh.") {
			originalFile := file[len(".wh."):]
			change.Path = filepath.Join(filepath.Dir(path), originalFile)
			change.Kind = ChangeDelete
		} else {
			// Otherwise, the file was added
			change.Kind = ChangeAdd

			// ...Unless it already existed in a top layer, in which case, it's a modification
			for _, layer := range layers {
				stat, err := os.Stat(filepath.Join(layer, path))
				if err != nil && !os.IsNotExist(err) {
					return err
				}
				if err == nil {
					// The file existed in the top layer, so that's a modification

					// However, if it's a directory, maybe it wasn't actually modified.
					// If you modify /foo/bar/baz, then /foo will be part of the changed files only because it's the parent of bar
					if stat.IsDir() && f.IsDir() {
						if f.Size() == stat.Size() && f.Mode() == stat.Mode() && f.ModTime() == stat.ModTime() {
							// Both directories are the same, don't record the change
							return nil
						}
					}
					change.Kind = ChangeModify
					break
				}
			}
		}

		// Record change
		changes = append(changes, change)
		return nil
	})
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}
	return changes, nil
}

type FileInfo struct {
	parent *FileInfo
	name string
	stat syscall.Stat_t
	children map[string]*FileInfo
}

func (root *FileInfo) LookUp(path string) *FileInfo {
	parent := root
	if path == "/" {
		return root
	}

	pathElements := strings.Split(path, "/")
	for _, elem := range pathElements {
		if elem != "" {
			child := parent.children[elem]
			if child == nil {
				return nil
			}
			parent = child
		}
	}
	return parent
}

func (info *FileInfo)path() string {
	if info.parent == nil {
		return "/"
	}
	return filepath.Join(info.parent.path(), info.name)
}

func (info *FileInfo)unlink() {
	if info.parent != nil {
		delete(info.parent.children, info.name)
	}
}

func (info *FileInfo)Remove(path string) bool {
	child := info.LookUp(path)
	if child != nil {
		child.unlink()
		return true
	}
	return false
}

func (info *FileInfo)isDir() bool {
	return info.parent == nil || info.stat.Mode&syscall.S_IFDIR == syscall.S_IFDIR
}

func (info *FileInfo)addChanges(oldInfo *FileInfo, changes *[]Change) {
	if oldInfo == nil {
		// add
		change := Change{
			Path: info.path(),
			Kind: ChangeAdd,
		}
		*changes = append(*changes, change)
	}

	// We make a copy so we can modify it to detect additions
	// also, we only recurse on the old dir if the new info is a directory
	// otherwise any previous delete/change is considered recursive
	oldChildren := make(map[string]*FileInfo)
	if oldInfo != nil && info.isDir() {
		for k, v := range oldInfo.children {
			oldChildren[k] = v
		}
	}

	for name, newChild := range info.children {
		oldChild, _ := oldChildren[name]
		if oldChild != nil {
			// change?
			oldStat := &oldChild.stat
			newStat := &newChild.stat
			// Note: We can't compare inode or ctime or blocksize here, because these change
			// when copying a file into a container. However, that is not generally a problem
			// because any content change will change mtime, and any status change should
			// be visible when actually comparing the stat fields. The only time this
			// breaks down is if some code intentionally hides a change by setting
			// back mtime
			oldMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
			newMtime := syscall.NsecToTimeval(oldStat.Mtim.Nano())
			if oldStat.Mode != newStat.Mode ||
				oldStat.Uid != newStat.Uid ||
				oldStat.Gid != newStat.Gid ||
				oldStat.Rdev != newStat.Rdev ||
				// Don't look at size for dirs, its not a good measure of change
				(oldStat.Size != newStat.Size && oldStat.Mode &syscall.S_IFDIR != syscall.S_IFDIR) ||
				oldMtime.Sec != newMtime.Sec ||
				oldMtime.Usec != newMtime.Usec {
				change := Change{
					Path: newChild.path(),
					Kind: ChangeModify,
				}
				*changes = append(*changes, change)
			}

			// Remove from copy so we can detect deletions
			delete(oldChildren, name)
		}

		newChild.addChanges(oldChild, changes)
	}
	for _, oldChild := range oldChildren {
		// delete
		change := Change{
			Path: oldChild.path(),
			Kind: ChangeDelete,
		}
		*changes = append(*changes, change)
	}

}

func (info *FileInfo)Changes(oldInfo *FileInfo) []Change {
	var changes []Change

	info.addChanges(oldInfo, &changes)

	return changes
}

func newRootFileInfo() *FileInfo {
	root := &FileInfo {
		name: "/",
		children: make(map[string]*FileInfo),
	}
	return root
}

func applyLayer(root *FileInfo, layer string) error {
	err := filepath.Walk(layer, func(layerPath string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Skip root
		if layerPath == layer {
			return nil
		}

		// rebase path
		relPath, err := filepath.Rel(layer, layerPath)
		if err != nil {
			return err
		}
		relPath = filepath.Join("/", relPath)

		// Skip AUFS metadata
		if matched, err := filepath.Match("/.wh..wh.*", relPath); err != nil || matched {
			if err != nil || !f.IsDir() {
				return err
			}
			return filepath.SkipDir
		}

		var layerStat syscall.Stat_t
		err = syscall.Lstat(layerPath, &layerStat)
		if err != nil {
			return err
		}

		file := filepath.Base(relPath)
		// If there is a whiteout, then the file was removed
		if strings.HasPrefix(file, ".wh.") {
			originalFile := file[len(".wh."):]
			deletePath := filepath.Join(filepath.Dir(relPath), originalFile)

			root.Remove(deletePath)
		} else {
			// Added or changed file
			existing := root.LookUp(relPath)
			if existing != nil {
				// Changed file
				existing.stat = layerStat
				if !existing.isDir() {
					// Changed from dir to non-dir, delete all previous files
					existing.children = make(map[string]*FileInfo)
				}
			} else {
				// Added file
				parent := root.LookUp(filepath.Dir(relPath))
				if parent == nil {
					return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
				}

				info := &FileInfo {
					name: filepath.Base(relPath),
					children: make(map[string]*FileInfo),
					parent: parent,
					stat: layerStat,
				}

				parent.children[info.name] = info
			}
		}
		return nil
	})
	return err
}

func collectFileInfo(sourceDir string) (*FileInfo, error) {
	root := newRootFileInfo()

	err := filepath.Walk(sourceDir, func(path string, f os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		// Rebase path
		relPath, err := filepath.Rel(sourceDir, path)
		if err != nil {
			return err
		}
		relPath = filepath.Join("/", relPath)

		if relPath == "/" {
			return nil
		}

		parent := root.LookUp(filepath.Dir(relPath))
		if parent == nil {
			return fmt.Errorf("collectFileInfo: Unexpectedly no parent for %s", relPath)
		}

		info := &FileInfo {
			name: filepath.Base(relPath),
			children: make(map[string]*FileInfo),
			parent: parent,
		}

		if err := syscall.Lstat(path, &info.stat); err != nil {
			return err
		}

		parent.children[info.name] = info

		return nil
	})
	if err != nil {
		return nil, err
	}
	return root, nil
}

func ChangesLayers(newDir string, layers []string) ([]Change, error) {
	newRoot, err := collectFileInfo(newDir)
	if err != nil {
		return nil, err
	}
	oldRoot := newRootFileInfo()
	for i := len(layers)-1; i >= 0; i-- {
		layer := layers[i]
		if err = applyLayer(oldRoot, layer); err != nil {
			return nil, err
		}
	}

	return newRoot.Changes(oldRoot), nil
}

func ChangesDirs(newDir, oldDir string) ([]Change, error) {
	oldRoot, err := collectFileInfo(oldDir)
	if err != nil {
		return nil, err
	}
	newRoot, err := collectFileInfo(newDir)
	if err != nil {
		return nil, err
	}

	// Ignore changes in .docker-id
	_ = newRoot.Remove("/.docker-id")
	_ = oldRoot.Remove("/.docker-id")

	return newRoot.Changes(oldRoot), nil
}
<|endoftext|>


<commit_before>package ari

import (
	"strings"

	"github.com/pkg/errors"
	"github.com/satori/go.uuid"
)

// Channel represents a communication path interacting with an Asterisk server.
type Channel interface {
	// Get returns a handle to a channel for further interaction
	Get(id string) *ChannelHandle

	// List lists the channels in asterisk
	List() ([]*ChannelHandle, error)

	// Create creates a new channel, returning a handle to it or an
	// error, if the creation failed
	Create(OriginateRequest) (*ChannelHandle, error)

	// Data returns the channel data for a given channel
	Data(id string) (ChannelData, error)

	// Continue tells Asterisk to return a channel to the dialplan
	Continue(id, context, extension, priority string) error

	// Busy hangs up the channel with the "busy" cause code
	Busy(id string) error

	// Congestion hangs up the channel with the "congestion" cause code
	Congestion(id string) error

	// Answer answers the channel
	Answer(id string) error

	// Hangup hangs up the given channel
	Hangup(id string, reason string) error

	// Ring indicates ringing to the channel
	Ring(id string) error

	// StopRing stops ringing on the channel
	StopRing(id string) error

	// SendDTMF sends DTMF to the channel
	SendDTMF(id string, dtmf string) error

	// Hold puts the channel on hold
	Hold(id string) error

	// StopHold retrieves the channel from hold
	StopHold(id string) error

	// Mute mutes a channel in the given direction (in,out,both)
	Mute(id string, dir string) error

	// Unmute unmutes a channel in the given direction (in,out,both)
	Unmute(id string, dir string) error

	// MOH plays music on hold
	MOH(id string, moh string) error

	// StopMOH stops music on hold
	StopMOH(id string) error

	// Silence plays silence to the channel
	Silence(id string) error

	// StopSilence stops the silence on the channel
	StopSilence(id string) error

	// Play plays the media URI to the channel
	Play(id string, playbackID string, mediaURI string) (*PlaybackHandle, error)
}

// ChannelData is the data for a specific channel
type ChannelData struct {
	ID           string      `json:"id"`    // Unique id for this channel (same as for AMI)
	Name         string      `json:"name"`  // Name of this channel (tech/name-id format)
	State        string      `json:"state"` // State of the channel
	Accountcode  string      `json:"accountcode"`
	Caller       CallerID    `json:"caller"`    // CallerId of the calling endpoint
	Connected    CallerID    `json:"connected"` // CallerId of the connected line
	Creationtime DateTime    `json:"creationtime"`
	Dialplan     DialplanCEP `json:"dialplan"` // Current location in the dialplan
}

// NewChannelHandle returns a handle to the given ARI channel
func NewChannelHandle(id string, c Channel) *ChannelHandle {
	return &ChannelHandle{
		id: id,
		c:  c,
	}
}

// ChannelHandle provides a wrapper to a Channel interface for
// operations on a particular channel ID
type ChannelHandle struct {
	id string  // id of the channel on which we are operating
	c  Channel // the Channel interface with which we are operating
}

// Data returns the channel's data
func (ch *ChannelHandle) Data() (ChannelData, error) {
	return ch.c.Data(ch.id)
}

// Continue tells Asterisk to return the channel to the dialplan
func (ch *ChannelHandle) Continue(context, extension, priority string) error {
	return ch.c.Continue(ch.id, context, extension, priority)
}

//---
// Play operations
//---

// Play initiates playback of the specified media uri
// to the channel, returning the Playback's ID
func (ch *ChannelHandle) Play(mediaURI string) (ph *PlaybackHandle, err error) {
	id := uuid.NewV1().String()
	ph, err = ch.c.Play(ch.id, id, mediaURI)
	return
}

//---
// Hangup Operations
//---

// Busy hangs up the channel with the "busy" cause code
func (ch *ChannelHandle) Busy() error {
	return ch.c.Busy(ch.id)
}

// Congestion hangs up the channel with the congestion cause code
func (ch *ChannelHandle) Congestion() error {
	return ch.c.Congestion(ch.id)
}

// Hangup hangs up the channel with the normal cause code
func (ch *ChannelHandle) Hangup() error {
	return ch.c.Hangup(ch.id, "normal")
}

//--

// --
// Answer operations
// --

// Answer answers the channel
func (ch *ChannelHandle) Answer() error {
	return ch.c.Answer(ch.id)
}

// IsAnswered checks the current state of the channel to see if it is "Up"
func (ch *ChannelHandle) IsAnswered() (bool, error) {
	updated, err := ch.Data()
	if err != nil {
		return false, errors.Wrap(err, "Failed to get updated channel")
	}
	return strings.ToLower(updated.State) == "up", nil
}

// ------

// --
// Ring Operations
// --

// Ring indicates ringing to the channel
func (ch *ChannelHandle) Ring() error {
	return ch.c.Ring(ch.id)
}

// StopRing stops ringing on the channel
func (ch *ChannelHandle) StopRing() error {
	return ch.c.StopRing(ch.id)
}

// ------

// --
// Mute operations
// --

// Mute mutes the channel in the given direction (in, out, both)
func (ch *ChannelHandle) Mute(dir string) (err error) {
	if err = normalizeDirection(&dir); err != nil {
		return
	}

	return ch.c.Mute(ch.id, dir)
}

// Unmute unmutes the channel in the given direction (in, out, both)
func (ch *ChannelHandle) Unmute(dir string) (err error) {
	if err = normalizeDirection(&dir); err != nil {
		return
	}

	return ch.c.Unmute(ch.id, dir)
}

// ----

// --
// Hold operations
// --

// Hold puts the channel on hold
func (ch *ChannelHandle) Hold() error {
	return ch.c.Hold(ch.id)
}

// StopHold retrieves the channel from hold
func (ch *ChannelHandle) StopHold() error {
	return ch.c.StopHold(ch.id)
}

// ----

// --
// Music on hold operations
// --

// MOH plays music on hold of the given class
// to the channel
func (ch *ChannelHandle) MOH(mohClass string) error {
	return ch.c.MOH(ch.id, mohClass)
}

// StopMOH stops playing of music on hold to the channel
func (ch *ChannelHandle) StopMOH() error {
	return ch.c.StopMOH(ch.id)
}

// ----

// --
// Silence operations
// --

// Silence plays silence to the channel
func (ch *ChannelHandle) Silence() error {
	return ch.c.Silence(ch.id)
}

// StopSilence stops silence to the channel
func (ch *ChannelHandle) StopSilence() error {
	return ch.c.StopSilence(ch.id)
}

// ----

// TODO: rest of ChannelHandle
<commit_msg>v3 - channels - Add ID() to Handle<commit_after>package ari

import (
	"strings"

	"github.com/pkg/errors"
	uuid "github.com/satori/go.uuid"
)

// Channel represents a communication path interacting with an Asterisk server.
type Channel interface {
	// Get returns a handle to a channel for further interaction
	Get(id string) *ChannelHandle

	// List lists the channels in asterisk
	List() ([]*ChannelHandle, error)

	// Create creates a new channel, returning a handle to it or an
	// error, if the creation failed
	Create(OriginateRequest) (*ChannelHandle, error)

	// Data returns the channel data for a given channel
	Data(id string) (ChannelData, error)

	// Continue tells Asterisk to return a channel to the dialplan
	Continue(id, context, extension, priority string) error

	// Busy hangs up the channel with the "busy" cause code
	Busy(id string) error

	// Congestion hangs up the channel with the "congestion" cause code
	Congestion(id string) error

	// Answer answers the channel
	Answer(id string) error

	// Hangup hangs up the given channel
	Hangup(id string, reason string) error

	// Ring indicates ringing to the channel
	Ring(id string) error

	// StopRing stops ringing on the channel
	StopRing(id string) error

	// SendDTMF sends DTMF to the channel
	SendDTMF(id string, dtmf string) error

	// Hold puts the channel on hold
	Hold(id string) error

	// StopHold retrieves the channel from hold
	StopHold(id string) error

	// Mute mutes a channel in the given direction (in,out,both)
	Mute(id string, dir string) error

	// Unmute unmutes a channel in the given direction (in,out,both)
	Unmute(id string, dir string) error

	// MOH plays music on hold
	MOH(id string, moh string) error

	// StopMOH stops music on hold
	StopMOH(id string) error

	// Silence plays silence to the channel
	Silence(id string) error

	// StopSilence stops the silence on the channel
	StopSilence(id string) error

	// Play plays the media URI to the channel
	Play(id string, playbackID string, mediaURI string) (*PlaybackHandle, error)
}

// ChannelData is the data for a specific channel
type ChannelData struct {
	ID           string      `json:"id"`    // Unique id for this channel (same as for AMI)
	Name         string      `json:"name"`  // Name of this channel (tech/name-id format)
	State        string      `json:"state"` // State of the channel
	Accountcode  string      `json:"accountcode"`
	Caller       CallerID    `json:"caller"`    // CallerId of the calling endpoint
	Connected    CallerID    `json:"connected"` // CallerId of the connected line
	Creationtime DateTime    `json:"creationtime"`
	Dialplan     DialplanCEP `json:"dialplan"` // Current location in the dialplan
}

// NewChannelHandle returns a handle to the given ARI channel
func NewChannelHandle(id string, c Channel) *ChannelHandle {
	return &ChannelHandle{
		id: id,
		c:  c,
	}
}

// ChannelHandle provides a wrapper to a Channel interface for
// operations on a particular channel ID
type ChannelHandle struct {
	id string  // id of the channel on which we are operating
	c  Channel // the Channel interface with which we are operating
}

// ID returns the identifier for the channel handle
func (ch *ChannelHandle) ID() string {
	return ch.id
}

// Data returns the channel's data
func (ch *ChannelHandle) Data() (ChannelData, error) {
	return ch.c.Data(ch.id)
}

// Continue tells Asterisk to return the channel to the dialplan
func (ch *ChannelHandle) Continue(context, extension, priority string) error {
	return ch.c.Continue(ch.id, context, extension, priority)
}

//---
// Play operations
//---

// Play initiates playback of the specified media uri
// to the channel, returning the Playback's ID
func (ch *ChannelHandle) Play(mediaURI string) (ph *PlaybackHandle, err error) {
	id := uuid.NewV1().String()
	ph, err = ch.c.Play(ch.id, id, mediaURI)
	return
}

//---
// Hangup Operations
//---

// Busy hangs up the channel with the "busy" cause code
func (ch *ChannelHandle) Busy() error {
	return ch.c.Busy(ch.id)
}

// Congestion hangs up the channel with the congestion cause code
func (ch *ChannelHandle) Congestion() error {
	return ch.c.Congestion(ch.id)
}

// Hangup hangs up the channel with the normal cause code
func (ch *ChannelHandle) Hangup() error {
	return ch.c.Hangup(ch.id, "normal")
}

//--

// --
// Answer operations
// --

// Answer answers the channel
func (ch *ChannelHandle) Answer() error {
	return ch.c.Answer(ch.id)
}

// IsAnswered checks the current state of the channel to see if it is "Up"
func (ch *ChannelHandle) IsAnswered() (bool, error) {
	updated, err := ch.Data()
	if err != nil {
		return false, errors.Wrap(err, "Failed to get updated channel")
	}
	return strings.ToLower(updated.State) == "up", nil
}

// ------

// --
// Ring Operations
// --

// Ring indicates ringing to the channel
func (ch *ChannelHandle) Ring() error {
	return ch.c.Ring(ch.id)
}

// StopRing stops ringing on the channel
func (ch *ChannelHandle) StopRing() error {
	return ch.c.StopRing(ch.id)
}

// ------

// --
// Mute operations
// --

// Mute mutes the channel in the given direction (in, out, both)
func (ch *ChannelHandle) Mute(dir string) (err error) {
	if err = normalizeDirection(&dir); err != nil {
		return
	}

	return ch.c.Mute(ch.id, dir)
}

// Unmute unmutes the channel in the given direction (in, out, both)
func (ch *ChannelHandle) Unmute(dir string) (err error) {
	if err = normalizeDirection(&dir); err != nil {
		return
	}

	return ch.c.Unmute(ch.id, dir)
}

// ----

// --
// Hold operations
// --

// Hold puts the channel on hold
func (ch *ChannelHandle) Hold() error {
	return ch.c.Hold(ch.id)
}

// StopHold retrieves the channel from hold
func (ch *ChannelHandle) StopHold() error {
	return ch.c.StopHold(ch.id)
}

// ----

// --
// Music on hold operations
// --

// MOH plays music on hold of the given class
// to the channel
func (ch *ChannelHandle) MOH(mohClass string) error {
	return ch.c.MOH(ch.id, mohClass)
}

// StopMOH stops playing of music on hold to the channel
func (ch *ChannelHandle) StopMOH() error {
	return ch.c.StopMOH(ch.id)
}

// ----

// --
// Silence operations
// --

// Silence plays silence to the channel
func (ch *ChannelHandle) Silence() error {
	return ch.c.Silence(ch.id)
}

// StopSilence stops silence to the channel
func (ch *ChannelHandle) StopSilence() error {
	return ch.c.StopSilence(ch.id)
}

// ----

// TODO: rest of ChannelHandle
<|endoftext|>


<commit_before>package tchannel

// Copyright (c) 2015 Uber Technologies, Inc.

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

import (
	"errors"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"sync"
	"time"

	"golang.org/x/net/context"
)

var (
	errAlreadyListening = errors.New("channel already listening")
)

const (
	clientOnlyHostPort = "0.0.0.0:0"
)

// A Handler is an object hat can be registered with a Channel
// to process incoming calls for a given service and operation
type Handler interface {
	// Handles an incoming call for service
	Handle(ctx context.Context, call *InboundCall)
}

// The HandlerFunc is an adapter to allow the use of ordering functions as
// TChannel handlers.  If f is a function with the appropriate signature,
// HandlerFunc(f) is a Hander object that calls f
type HandlerFunc func(ctx context.Context, call *InboundCall)

// Handle calls f(ctx, call)
func (f HandlerFunc) Handle(ctx context.Context, call *InboundCall) { f(ctx, call) }

// ChannelOptions are used to control parameters on a create a TChannel
type ChannelOptions struct {
	// Default Connection options
	DefaultConnectionOptions ConnectionOptions

	// The name of the process, for logging and reporting to peers
	ProcessName string

	// The logger to use for this channel
	Logger Logger
}

// A Channel is a bi-directional connection to the peering and routing network.  Applications
// can use a Channel to make service calls to remote peers via BeginCall, or to listen for incoming calls
// from peers.  Applications that want to receive requests should call one of Serve or ListenAndServe
type Channel struct {
	log               Logger
	hostPort          string
	processName       string
	connectionOptions ConnectionOptions
	handlers          handlerMap

	mut sync.RWMutex // protects the listener
	l   net.Listener // May be nil if this is a client only channel
}

// NewChannel creates a new Channel.  The new channel can be used to send outbound requests
// to peers, but will not listen or handling incoming requests until one of ListenAndServe
// or Serve is called
func NewChannel(opts *ChannelOptions) (*Channel, error) {
	if opts == nil {
		opts = &ChannelOptions{}
	}

	logger := opts.Logger
	if logger == nil {
		logger = NullLogger
	}

	processName := opts.ProcessName
	if processName == "" {
		processName = fmt.Sprintf("%s[%d]", filepath.Base(os.Args[0]), os.Getpid())
	}

	ch := &Channel{
		connectionOptions: opts.DefaultConnectionOptions,
		processName:       processName,
		hostPort:          clientOnlyHostPort,
		log:               logger,
	}

	ch.connectionOptions.PeerInfo.HostPort = ch.hostPort
	ch.connectionOptions.PeerInfo.ProcessName = ch.processName
	ch.connectionOptions.ChecksumType = ChecksumTypeCrc32
	return ch, nil
}

// Serve serves incoming requests using the provided listener
func (ch *Channel) Serve(l net.Listener) error {
	ch.mut.Lock()

	if ch.l != nil {
		ch.mut.Unlock()
		return errAlreadyListening
	}

	ch.l = l
	ch.connectionOptions.PeerInfo.HostPort = ch.hostPort
	ch.hostPort = ch.l.Addr().String()
	ch.mut.Unlock()

	return ch.serve()
}

// ListenAndServe listens on the given address and serves incoming requests.  The port
// may be 0, in which case the channel will use an OS assigned port
func (ch *Channel) ListenAndServe(hostPort string) error {
	if err := ch.listen(hostPort); err != nil {
		return err
	}

	return ch.serve()
}

// listen listens on the given address but does not begin serving request.
func (ch *Channel) listen(hostPort string) error {
	ch.mut.Lock()
	defer ch.mut.Unlock()

	if ch.l != nil {
		return errAlreadyListening
	}

	addr, err := net.ResolveTCPAddr("tcp", hostPort)
	if err != nil {
		ch.log.Errorf("Could not resolve network %s: %v", hostPort, err)
		return err
	}

	ch.l, err = net.ListenTCP("tcp", addr)
	if err != nil {
		ch.log.Errorf("Could not listen on %s: %v", hostPort, err)
		return err
	}

	ch.connectionOptions.PeerInfo.HostPort = ch.hostPort
	ch.hostPort = ch.l.Addr().String()
	ch.log.Infof("%s listening on %s", ch.processName, ch.hostPort)
	return nil
}

// Register regsters a handler for a service+operation pair
func (ch *Channel) Register(h Handler, serviceName, operationName string) {
	ch.handlers.register(h, serviceName, operationName)
}

// BeginCall starts a new call to a remote peer, returning an OutboundCall that can
// be used to write the arguments of the call
// TODO(mmihic): Support CallOptions such as format, request specific checksums, retries, etc
func (ch *Channel) BeginCall(ctx context.Context, hostPort,
	serviceName, operationName string) (*OutboundCall, error) {
	// TODO(mmihic): Keep-alive, manage pools, use existing inbound if possible, all that jazz
	nconn, err := net.Dial("tcp", hostPort)
	if err != nil {
		return nil, err
	}

	conn, err := newOutboundConnection(nconn, ch.handlers, ch.log, &ch.connectionOptions)
	if err != nil {
		return nil, err
	}

	if err := conn.sendInit(ctx); err != nil {
		return nil, err
	}

	call, err := conn.beginCall(ctx, serviceName)
	if err != nil {
		return nil, err
	}

	if err := call.writeOperation([]byte(operationName)); err != nil {
		return nil, err
	}

	return call, nil
}

// RoundTrip calls a peer and waits for the response
func (ch *Channel) RoundTrip(ctx context.Context, hostPort, serviceName, operationName string,
	reqArg2, reqArg3 Output, resArg2, resArg3 Input) (bool, error) {

	call, err := ch.BeginCall(ctx, hostPort, serviceName, operationName)
	if err != nil {
		return false, err
	}

	if err := call.WriteArg2(reqArg2); err != nil {
		return false, err
	}

	if err := call.WriteArg3(reqArg3); err != nil {
		return false, err
	}

	if err := call.Response().ReadArg2(resArg2); err != nil {
		return false, err
	}

	if err := call.Response().ReadArg3(resArg3); err != nil {
		return false, err
	}

	return call.Response().ApplicationError(), nil
}

// serve runs the listener to accept and manage new incoming connections, blocking
// until the channel is closed.
func (ch *Channel) serve() error {
	acceptBackoff := 0 * time.Millisecond

	for {
		netConn, err := ch.l.Accept()
		if err != nil {
			// Backoff from new accepts if this is a temporary error
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				if acceptBackoff == 0 {
					acceptBackoff = 5 * time.Millisecond
				} else {
					acceptBackoff *= 2
				}
				if max := 1 * time.Second; acceptBackoff > max {
					acceptBackoff = max
				}
				ch.log.Warnf("accept error: %v; retrying in %v", err, acceptBackoff)
				time.Sleep(acceptBackoff)
				continue
			} else {
				ch.log.Errorf("unrecoverable accept error: %v; closing server", err)
				return nil
			}
		}

		acceptBackoff = 0

		_, err = newInboundConnection(netConn, ch.handlers, ch.log, &ch.connectionOptions)
		if err != nil {
			// Server is getting overloaded - begin rejecting new connections
			ch.log.Errorf("could not create new TChannelConnection for incoming conn: %v", err)
			netConn.Close()
			continue
		}

		// TODO(mmihic): Register connection so we can close them when the channel is closed
	}
}
<commit_msg>Constant naming feedback<commit_after>package tchannel

// Copyright (c) 2015 Uber Technologies, Inc.

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

import (
	"errors"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"sync"
	"time"

	"golang.org/x/net/context"
)

var (
	errAlreadyListening = errors.New("channel already listening")
)

const (
	ephemeralHostPort = "0.0.0.0:0"
)

// A Handler is an object hat can be registered with a Channel
// to process incoming calls for a given service and operation
type Handler interface {
	// Handles an incoming call for service
	Handle(ctx context.Context, call *InboundCall)
}

// The HandlerFunc is an adapter to allow the use of ordering functions as
// TChannel handlers.  If f is a function with the appropriate signature,
// HandlerFunc(f) is a Hander object that calls f
type HandlerFunc func(ctx context.Context, call *InboundCall)

// Handle calls f(ctx, call)
func (f HandlerFunc) Handle(ctx context.Context, call *InboundCall) { f(ctx, call) }

// ChannelOptions are used to control parameters on a create a TChannel
type ChannelOptions struct {
	// Default Connection options
	DefaultConnectionOptions ConnectionOptions

	// The name of the process, for logging and reporting to peers
	ProcessName string

	// The logger to use for this channel
	Logger Logger
}

// A Channel is a bi-directional connection to the peering and routing network.  Applications
// can use a Channel to make service calls to remote peers via BeginCall, or to listen for incoming calls
// from peers.  Applications that want to receive requests should call one of Serve or ListenAndServe
type Channel struct {
	log               Logger
	hostPort          string
	processName       string
	connectionOptions ConnectionOptions
	handlers          handlerMap

	mut sync.RWMutex // protects the listener
	l   net.Listener // May be nil if this is a client only channel
}

// NewChannel creates a new Channel.  The new channel can be used to send outbound requests
// to peers, but will not listen or handling incoming requests until one of ListenAndServe
// or Serve is called
func NewChannel(opts *ChannelOptions) (*Channel, error) {
	if opts == nil {
		opts = &ChannelOptions{}
	}

	logger := opts.Logger
	if logger == nil {
		logger = NullLogger
	}

	processName := opts.ProcessName
	if processName == "" {
		processName = fmt.Sprintf("%s[%d]", filepath.Base(os.Args[0]), os.Getpid())
	}

	ch := &Channel{
		connectionOptions: opts.DefaultConnectionOptions,
		processName:       processName,
		hostPort:          ephemeralHostPort,
		log:               logger,
	}

	ch.connectionOptions.PeerInfo.HostPort = ch.hostPort
	ch.connectionOptions.PeerInfo.ProcessName = ch.processName
	ch.connectionOptions.ChecksumType = ChecksumTypeCrc32
	return ch, nil
}

// Serve serves incoming requests using the provided listener
func (ch *Channel) Serve(l net.Listener) error {
	ch.mut.Lock()

	if ch.l != nil {
		ch.mut.Unlock()
		return errAlreadyListening
	}

	ch.l = l
	ch.connectionOptions.PeerInfo.HostPort = ch.hostPort
	ch.hostPort = ch.l.Addr().String()
	ch.mut.Unlock()

	return ch.serve()
}

// ListenAndServe listens on the given address and serves incoming requests.  The port
// may be 0, in which case the channel will use an OS assigned port
func (ch *Channel) ListenAndServe(hostPort string) error {
	if err := ch.listen(hostPort); err != nil {
		return err
	}

	return ch.serve()
}

// listen listens on the given address but does not begin serving request.
func (ch *Channel) listen(hostPort string) error {
	ch.mut.Lock()
	defer ch.mut.Unlock()

	if ch.l != nil {
		return errAlreadyListening
	}

	addr, err := net.ResolveTCPAddr("tcp", hostPort)
	if err != nil {
		ch.log.Errorf("Could not resolve network %s: %v", hostPort, err)
		return err
	}

	ch.l, err = net.ListenTCP("tcp", addr)
	if err != nil {
		ch.log.Errorf("Could not listen on %s: %v", hostPort, err)
		return err
	}

	ch.connectionOptions.PeerInfo.HostPort = ch.hostPort
	ch.hostPort = ch.l.Addr().String()
	ch.log.Infof("%s listening on %s", ch.processName, ch.hostPort)
	return nil
}

// Register regsters a handler for a service+operation pair
func (ch *Channel) Register(h Handler, serviceName, operationName string) {
	ch.handlers.register(h, serviceName, operationName)
}

// BeginCall starts a new call to a remote peer, returning an OutboundCall that can
// be used to write the arguments of the call
// TODO(mmihic): Support CallOptions such as format, request specific checksums, retries, etc
func (ch *Channel) BeginCall(ctx context.Context, hostPort,
	serviceName, operationName string) (*OutboundCall, error) {
	// TODO(mmihic): Keep-alive, manage pools, use existing inbound if possible, all that jazz
	nconn, err := net.Dial("tcp", hostPort)
	if err != nil {
		return nil, err
	}

	conn, err := newOutboundConnection(nconn, ch.handlers, ch.log, &ch.connectionOptions)
	if err != nil {
		return nil, err
	}

	if err := conn.sendInit(ctx); err != nil {
		return nil, err
	}

	call, err := conn.beginCall(ctx, serviceName)
	if err != nil {
		return nil, err
	}

	if err := call.writeOperation([]byte(operationName)); err != nil {
		return nil, err
	}

	return call, nil
}

// RoundTrip calls a peer and waits for the response
func (ch *Channel) RoundTrip(ctx context.Context, hostPort, serviceName, operationName string,
	reqArg2, reqArg3 Output, resArg2, resArg3 Input) (bool, error) {

	call, err := ch.BeginCall(ctx, hostPort, serviceName, operationName)
	if err != nil {
		return false, err
	}

	if err := call.WriteArg2(reqArg2); err != nil {
		return false, err
	}

	if err := call.WriteArg3(reqArg3); err != nil {
		return false, err
	}

	if err := call.Response().ReadArg2(resArg2); err != nil {
		return false, err
	}

	if err := call.Response().ReadArg3(resArg3); err != nil {
		return false, err
	}

	return call.Response().ApplicationError(), nil
}

// serve runs the listener to accept and manage new incoming connections, blocking
// until the channel is closed.
func (ch *Channel) serve() error {
	acceptBackoff := 0 * time.Millisecond

	for {
		netConn, err := ch.l.Accept()
		if err != nil {
			// Backoff from new accepts if this is a temporary error
			if ne, ok := err.(net.Error); ok && ne.Temporary() {
				if acceptBackoff == 0 {
					acceptBackoff = 5 * time.Millisecond
				} else {
					acceptBackoff *= 2
				}
				if max := 1 * time.Second; acceptBackoff > max {
					acceptBackoff = max
				}
				ch.log.Warnf("accept error: %v; retrying in %v", err, acceptBackoff)
				time.Sleep(acceptBackoff)
				continue
			} else {
				ch.log.Errorf("unrecoverable accept error: %v; closing server", err)
				return nil
			}
		}

		acceptBackoff = 0

		_, err = newInboundConnection(netConn, ch.handlers, ch.log, &ch.connectionOptions)
		if err != nil {
			// Server is getting overloaded - begin rejecting new connections
			ch.log.Errorf("could not create new TChannelConnection for incoming conn: %v", err)
			netConn.Close()
			continue
		}

		// TODO(mmihic): Register connection so we can close them when the channel is closed
	}
}
<|endoftext|>


<commit_before>// Package oauth2 implements an OAuth 2.0 authorization server with support
// for token revokation.
//
// For details about the specs implemented please refer to
// * http://tools.ietf.org/html/rfc6749
// * http://tools.ietf.org/html/rfc6750
// * https://tools.ietf.org/html/rfc7009
package oauth2

import (
	"html/template"
	"log"
	"net/http"
	"strings"
	"time"

	"golang.org/x/net/context"
)

// Client defines client information required by oauth2 to:
//   * Show an authorization form to a resource owner
//   * Validate that the provided request_uri parameter matches the one previously
//     registered for the client.
type Client struct {
	ID            string
	Name          string
	Desc          string
	ProfileImgURL string
	HomepageURL   string
	RedirectURL   string
}

// Scope defines a type for manipulating OAuth2 scopes.
type Scope struct {
	ID   string
	Desc string
}

// Defines a type for the two defined token types in OAuth2.
type TokenType string

const (
	AccessToken  TokenType = "access"
	RefreshToken TokenType = "refresh"
)

type Provider interface {
	// ClientInfo returns 3rd-party client information
	ClientInfo(clientID string) (info Client, err error)

	// GenAuthzCode issues and stores an authorization grant code, in a persistent storage.
	GenAuthzCode(clientID, scopes []Scope) (code string, err error)

	// RevokeAuthzCode expires the grant code as well as all access and refresh tokens generated with it.
	RevokeAuthzCode(code string) error

	// ScopesInfo parses the list of scopes requested by the client and
	// returns its descriptions for the resource owner to fully understand
	// what he is authorizing the client to access to. An error is returned
	// if the scopes list does not comply with http://tools.ietf.org/html/rfc6749#section-3.3
	//
	// Unrecognized or non-existent scopes are ignored.
	ScopesInfo(scopes string) ([]Scope, error)

	// GenToken generates and stores token.
	GenToken(tokenType TokenType, scopes []Scope) (token string, err error)

	// RevokeToken expires a specific token.
	RevokeToken(token string) error

	// RefreshToken refreshes an access token.
	RefreshToken(refreshToken, scopes []Scope) (accessToken string, err error)

	// AuthzForm returns the HTML authorization form.
	AuthzForm() string

	// LoginURL returns the login URL for the resource owner to authenticate if there is
	// not a valid session. The authentication system should send back the user
	// to the referer URL in order to complete the OAuth2 authorization process.
	LoginURL(refererURL string) (url string)

	// CheckSession checks whether or not the resource owner has a valid session
	// with the system. If not, it redirects the user to the login URL.
	CheckSession() (invalid bool)
}

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*config)

type config struct {
	authzEndpoint  string
	tokenEndpoint  string
	revokeEndpoint string
	ctx            context.Context
	stsMaxAge      time.Duration
	authzForm      *template.Template
	provider       Provider
}

// TokenEndpoint allows setting token endpoint. Defaults to "/oauth2/tokens".
//
// The token endpoint is used by the client to obtain an access token by
// presenting its authorization grant or refresh token.  The token
// endpoint is used with every authorization grant except for the
// implicit grant type (since an access token is issued directly).
//
// Since requests to the token endpoint result in the transmission of
// clear-text credentials (in the HTTP request and response), the
// authorization server MUST require the use of TLS as described in
// Section 1.6 when sending requests to the token endpoint.
//
// -- http://tools.ietf.org/html/rfc6749#section-3.2
func SetTokenEndpoint(endpoint string) option {
	return func(c *config) {
		c.tokenEndpoint = endpoint
	}
}

// AuthzEndpoint allows setting authorization endpoint. Defaults to "/oauth2/authzs"
//
// The authorization endpoint is used to interact with the resource owner and
// obtain an authorization grant.
//
// Since requests to the authorization endpoint result in user authentication
// and the transmission of clear-text credentials (in the HTTP response), the
// authorization server MUST require the use of TLS as described in Section 1.6
// when sending requests to the authorization endpoint.
//
// -- http://tools.ietf.org/html/rfc6749#section-3.1.1
func SetAuthzEndpoint(endpoint string) option {
	return func(c *config) {
		c.authzEndpoint = endpoint
	}
}

// SetRevokeEndpoint allows setting a custom token revoke URI. Defaults to "/oauth2/revoke"
func SetRevokeEndpoint(endpoint string) option {
	return func(c *config) {
		c.revokeEndpoint = endpoint
	}
}

// SetSTSMaxAge sets Strict Transport Security maximum age. Defaults to 1yr
func SetSTSMaxAge(maxAge time.Duration) option {
	return func(c *config) {
		c.stsMaxAge = maxAge
	}
}

// SetAuthzForm sets authorization form to show to the resource owner
func SetAuthzForm(form string) option {
	return func(c *config) {
		t := template.New("authzform")
		tpl, err := t.Parse(form)
		if err != nil {
			log.Fatalln("Error parsing authorization form: %v", err)
		}
		c.authzForm = tpl
	}
}

// Handler handles OAuth2 requests.
func Handler(next http.Handler, opts ...option) http.Handler {
	// Default configuration options.
	cfg := &config{
		tokenEndpoint:  "/oauth2/tokens",
		authzEndpoint:  "/oauth2/authzs",
		revokeEndpoint: "/oauth2/revoke",
	}

	cfg.stsMaxAge = time.Duration(31536000) * time.Second // 1yr

	// Applies user's configuration.
	for _, opt := range opts {
		opt(cfg)
	}

	if cfg.authzForm == nil {
		log.Fatalln("Authorization form is required")
	}

	if cfg.provider == nil {
		log.Fatalln("An implementation of the oauth2.Provider interface is expected")
	}

	// Keeps a registry of path function handlers for OAuth2 requests.
	registry := map[string]map[string]func(http.ResponseWriter, *http.Request, *config, http.Handler){
		cfg.authzEndpoint: AuthzHandlers,
		cfg.tokenEndpoint: TokenHandlers,
		// TODO(c4milo): URL handlers for revoking tokens and grants
	}

	// Locates and runs specific OAuth2 handler for request's method
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		for p, handlers := range registry {
			if strings.HasPrefix(req.URL.Path, p) {
				if handlerFn, ok := handlers[req.Method]; ok {
					handlerFn(w, req, cfg, next)
					return
				}
				w.WriteHeader(http.StatusMethodNotAllowed)
				w.Write([]byte("Method Not Allowed"))
				return
			}
		}
	})
}
<commit_msg>No need for context so far<commit_after>// Package oauth2 implements an OAuth 2.0 authorization server with support
// for token revokation.
//
// For details about the specs implemented please refer to
// * http://tools.ietf.org/html/rfc6749
// * http://tools.ietf.org/html/rfc6750
// * https://tools.ietf.org/html/rfc7009
package oauth2

import (
	"html/template"
	"log"
	"net/http"
	"strings"
	"time"
)

// Client defines client information required by oauth2 to:
//   * Show an authorization form to a resource owner
//   * Validate that the provided request_uri parameter matches the one previously
//     registered for the client.
type Client struct {
	ID            string
	Name          string
	Desc          string
	ProfileImgURL string
	HomepageURL   string
	RedirectURL   string
}

// Scope defines a type for manipulating OAuth2 scopes.
type Scope struct {
	ID   string
	Desc string
}

// Defines a type for the two defined token types in OAuth2.
type TokenType string

const (
	AccessToken  TokenType = "access"
	RefreshToken TokenType = "refresh"
)

type Provider interface {
	// ClientInfo returns 3rd-party client information
	ClientInfo(clientID string) (info Client, err error)

	// GenAuthzCode issues and stores an authorization grant code, in a persistent storage.
	GenAuthzCode(clientID, scopes []Scope) (code string, err error)

	// RevokeAuthzCode expires the grant code as well as all access and refresh tokens generated with it.
	RevokeAuthzCode(code string) error

	// ScopesInfo parses the list of scopes requested by the client and
	// returns its descriptions for the resource owner to fully understand
	// what he is authorizing the client to access to. An error is returned
	// if the scopes list does not comply with http://tools.ietf.org/html/rfc6749#section-3.3
	//
	// Unrecognized or non-existent scopes are ignored.
	ScopesInfo(scopes string) ([]Scope, error)

	// GenToken generates and stores token.
	GenToken(tokenType TokenType, scopes []Scope) (token string, err error)

	// RevokeToken expires a specific token.
	RevokeToken(token string) error

	// RefreshToken refreshes an access token.
	RefreshToken(refreshToken, scopes []Scope) (accessToken string, err error)

	// AuthzForm returns the HTML authorization form.
	AuthzForm() string

	// LoginURL returns the login URL for the resource owner to authenticate if there is
	// not a valid session. The authentication system should send back the user
	// to the referer URL in order to complete the OAuth2 authorization process.
	LoginURL(refererURL string) (url string)

	// CheckSession checks whether or not the resource owner has a valid session
	// with the system. If not, it redirects the user to the login URL.
	CheckSession() (invalid bool)
}

// http://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html
type option func(*config)

type config struct {
	authzEndpoint  string
	tokenEndpoint  string
	revokeEndpoint string
	stsMaxAge      time.Duration
	authzForm      *template.Template
	provider       Provider
}

// TokenEndpoint allows setting token endpoint. Defaults to "/oauth2/tokens".
//
// The token endpoint is used by the client to obtain an access token by
// presenting its authorization grant or refresh token.  The token
// endpoint is used with every authorization grant except for the
// implicit grant type (since an access token is issued directly).
//
// Since requests to the token endpoint result in the transmission of
// clear-text credentials (in the HTTP request and response), the
// authorization server MUST require the use of TLS as described in
// Section 1.6 when sending requests to the token endpoint.
//
// -- http://tools.ietf.org/html/rfc6749#section-3.2
func SetTokenEndpoint(endpoint string) option {
	return func(c *config) {
		c.tokenEndpoint = endpoint
	}
}

// AuthzEndpoint allows setting authorization endpoint. Defaults to "/oauth2/authzs"
//
// The authorization endpoint is used to interact with the resource owner and
// obtain an authorization grant.
//
// Since requests to the authorization endpoint result in user authentication
// and the transmission of clear-text credentials (in the HTTP response), the
// authorization server MUST require the use of TLS as described in Section 1.6
// when sending requests to the authorization endpoint.
//
// -- http://tools.ietf.org/html/rfc6749#section-3.1.1
func SetAuthzEndpoint(endpoint string) option {
	return func(c *config) {
		c.authzEndpoint = endpoint
	}
}

// SetRevokeEndpoint allows setting a custom token revoke URI. Defaults to "/oauth2/revoke"
func SetRevokeEndpoint(endpoint string) option {
	return func(c *config) {
		c.revokeEndpoint = endpoint
	}
}

// SetSTSMaxAge sets Strict Transport Security maximum age. Defaults to 1yr
func SetSTSMaxAge(maxAge time.Duration) option {
	return func(c *config) {
		c.stsMaxAge = maxAge
	}
}

// SetAuthzForm sets authorization form to show to the resource owner
func SetAuthzForm(form string) option {
	return func(c *config) {
		t := template.New("authzform")
		tpl, err := t.Parse(form)
		if err != nil {
			log.Fatalln("Error parsing authorization form: %v", err)
		}
		c.authzForm = tpl
	}
}

// Handler handles OAuth2 requests.
func Handler(next http.Handler, opts ...option) http.Handler {
	// Default configuration options.
	cfg := &config{
		tokenEndpoint:  "/oauth2/tokens",
		authzEndpoint:  "/oauth2/authzs",
		revokeEndpoint: "/oauth2/revoke",
	}

	cfg.stsMaxAge = time.Duration(31536000) * time.Second // 1yr

	// Applies user's configuration.
	for _, opt := range opts {
		opt(cfg)
	}

	if cfg.authzForm == nil {
		log.Fatalln("Authorization form is required")
	}

	if cfg.provider == nil {
		log.Fatalln("An implementation of the oauth2.Provider interface is expected")
	}

	// Keeps a registry of path function handlers for OAuth2 requests.
	registry := map[string]map[string]func(http.ResponseWriter, *http.Request, *config, http.Handler){
		cfg.authzEndpoint: AuthzHandlers,
		cfg.tokenEndpoint: TokenHandlers,
		// TODO(c4milo): URL handlers for revoking tokens and grants
	}

	// Locates and runs specific OAuth2 handler for request's method
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		for p, handlers := range registry {
			if strings.HasPrefix(req.URL.Path, p) {
				if handlerFn, ok := handlers[req.Method]; ok {
					handlerFn(w, req, cfg, next)
					return
				}
				w.WriteHeader(http.StatusMethodNotAllowed)
				w.Write([]byte("Method Not Allowed"))
				return
			}
		}
	})
}
<|endoftext|>


<commit_before>// Copyright 2015 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.

package ebiten

import (
	"github.com/hajimehoshi/ebiten/internal/driver"
)

// InputChars return "printable" runes read from the keyboard at the time update is called.
//
// InputChars represents the environment's locale-dependent translation of keyboard
// input to Unicode characters.
//
// IsKeyPressed is based on a mapping of device (US keyboard) codes to input device keys.
// "Control" and modifier keys should be handled with IsKeyPressed.
//
// InputChars is concurrent-safe.
func InputChars() []rune {
	rb := uiDriver().Input().RuneBuffer()
	return append(make([]rune, 0, len(rb)), rb...)
}

// IsKeyPressed returns a boolean indicating whether key is pressed.
//
// Known issue: On Edge browser, some keys don't work well:
//
//   - KeyKPEnter and KeyKPEqual are recognized as KeyEnter and KeyEqual.
//   - KeyPrintScreen is only treated at keyup event.
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keys.
//
// IsKeyPressed is concurrent-safe.
func IsKeyPressed(key Key) bool {
	// There are keys that are invalid values as ebiten.Key (e.g., driver.KeyLeftAlt).
	// Skip such values.
	if !key.isValid() {
		return false
	}

	var keys []driver.Key
	switch key {
	case KeyAlt:
		keys = []driver.Key{driver.KeyLeftAlt, driver.KeyRightAlt}
	case KeyControl:
		keys = []driver.Key{driver.KeyLeftControl, driver.KeyRightControl}
	case KeyShift:
		keys = []driver.Key{driver.KeyLeftShift, driver.KeyRightShift}
	default:
		keys = []driver.Key{driver.Key(key)}
	}
	for _, k := range keys {
		if uiDriver().Input().IsKeyPressed(k) {
			return true
		}
	}
	return false
}

// CursorPosition returns a position of a mouse cursor relative to the game screen (window). The cursor position is
// 'logical' position and this considers the scale of the screen.
//
// CursorPosition is concurrent-safe.
func CursorPosition() (x, y int) {
	return uiDriver().Input().CursorPosition()
}

// Wheel returns the x and y offset of the mouse wheel or touchpad scroll.
// It returns 0 if the wheel isn't being rolled.
//
// Wheel is concurrent-safe.
func Wheel() (xoff, yoff float64) {
	return uiDriver().Input().Wheel()
}

// IsMouseButtonPressed returns a boolean indicating whether mouseButton is pressed.
//
// IsMouseButtonPressed is concurrent-safe.
//
// Note that touch events not longer affect IsMouseButtonPressed's result as of 1.4.0-alpha.
// Use Touches instead.
func IsMouseButtonPressed(mouseButton MouseButton) bool {
	return uiDriver().Input().IsMouseButtonPressed(driver.MouseButton(mouseButton))
}

// GamepadSDLID returns a string with the GUID generated in the same way as SDL.
// To detect devices, see also the community project of gamepad devices database: https://github.com/gabomdq/SDL_GameControllerDB
//
// GamepadSDLID always returns an empty string on browsers and mobiles.
//
// GamepadSDLID is concurrent-safe.
func GamepadSDLID(id int) string {
	return uiDriver().Input().GamepadSDLID(id)
}

// GamepadName returns a string with the name.
// This function may vary in how it returns descriptions for the same device across platforms
// for example the following drivers/platforms see a Xbox One controller as the following:
//
//   - Windows: "Xbox Controller"
//   - Chrome: "Xbox 360 Controller (XInput STANDARD GAMEPAD)"
//   - Firefox: "xinput"
//
// GamepadName always returns an empty string on mobiles.
//
// GamepadName is concurrent-safe.
func GamepadName(id int) string {
	return uiDriver().Input().GamepadName(id)
}

// GamepadIDs returns a slice indicating available gamepad IDs.
//
// GamepadIDs is concurrent-safe.
//
// GamepadIDs always returns an empty slice on mobiles.
func GamepadIDs() []int {
	return uiDriver().Input().GamepadIDs()
}

// GamepadAxisNum returns the number of axes of the gamepad (id).
//
// GamepadAxisNum is concurrent-safe.
//
// GamepadAxisNum always returns 0 on mobiles.
func GamepadAxisNum(id int) int {
	return uiDriver().Input().GamepadAxisNum(id)
}

// GamepadAxis returns the float value [-1.0 - 1.0] of the given gamepad (id)'s axis (axis).
//
// GamepadAxis is concurrent-safe.
//
// GamepadAxis always returns 0 on mobiles.
func GamepadAxis(id int, axis int) float64 {
	return uiDriver().Input().GamepadAxis(id, axis)
}

// GamepadButtonNum returns the number of the buttons of the given gamepad (id).
//
// GamepadButtonNum is concurrent-safe.
//
// GamepadButtonNum always returns 0 on mobiles.
func GamepadButtonNum(id int) int {
	return uiDriver().Input().GamepadButtonNum(id)
}

// IsGamepadButtonPressed returns the boolean indicating the given button of the gamepad (id) is pressed or not.
//
// IsGamepadButtonPressed is concurrent-safe.
//
// The relationships between physical buttons and buttion IDs depend on environments.
// There can be differences even between Chrome and Firefox.
//
// IsGamepadButtonPressed always returns false on mobiles.
func IsGamepadButtonPressed(id int, button GamepadButton) bool {
	return uiDriver().Input().IsGamepadButtonPressed(id, driver.GamepadButton(button))
}

// TouchIDs returns the current touch states.
//
// TouchIDs returns nil when there are no touches.
// TouchIDs always returns nil on desktops.
//
// TouchIDs is concurrent-safe.
func TouchIDs() []int {
	return uiDriver().Input().TouchIDs()
}

// TouchPosition returns the position for the touch of the specified ID.
//
// If the touch of the specified ID is not present, TouchPosition returns (0, 0).
//
// TouchPosition is cuncurrent-safe.
func TouchPosition(id int) (int, int) {
	found := false
	for _, i := range uiDriver().Input().TouchIDs() {
		if id == i {
			found = true
			break
		}
	}
	if !found {
		return 0, 0
	}

	return uiDriver().Input().TouchPosition(id)
}

// Touch is deprecated as of 1.7.0. Use TouchPosition instead.
type Touch interface {
	// ID returns an identifier for one stroke.
	ID() int

	// Position returns the position of the touch.
	Position() (x, y int)
}

type touch struct {
	id int
	x  int
	y  int
}

func (t *touch) ID() int {
	return t.id
}

func (t *touch) Position() (x, y int) {
	return t.x, t.y
}

// Touches is deprecated as of 1.7.0. Use TouchIDs instead.
func Touches() []Touch {
	var ts []Touch
	for _, id := range TouchIDs() {
		x, y := TouchPosition(id)
		ts = append(ts, &touch{
			id: id,
			x:  x,
			y:  y,
		})
	}
	return ts
}
<commit_msg>input: Add comments about mobiles<commit_after>// Copyright 2015 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.

package ebiten

import (
	"github.com/hajimehoshi/ebiten/internal/driver"
)

// InputChars return "printable" runes read from the keyboard at the time update is called.
//
// InputChars represents the environment's locale-dependent translation of keyboard
// input to Unicode characters.
//
// IsKeyPressed is based on a mapping of device (US keyboard) codes to input device keys.
// "Control" and modifier keys should be handled with IsKeyPressed.
//
// InputChars is concurrent-safe.
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
//
// Keyboards don't work on iOS yet (#1090).
func InputChars() []rune {
	rb := uiDriver().Input().RuneBuffer()
	return append(make([]rune, 0, len(rb)), rb...)
}

// IsKeyPressed returns a boolean indicating whether key is pressed.
//
// Known issue: On Edge browser, some keys don't work well:
//
//   - KeyKPEnter and KeyKPEqual are recognized as KeyEnter and KeyEqual.
//   - KeyPrintScreen is only treated at keyup event.
//
// IsKeyPressed is concurrent-safe.
//
// On Android (ebitenmobile), EbitenView must be focusable to enable to handle keyboard keys.
//
// Keyboards don't work on iOS yet (#1090).
func IsKeyPressed(key Key) bool {
	// There are keys that are invalid values as ebiten.Key (e.g., driver.KeyLeftAlt).
	// Skip such values.
	if !key.isValid() {
		return false
	}

	var keys []driver.Key
	switch key {
	case KeyAlt:
		keys = []driver.Key{driver.KeyLeftAlt, driver.KeyRightAlt}
	case KeyControl:
		keys = []driver.Key{driver.KeyLeftControl, driver.KeyRightControl}
	case KeyShift:
		keys = []driver.Key{driver.KeyLeftShift, driver.KeyRightShift}
	default:
		keys = []driver.Key{driver.Key(key)}
	}
	for _, k := range keys {
		if uiDriver().Input().IsKeyPressed(k) {
			return true
		}
	}
	return false
}

// CursorPosition returns a position of a mouse cursor relative to the game screen (window). The cursor position is
// 'logical' position and this considers the scale of the screen.
//
// CursorPosition is concurrent-safe.
func CursorPosition() (x, y int) {
	return uiDriver().Input().CursorPosition()
}

// Wheel returns the x and y offset of the mouse wheel or touchpad scroll.
// It returns 0 if the wheel isn't being rolled.
//
// Wheel is concurrent-safe.
func Wheel() (xoff, yoff float64) {
	return uiDriver().Input().Wheel()
}

// IsMouseButtonPressed returns a boolean indicating whether mouseButton is pressed.
//
// IsMouseButtonPressed is concurrent-safe.
//
// Note that touch events not longer affect IsMouseButtonPressed's result as of 1.4.0-alpha.
// Use Touches instead.
func IsMouseButtonPressed(mouseButton MouseButton) bool {
	return uiDriver().Input().IsMouseButtonPressed(driver.MouseButton(mouseButton))
}

// GamepadSDLID returns a string with the GUID generated in the same way as SDL.
// To detect devices, see also the community project of gamepad devices database: https://github.com/gabomdq/SDL_GameControllerDB
//
// GamepadSDLID always returns an empty string on browsers and mobiles.
//
// GamepadSDLID is concurrent-safe.
func GamepadSDLID(id int) string {
	return uiDriver().Input().GamepadSDLID(id)
}

// GamepadName returns a string with the name.
// This function may vary in how it returns descriptions for the same device across platforms
// for example the following drivers/platforms see a Xbox One controller as the following:
//
//   - Windows: "Xbox Controller"
//   - Chrome: "Xbox 360 Controller (XInput STANDARD GAMEPAD)"
//   - Firefox: "xinput"
//
// GamepadName always returns an empty string on mobiles.
//
// GamepadName is concurrent-safe.
func GamepadName(id int) string {
	return uiDriver().Input().GamepadName(id)
}

// GamepadIDs returns a slice indicating available gamepad IDs.
//
// GamepadIDs is concurrent-safe.
//
// GamepadIDs always returns an empty slice on mobiles.
func GamepadIDs() []int {
	return uiDriver().Input().GamepadIDs()
}

// GamepadAxisNum returns the number of axes of the gamepad (id).
//
// GamepadAxisNum is concurrent-safe.
//
// GamepadAxisNum always returns 0 on mobiles.
func GamepadAxisNum(id int) int {
	return uiDriver().Input().GamepadAxisNum(id)
}

// GamepadAxis returns the float value [-1.0 - 1.0] of the given gamepad (id)'s axis (axis).
//
// GamepadAxis is concurrent-safe.
//
// GamepadAxis always returns 0 on mobiles.
func GamepadAxis(id int, axis int) float64 {
	return uiDriver().Input().GamepadAxis(id, axis)
}

// GamepadButtonNum returns the number of the buttons of the given gamepad (id).
//
// GamepadButtonNum is concurrent-safe.
//
// GamepadButtonNum always returns 0 on mobiles.
func GamepadButtonNum(id int) int {
	return uiDriver().Input().GamepadButtonNum(id)
}

// IsGamepadButtonPressed returns the boolean indicating the given button of the gamepad (id) is pressed or not.
//
// IsGamepadButtonPressed is concurrent-safe.
//
// The relationships between physical buttons and buttion IDs depend on environments.
// There can be differences even between Chrome and Firefox.
//
// IsGamepadButtonPressed always returns false on mobiles.
func IsGamepadButtonPressed(id int, button GamepadButton) bool {
	return uiDriver().Input().IsGamepadButtonPressed(id, driver.GamepadButton(button))
}

// TouchIDs returns the current touch states.
//
// TouchIDs returns nil when there are no touches.
// TouchIDs always returns nil on desktops.
//
// TouchIDs is concurrent-safe.
func TouchIDs() []int {
	return uiDriver().Input().TouchIDs()
}

// TouchPosition returns the position for the touch of the specified ID.
//
// If the touch of the specified ID is not present, TouchPosition returns (0, 0).
//
// TouchPosition is cuncurrent-safe.
func TouchPosition(id int) (int, int) {
	found := false
	for _, i := range uiDriver().Input().TouchIDs() {
		if id == i {
			found = true
			break
		}
	}
	if !found {
		return 0, 0
	}

	return uiDriver().Input().TouchPosition(id)
}

// Touch is deprecated as of 1.7.0. Use TouchPosition instead.
type Touch interface {
	// ID returns an identifier for one stroke.
	ID() int

	// Position returns the position of the touch.
	Position() (x, y int)
}

type touch struct {
	id int
	x  int
	y  int
}

func (t *touch) ID() int {
	return t.id
}

func (t *touch) Position() (x, y int) {
	return t.x, t.y
}

// Touches is deprecated as of 1.7.0. Use TouchIDs instead.
func Touches() []Touch {
	var ts []Touch
	for _, id := range TouchIDs() {
		x, y := TouchPosition(id)
		ts = append(ts, &touch{
			id: id,
			x:  x,
			y:  y,
		})
	}
	return ts
}
<|endoftext|>


<commit_before>/*
go-input is package to interact with user input from command line interface.

http://github.com/tcnksm/go-input
*/
package input

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/signal"
	"strconv"
)

var (
	defaultWriter = os.Stdout
	defaultReader = os.Stdin
)

// UI
type UI struct {
	// Writer is where output is written. For example query
	// to the user will be written. By default, it's os.Stdout.
	Writer io.Writer

	// Reader is source of input. By default, it's os.Stdin.
	Reader io.Reader
}

type Options struct {
	// Default is the value when no thing is innputted
	Default string

	// Loop continues to asking user to input until
	// he/she provides valid value.
	Loop bool

	// Hide hides user input is prompting console.
	Hide bool
}

func (i *UI) Ask(query string, opts *Options) (string, error) {

	wr := i.Writer
	if wr == nil {
		wr = defaultWriter
	}

	rd := i.Reader
	if rd == nil {
		rd = defaultReader
	}

	// Construct the query to the user
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("%s\n", query))
	fmt.Fprintf(wr, buf.String())

	resultCh := make(chan string, 1)
	errCh := make(chan error, 1)
	go func() {
		// Loop only when error by invalid user input and opts.Loop is true.
		for {
			// Construct the asking line to input
			var buf bytes.Buffer
			buf.WriteString("Enter a value")

			// Add default val if provided
			if opts.Default != "" {
				buf.WriteString(fmt.Sprintf(" (Default is %s)", opts.Default))
			}

			buf.WriteString(": ")
			fmt.Fprintf(wr, buf.String())

			// Read user input from reader.
			var line string
			if _, err := fmt.Fscanln(rd, &line); err != nil {
				// Handle error if it's not `unexpected newline`
				if err.Error() != "unexpected newline" {
					errCh <- fmt.Errorf("failed to read the input: %s", err)
					break
				}
			}

			// Use default value if provided
			if line == "" && opts.Default != "" {
				resultCh <- opts.Default
				return
			}

			resultCh <- line
			return
		}
	}()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)
	defer signal.Stop(sigCh)

	select {
	case result := <-resultCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return result, nil
	case err := <-errCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return "", err
	case <-sigCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return "", ErrInterrupted
	}
}

func (i *UI) Select(query string, list []string, opts *Options) (string, error) {

	wr := i.Writer
	if wr == nil {
		wr = defaultWriter
	}

	rd := i.Reader
	if rd == nil {
		rd = defaultReader
	}

	defaultIndex := -1
	defaultVal := opts.Default
	if defaultVal != "" {
		for i, item := range list {
			if item == defaultVal {
				defaultIndex = i
			}
		}

		// DefaultVal is set but does'nt exist in list
		if defaultIndex == -1 {
			// This error message is not for user
			// Should be found while development
			return "", fmt.Errorf("opt.Default is specied but does not exst in list")
		}
	}

	// Construct the query to the user
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("%s\n\n", query))
	for i, item := range list {
		buf.WriteString(fmt.Sprintf("%d. %s\n", i+1, item))
	}
	buf.WriteString("\n")

	// Prompt the message
	fmt.Fprintf(wr, buf.String())

	resultCh := make(chan string, 1)
	errCh := make(chan error, 1)
	go func() {
		// Loop only when error by invalid user input and opts.Loop is true.
		for {
			// Construct the asking line to input
			var buf bytes.Buffer
			buf.WriteString("Enter a number")

			// Add default val if provided
			if defaultIndex >= 0 {
				buf.WriteString(fmt.Sprintf(" (Default is %d)", defaultIndex+1))
			}

			buf.WriteString(": ")
			fmt.Fprintf(wr, buf.String())

			// Read user input from reader.
			var line string
			if _, err := fmt.Fscanln(rd, &line); err != nil {
				// Handle error if it's not `unexpected newline`
				if err.Error() != "unexpected newline" {
					errCh <- fmt.Errorf("failed to read the input: %s", err)
					break
				}
			}

			// execSelect selects a item from list by user input.
			result, err := execSelect(list, line, defaultIndex)
			if err != nil {

				// Don't loop and just return error if Loop is false
				if !opts.Loop {
					errCh <- err
					return
				}

				// Check error and if it's possible to ask again to user
				// then provide appropriate message and run loop again
				switch err {
				case ErrEmpty:
					fmt.Fprintf(wr, "Input must not be empty. Answer by a number.\n\n")
					continue
				case ErrNotNumber:
					fmt.Fprintf(wr,
						"%q is not a valid input. Answer by a number.\n\n", line)
					continue
				case ErrOutOfRange:
					fmt.Fprintf(wr,
						"%q is not a valid choice. Choose from 1 to %d.\n\n",
						line, len(list))
					continue
				default:
					// If other error is returned, it means asking again is
					// impossible
					errCh <- err
					return
				}
			}

			resultCh <- result
			return
		}
	}()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)
	defer signal.Stop(sigCh)

	select {
	case result := <-resultCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return result, nil
	case err := <-errCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return "", err
	case <-sigCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return "", ErrInterrupted
	}
}

// execSelect selects a item from list by user input.
// It checks input meets the condition to choose answer from list and if not
// returns appropriate error. See more about error in `error.go` file.
func execSelect(list []string, input string, defaultIndex int) (string, error) {
	if input == "" {
		if defaultIndex >= 0 {
			return list[defaultIndex], nil
		}
		return "", ErrEmpty
	}

	// Convert user input string to int val
	n, err := strconv.Atoi(input)
	if err != nil {
		return "", ErrNotNumber
	}

	// Check answer is in range of list
	if n < 1 || len(list) < n {
		return "", ErrOutOfRange
	}

	return list[n-1], nil
}
<commit_msg>Refactoring<commit_after>/*
go-input is package to interact with user input from command line interface.

http://github.com/tcnksm/go-input
*/
package input

import (
	"bytes"
	"fmt"
	"io"
	"os"
	"os/signal"
	"strconv"
)

var (
	defaultWriter = os.Stdout
	defaultReader = os.Stdin
)

// UI
type UI struct {
	// Writer is where output is written. For example query
	// to the user will be written. By default, it's os.Stdout.
	Writer io.Writer

	// Reader is source of input. By default, it's os.Stdin.
	Reader io.Reader
}

type Options struct {
	// Default is the value when no thing is innputted.
	Default string

	// Loop continues to asking user to input until getting valid input.
	Loop bool

	// Required returns error when input is empty.
	Required bool

	// Hide hides user input is prompting console.
	Hide bool
}

func (i *UI) Ask(query string, opts *Options) (string, error) {

	// Set the default writer & reader if not provided
	wr, rd := i.Writer, i.Reader
	if wr == nil {
		wr = defaultWriter
	}
	if rd == nil {
		rd = defaultReader
	}

	// Construct the query to the user
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("%s\n", query))
	fmt.Fprintf(wr, buf.String())

	resultCh := make(chan string, 1)
	errCh := make(chan error, 1)
	go func() {
		// Loop only when error by invalid user input and opts.Loop is true.
		for {
			// Construct the asking line to input
			var buf bytes.Buffer
			buf.WriteString("Enter a value")

			// Add default val if provided
			if opts.Default != "" {
				buf.WriteString(fmt.Sprintf(" (Default is %s)", opts.Default))
			}

			buf.WriteString(": ")
			fmt.Fprintf(wr, buf.String())

			// Read user input from reader.
			var line string
			if _, err := fmt.Fscanln(rd, &line); err != nil {
				// Handle error if it's not `unexpected newline`
				if err.Error() != "unexpected newline" {
					errCh <- fmt.Errorf("failed to read the input: %s", err)
					break
				}
			}

			// Use default value if provided
			if line == "" && opts.Default != "" {
				resultCh <- opts.Default
				return
			}

			resultCh <- line
			return
		}
	}()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)
	defer signal.Stop(sigCh)

	select {
	case result := <-resultCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return result, nil
	case err := <-errCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return "", err
	case <-sigCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return "", ErrInterrupted
	}
}

func (i *UI) Select(query string, list []string, opts *Options) (string, error) {

	// Set the default writer & reader if not provided
	wr, rd := i.Writer, i.Reader
	if wr == nil {
		wr = defaultWriter
	}
	if rd == nil {
		rd = defaultReader
	}

	// Find default index which opts.Default indicates
	defaultIndex := -1
	defaultVal := opts.Default
	if defaultVal != "" {
		for i, item := range list {
			if item == defaultVal {
				defaultIndex = i
			}
		}

		// DefaultVal is set but does'nt exist in list
		if defaultIndex == -1 {
			// This error message is not for user
			// Should be found while development
			return "", fmt.Errorf("opt.Default is specied but does not exst in list")
		}
	}

	// Construct the query to the user
	var buf bytes.Buffer
	buf.WriteString(fmt.Sprintf("%s\n\n", query))
	for i, item := range list {
		buf.WriteString(fmt.Sprintf("%d. %s\n", i+1, item))
	}

	// Prompt the query
	buf.WriteString("\n")
	fmt.Fprintf(wr, buf.String())

	resultCh := make(chan string, 1)
	errCh := make(chan error, 1)
	go func() {
		// Loop only when error by invalid user input and opts.Loop is true.
		for {
			// Construct the asking line to input
			var buf bytes.Buffer
			buf.WriteString("Enter a number")

			// Add default val if provided
			if defaultIndex >= 0 {
				buf.WriteString(fmt.Sprintf(" (Default is %d)", defaultIndex+1))
			}

			buf.WriteString(": ")
			fmt.Fprintf(wr, buf.String())

			// Read user input from reader.
			var line string
			if _, err := fmt.Fscanln(rd, &line); err != nil {
				// Handle error if it's not `unexpected newline`
				if err.Error() != "unexpected newline" {
					errCh <- fmt.Errorf("failed to read the input: %s", err)
					break
				}
			}

			// execSelect selects a item from list by user input.
			result, err := execSelect(list, line, defaultIndex)
			if err != nil {

				// Don't loop and just return error if Loop is false
				if !opts.Loop {
					errCh <- err
					return
				}

				// Check error and if it's possible to ask again to user
				// then provide appropriate message and run loop again
				switch err {
				case ErrEmpty:
					fmt.Fprintf(wr, "Input must not be empty. Answer by a number.\n\n")
					continue
				case ErrNotNumber:
					fmt.Fprintf(wr,
						"%q is not a valid input. Answer by a number.\n\n", line)
					continue
				case ErrOutOfRange:
					fmt.Fprintf(wr,
						"%q is not a valid choice. Choose from 1 to %d.\n\n",
						line, len(list))
					continue
				default:
					// If other error is returned, it means asking again is
					// impossible
					errCh <- err
					return
				}
			}

			resultCh <- result
			return
		}
	}()

	sigCh := make(chan os.Signal, 1)
	signal.Notify(sigCh, os.Interrupt)
	defer signal.Stop(sigCh)

	select {
	case result := <-resultCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return result, nil
	case err := <-errCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return "", err
	case <-sigCh:
		// Insert the new line for next output
		fmt.Fprintf(wr, "\n")
		return "", ErrInterrupted
	}
}

// execSelect selects a item from list by user input.
// It checks input meets the condition to choose answer from list and if not
// returns appropriate error. See more about error in `error.go` file.
func execSelect(list []string, input string, defaultIndex int) (string, error) {
	if input == "" {
		if defaultIndex >= 0 {
			return list[defaultIndex], nil
		}
		return "", ErrEmpty
	}

	// Convert user input string to int val
	n, err := strconv.Atoi(input)
	if err != nil {
		return "", ErrNotNumber
	}

	// Check answer is in range of list
	if n < 1 || len(list) < n {
		return "", ErrOutOfRange
	}

	return list[n-1], nil
}
<|endoftext|>


<commit_before>package cast

// Int16 will return an int16 when `v` is of type int16, int8, uint8 or has a method:
//
//	type interface {
//		Int16() (int16, error)
//	}
//
// ... that returns successfully, or has a method:
//
//	type interface {
//		Int8() (int8, error)
//	}
//
// ... that returns successfully, or has a method:
//
//	type interface {
//		Uint8() (uint8, error)
//	}
//
// ... that returns successfully.
//
// Else it will return an error.
func Int16(v interface{}) (int16, error) {

	switch value := v.(type) {
	case int16:
		return int16(value), nil
	case int16er:
		return value.Int16()
	case int8:
		return int16(value), nil
	case int8er:
		return func()(int16, error){
			value, err := value.Int8()
			if nil != err {
				return 0, err
			}
			return int16(value), nil
		}()
	case uint8:
		return int16(value), nil
	case uint8er:
		return func()(int16, error){
			value, err := value.Uint8()
			if nil != err {
				return 0, err
			}
			return int16(value), nil
		}()
	default:
		return 0, internalCannotCastComplainer{expectedType:"int16", actualType:typeof(value)}
	}
}

// MustInt16 is like Int16, expect panic()s on an error.
func MustInt16(v interface{}) int16 {

	x, err := Int16(v)
	if nil != err {
		panic(err)
	}

	return x
}

type int16er interface {
	Int16() (int16, error)
}
<commit_msg>tweaked variable name to make it more readable.<commit_after>package cast

// Int16 will return an int16 when `v` is of type int16, int8, uint8 or has a method:
//
//	type interface {
//		Int16() (int16, error)
//	}
//
// ... that returns successfully, or has a method:
//
//	type interface {
//		Int8() (int8, error)
//	}
//
// ... that returns successfully, or has a method:
//
//	type interface {
//		Uint8() (uint8, error)
//	}
//
// ... that returns successfully.
//
// Else it will return an error.
func Int16(v interface{}) (int16, error) {

	switch value := v.(type) {
	case int16:
		return int16(value), nil
	case int16er:
		return value.Int16()
	case int8:
		return int16(value), nil
	case int8er:
		return func()(int16, error){
			casted, err := value.Int8()
			if nil != err {
				return 0, err
			}
			return int16(casted), nil
		}()
	case uint8:
		return int16(value), nil
	case uint8er:
		return func()(int16, error){
			casted, err := value.Uint8()
			if nil != err {
				return 0, err
			}
			return int16(casted), nil
		}()
	default:
		return 0, internalCannotCastComplainer{expectedType:"int16", actualType:typeof(value)}
	}
}

// MustInt16 is like Int16, expect panic()s on an error.
func MustInt16(v interface{}) int16 {

	x, err := Int16(v)
	if nil != err {
		panic(err)
	}

	return x
}

type int16er interface {
	Int16() (int16, error)
}
<|endoftext|>


<commit_before>package io

import (
	"bufio"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	"path"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"gonum.org/v1/plot"
	"gonum.org/v1/plot/plotutil"
	"gonum.org/v1/plot/vg"
)

type TestCaseFunc func(*Input, *Output)

type Parser struct {
	f             TestCaseFunc
	input         *Input
	output        *Output
	compareOutput *CompareOutput

	debugNumber *int

	baseFn    string
	inputFn   string
	outputFn  string
	correctFn string
	profileFn string
}

func TestCases(f TestCaseFunc) {
	log.SetFlags(0)

	parser := Parser{
		f:           f,
		debugNumber: flag.Int("d", -1, "Debug output only for this case"),
	}

	inputFileFlag := flag.String("i", "", "Input file")

	flag.Parse()

	inputFile := ""
	if *inputFileFlag != "" {
		inputFile = *inputFileFlag
	} else if len(flag.Args()) == 1 {
		inputFile = flag.Args()[0]
	} else {
		usr, err := user.Current()
		if err != nil {
			log.Fatal(err)
		}
		downloadFiles, err := filepath.Glob(path.Join(usr.HomeDir, "Downloads/*.in"))
		if err != nil {
			log.Fatal(err)
		}
		if len(downloadFiles) > 1 {
			log.Fatal("Multiple input files in Downloads")
		} else if len(downloadFiles) == 1 {
			inputFile = filepath.Base(downloadFiles[0])
			os.Rename(downloadFiles[0], inputFile)
		} else {
			localFiles, err := filepath.Glob("*.in")
			if err != nil {
				log.Fatal(err)
			}
			if len(localFiles) > 1 {
				log.Fatal("Multiple input files found, please specify one!")
			} else if len(localFiles) == 1 {
				inputFile = localFiles[0]
			} else {
				log.Fatal("No input files found")
			}
		}
	}

	if !strings.HasSuffix(inputFile, ".in") {
		if inputFile[len(inputFile)-1] != '.' {
			inputFile += ".in"
		} else {
			inputFile += "in"
		}
	}

	log.Println("Using input file:", inputFile)

	parser.SetFn(inputFile)
	parser.ParseFile()
}

func (parser *Parser) SetFn(inputFn string) {
	parser.inputFn = inputFn
	parser.baseFn = strings.TrimSuffix(inputFn, ".in")
	parser.outputFn = parser.baseFn + ".out"
	parser.correctFn = parser.baseFn + ".correct"
	parser.profileFn = parser.baseFn + ".prof"
}

func (parser *Parser) ParseFile() {
	inputF, err := os.Open(parser.inputFn)
	if err != nil {
		log.Fatalln("Error opening input file:", err)
	}
	defer inputF.Close()

	outputF, err := os.Create(parser.outputFn)
	if err != nil {
		log.Fatalln("Error creating output file:", err)
	}
	defer outputF.Close()

	var outputWriter io.Writer = outputF
	if parser.inputFn == "example.in" {
		outputWriter = io.MultiWriter(outputF, os.Stdout)
	}

	scanner := bufio.NewScanner(inputF)
	scanner.Split(bufio.ScanWords)

	parser.output = newOutput(outputWriter)
	parser.input = newInput(scanner)

	parser.compareOutput = nil
	if _, err := os.Stat(parser.correctFn); err == nil {
		correctF, err := os.Open(parser.correctFn)
		if err != nil {
			log.Fatalln("Error opening correct file:", err)
		}
		defer correctF.Close()

		parser.compareOutput = NewCompareOutput(correctF)
	}

	T := parser.input.Int()

	if *parser.debugNumber != -1 {
		log.SetOutput(ioutil.Discard)
	}

	startTime := time.Now()
	for i := 1; i <= T; i++ {
		if *parser.debugNumber == i {
			log.SetOutput(os.Stderr)
		}
		parser.output.startTime = time.Now()
		parser.runTestCase(i)
		if *parser.debugNumber == i {
			log.SetOutput(ioutil.Discard)
		}
	}

	for key, timer := range parser.output.timers {
		log.Printf("Timer %s: %v", key, timer.Total)
	}
	log.Println("Total time:", time.Now().Sub(startTime))
}

func (parser *Parser) runTestCase(i int) {
	warningTimer := time.NewTimer(1 * time.Second)
	periodicPrintTicker := time.NewTicker(1 * time.Second)

	doneChan := make(chan bool)

	go func() {
		parser.output.init(parser.input, i)
		parser.input.init()

		parser.f(parser.input, parser.output)

		if parser.compareOutput != nil && parser.compareOutput.HasOutput(i) {
			parser.output.AssertEqual(string(parser.compareOutput.GetOutput(i)))
		}

		parser.output.flush()
		parser.writeChart(i)
		doneChan <- true
	}()

loop:
	for {
		select {
		case <-warningTimer.C:
			parser.output.Debug("Long calculation")
		case <-periodicPrintTicker.C:
			parser.output.triggerPeriodic()
		case <-doneChan:
			break loop
		}
	}

	periodicPrintTicker.Stop()
	parser.output.resetPeriodic()
}

func (parser *Parser) writeChart(i int) {
	if len(parser.output.points) == 0 {
		return
	}

	p, err := plot.New()
	if err != nil {
		log.Fatalln("Error creating plot:", err)
	}

	err = plotutil.AddLinePoints(p, "", parser.output.points)
	if err != nil {
		log.Fatalln("Error adding linepoints:", err)
	}

	err = p.Save(4*vg.Inch, 4*vg.Inch, parser.baseFn+strconv.Itoa(i)+".png")
	if err != nil {
		log.Fatalln("Error saving img:", err)
	}

	parser.output.points = parser.output.points[:0]
}
<commit_msg>Added profile as option<commit_after>package io

import (
	"bufio"
	"flag"
	"io"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"os/user"
	"path"
	"path/filepath"
	"runtime/pprof"
	"strconv"
	"strings"
	"time"

	"gonum.org/v1/plot"
	"gonum.org/v1/plot/plotutil"
	"gonum.org/v1/plot/vg"
)

type TestCaseFunc func(*Input, *Output)

type Parser struct {
	f             TestCaseFunc
	input         *Input
	output        *Output
	compareOutput *CompareOutput

	debugNumber *int
	profile     *bool

	baseFn    string
	inputFn   string
	outputFn  string
	correctFn string
	profileFn string
}

func TestCases(f TestCaseFunc) {
	log.SetFlags(0)

	parser := Parser{
		f:           f,
		debugNumber: flag.Int("d", -1, "Debug output only for this case"),
		profile:     flag.Bool("p", false, "Run profiler"),
	}

	inputFileFlag := flag.String("i", "", "Input file")

	flag.Parse()

	inputFile := ""
	if *inputFileFlag != "" {
		inputFile = *inputFileFlag
	} else if len(flag.Args()) == 1 {
		inputFile = flag.Args()[0]
	} else {
		usr, err := user.Current()
		if err != nil {
			log.Fatal(err)
		}
		downloadFiles, err := filepath.Glob(path.Join(usr.HomeDir, "Downloads/*.in"))
		if err != nil {
			log.Fatal(err)
		}
		if len(downloadFiles) > 1 {
			log.Fatal("Multiple input files in Downloads")
		} else if len(downloadFiles) == 1 {
			inputFile = filepath.Base(downloadFiles[0])
			os.Rename(downloadFiles[0], inputFile)
		} else {
			localFiles, err := filepath.Glob("*.in")
			if err != nil {
				log.Fatal(err)
			}
			if len(localFiles) > 1 {
				log.Fatal("Multiple input files found, please specify one!")
			} else if len(localFiles) == 1 {
				inputFile = localFiles[0]
			} else {
				log.Fatal("No input files found")
			}
		}
	}

	if !strings.HasSuffix(inputFile, ".in") {
		if inputFile[len(inputFile)-1] != '.' {
			inputFile += ".in"
		} else {
			inputFile += "in"
		}
	}

	log.Println("Using input file:", inputFile)

	parser.SetFn(inputFile)
	parser.ParseFile()
}

func (parser *Parser) SetFn(inputFn string) {
	parser.inputFn = inputFn
	parser.baseFn = strings.TrimSuffix(inputFn, ".in")
	parser.outputFn = parser.baseFn + ".out"
	parser.correctFn = parser.baseFn + ".correct"
	parser.profileFn = parser.baseFn + ".prof"
}

func (parser *Parser) ParseFile() {
	inputF, err := os.Open(parser.inputFn)
	if err != nil {
		log.Fatalln("Error opening input file:", err)
	}
	defer inputF.Close()

	outputF, err := os.Create(parser.outputFn)
	if err != nil {
		log.Fatalln("Error creating output file:", err)
	}
	defer outputF.Close()

	var outputWriter io.Writer = outputF
	if parser.inputFn == "example.in" {
		outputWriter = io.MultiWriter(outputF, os.Stdout)
	}

	scanner := bufio.NewScanner(inputF)
	scanner.Split(bufio.ScanWords)

	parser.output = newOutput(outputWriter)
	parser.input = newInput(scanner)

	parser.compareOutput = nil
	if _, err := os.Stat(parser.correctFn); err == nil {
		correctF, err := os.Open(parser.correctFn)
		if err != nil {
			log.Fatalln("Error opening correct file:", err)
		}
		defer correctF.Close()

		parser.compareOutput = NewCompareOutput(correctF)
	}

	T := parser.input.Int()

	if *parser.debugNumber != -1 {
		log.SetOutput(ioutil.Discard)
	}

	if *parser.profile {
		f, err := os.Create(parser.profileFn)
		if err != nil {
			log.Fatalln("Error creating profile file:", err)
		}
		pprof.StartCPUProfile(f)
	}

	startTime := time.Now()
	for i := 1; i <= T; i++ {
		if *parser.debugNumber == i {
			log.SetOutput(os.Stderr)
		}
		parser.output.startTime = time.Now()
		parser.runTestCase(i)
		if *parser.debugNumber == i {
			log.SetOutput(ioutil.Discard)
		}
	}
	totalTime := time.Now().Sub(startTime)

	if *parser.profile {
		parser.printProfile()
	}

	for key, timer := range parser.output.timers {
		log.Printf("Timer %s: %v", key, timer.Total)
	}
	log.Println("Total time:", totalTime)
}

func (parser *Parser) runTestCase(i int) {
	warningTimer := time.NewTimer(1 * time.Second)
	stopProfileTimer := time.NewTimer(10 * time.Second)
	periodicPrintTicker := time.NewTicker(1 * time.Second)

	doneChan := make(chan bool)

	go func() {
		parser.output.init(parser.input, i)
		parser.input.init()

		parser.f(parser.input, parser.output)

		if parser.compareOutput != nil && parser.compareOutput.HasOutput(i) {
			parser.output.AssertEqual(string(parser.compareOutput.GetOutput(i)))
		}

		parser.output.flush()
		parser.writeChart(i)
		doneChan <- true
	}()

loop:
	for {
		select {
		case <-warningTimer.C:
			parser.output.Debug("Long calculation")
		case <-periodicPrintTicker.C:
			parser.output.triggerPeriodic()
		case <-stopProfileTimer.C:
			if *parser.profile {
				parser.printProfile()
				log.Fatalln("Profile finished")
			}
		case <-doneChan:
			break loop
		}
	}

	periodicPrintTicker.Stop()
	parser.output.resetPeriodic()
}

func (parser *Parser) printProfile() {
	pprof.StopCPUProfile()
	out, err := exec.Command("go", "tool", "pprof", "-top", os.Args[0], parser.profileFn).CombinedOutput()
	if err != nil {
		log.Fatalln("Error running profile tool:", err)
	}
	parser.output.Debug("CPUProfile:", string(out))

	err = exec.Command("go", "tool", "pprof", "-web", os.Args[0], parser.profileFn).Run()
	if err != nil {
		log.Fatalln("Error running profile tool:", err)
	}
}

func (parser *Parser) writeChart(i int) {
	if len(parser.output.points) == 0 {
		return
	}

	p, err := plot.New()
	if err != nil {
		log.Fatalln("Error creating plot:", err)
	}

	err = plotutil.AddLinePoints(p, "", parser.output.points)
	if err != nil {
		log.Fatalln("Error adding linepoints:", err)
	}

	err = p.Save(4*vg.Inch, 4*vg.Inch, parser.baseFn+strconv.Itoa(i)+".png")
	if err != nil {
		log.Fatalln("Error saving img:", err)
	}

	parser.output.points = parser.output.points[:0]
}
<|endoftext|>