<commit_before>package taggolib

import (
	"io"
	"time"
)

var (
	// mp3MagicNumber is the magic number used to identify a MP3 audio stream
	mp3MagicNumber = []byte("ID3")
)

// MP3Parser represents a MP3 audio metadata tag parser
type MP3Parser struct {
	reader io.ReadSeeker
}

// Album returns the Album tag for this stream
func (m MP3Parser) Album() string {
	return ""
}

// AlbumArtist returns the AlbumArtist tag for this stream
func (m MP3Parser) AlbumArtist() string {
	return ""
}

// Artist returns the Artist tag for this stream
func (m MP3Parser) Artist() string {
	return ""
}

// BitDepth returns the bits-per-sample of this stream
func (m MP3Parser) BitDepth() int {
	return 0
}

// Bitrate calculates the audio bitrate for this stream
func (m MP3Parser) Bitrate() int {
	return 0
}

// Channels returns the number of channels for this stream
func (m MP3Parser) Channels() int {
	return 0
}

// Checksum returns the checksum for this stream
func (m MP3Parser) Checksum() string {
	return ""
}

// Comment returns the Comment tag for this stream
func (m MP3Parser) Comment() string {
	return ""
}

// Date returns the Date tag for this stream
func (m MP3Parser) Date() string {
	return ""
}

// Duration returns the time duration for this stream
func (m MP3Parser) Duration() time.Duration {
	return time.Duration(1 * time.Second)
}

// Encoder returns the encoder for this stream
func (m MP3Parser) Encoder() string {
	return ""
}

// Format returns the name of the MP3 format
func (m MP3Parser) Format() string {
	return "MP3"
}

// Genre returns the Genre tag for this stream
func (m MP3Parser) Genre() string {
	return ""
}

// SampleRate returns the sample rate in Hertz for this stream
func (m MP3Parser) SampleRate() int {
	return 0
}

// Tag attempts to return the raw, unprocessed tag with the specified name for this stream
func (m MP3Parser) Tag(name string) string {
	return ""
}

// Title returns the Title tag for this stream
func (m MP3Parser) Title() string {
	return ""
}

// TrackNumber returns the TrackNumber tag for this stream
func (m MP3Parser) TrackNumber() int {
	return 0
}

// newMP3Parser creates a parser for MP3 audio streams
func newMP3Parser(reader io.ReadSeeker) (*MP3Parser, error) {
	// Create MP3 parser
	parser := &MP3Parser{
		reader: reader,
	}

	// Return parser
	return parser, nil
}
<commit_msg>Initial, working commit of ID3v2.4 MP3 parsing<commit_after>package taggolib

import (
	"encoding/binary"
	"io"
	"strconv"
	"strings"
	"time"

	"github.com/eaburns/bit"
)

const (
	// Tags specific to ID3v2 MP3
	mp3TagEncoder = "ENCODER"
	mp3TagLength  = "LENGTH"
)

var (
	// mp3MagicNumber is the magic number used to identify a MP3 audio stream
	mp3MagicNumber = []byte("ID3")
)

// MP3Parser represents a MP3 audio metadata tag parser
type MP3Parser struct {
	id3Header *mp3ID3v2Header
	mp3Header *mp3Header
	reader    io.ReadSeeker
	tags      map[string]string
}

// Album returns the Album tag for this stream
func (m MP3Parser) Album() string {
	return m.tags[tagAlbum]
}

// AlbumArtist returns the AlbumArtist tag for this stream
func (m MP3Parser) AlbumArtist() string {
	return m.tags[tagAlbumArtist]
}

// Artist returns the Artist tag for this stream
func (m MP3Parser) Artist() string {
	return m.tags[tagArtist]
}

// BitDepth returns the bits-per-sample of this stream
func (m MP3Parser) BitDepth() int {
	return 16
}

// Bitrate calculates the audio bitrate for this stream
func (m MP3Parser) Bitrate() int {
	return mp3BitrateMap[m.mp3Header.Bitrate]
}

// Channels returns the number of channels for this stream
func (m MP3Parser) Channels() int {
	return mp3ChannelModeMap[m.mp3Header.ChannelMode]
}

// Checksum returns the checksum for this stream
func (m MP3Parser) Checksum() string {
	return ""
}

// Comment returns the Comment tag for this stream
func (m MP3Parser) Comment() string {
	return m.tags[tagComment]
}

// Date returns the Date tag for this stream
func (m MP3Parser) Date() string {
	return m.tags[tagDate]
}

// Duration returns the time duration for this stream
func (m MP3Parser) Duration() time.Duration {
	// Parse length as integer
	length, err := strconv.Atoi(m.tags[mp3TagLength])
	if err != nil {
		return time.Duration(0 * time.Second)
	}

	return time.Duration(length/1000) * time.Second
}

// Encoder returns the encoder for this stream
func (m MP3Parser) Encoder() string {
	return m.tags[mp3TagEncoder]
}

// Format returns the name of the MP3 format
func (m MP3Parser) Format() string {
	return "MP3"
}

// Genre returns the Genre tag for this stream
func (m MP3Parser) Genre() string {
	return m.tags[tagGenre]
}

// SampleRate returns the sample rate in Hertz for this stream
func (m MP3Parser) SampleRate() int {
	return mp3SampleRateMap[m.mp3Header.SampleRate]
}

// Tag attempts to return the raw, unprocessed tag with the specified name for this stream
func (m MP3Parser) Tag(name string) string {
	return m.tags[name]
}

// Title returns the Title tag for this stream
func (m MP3Parser) Title() string {
	return m.tags[tagTitle]
}

// TrackNumber returns the TrackNumber tag for this stream
func (m MP3Parser) TrackNumber() int {
	// Check for a /, such as 2/8
	track, err := strconv.Atoi(strings.Split(m.tags[tagTrackNumber], "/")[0])
	if err != nil {
		return 0
	}

	return track
}

// newMP3Parser creates a parser for MP3 audio streams
func newMP3Parser(reader io.ReadSeeker) (*MP3Parser, error) {
	// Create MP3 parser
	parser := &MP3Parser{
		reader: reader,
	}

	// Parse ID3v2 header
	if err := parser.parseID3v2Header(); err != nil {
		return nil, err
	}

	// Parse ID3v2 frames
	if err := parser.parseID3v2Frames(); err != nil {
		return nil, err
	}

	// Parse MP3 header
	if err := parser.parseMP3Header(); err != nil {
		return nil, err
	}

	// Return parser
	return parser, nil
}

// parseID3v2Header parses the ID3v2 header at the start of an MP3 stream
func (m *MP3Parser) parseID3v2Header() error {
	// Create and use a bit reader to parse the following fields
	//   8 - ID3v2 major version
	//   8 - ID3v2 minor version
	//   1 - Unsynchronization (boolean)
	//   1 - Extended (boolean)
	//   1 - Experimental (boolean)
	//   1 - Footer (boolean)
	//   4 - (empty)
	//  24 - Size
	fields, err := bit.NewReader(m.reader).ReadFields(8, 8, 1, 1, 1, 1, 4, 32)
	if err != nil {
		return err
	}

	// Generate ID3v2 header
	m.id3Header = &mp3ID3v2Header{
		MajorVersion:      uint8(fields[0]),
		MinorVersion:      uint8(fields[1]),
		Unsynchronization: fields[2] == 1,
		Extended:          fields[3] == 1,
		Experimental:      fields[4] == 1,
		Footer:            fields[5] == 1,
		Size:              uint32(fields[7]),
	}

	// Check for extended header
	if m.id3Header.Extended {
		// Read size of extended header
		var headerSize uint32
		if err := binary.Read(m.reader, binary.BigEndian, &headerSize); err != nil {
			return err
		}

		// Seek past extended header (minus size of uint32 read), since the information
		// is irrelevant for tag parsing
		if _, err := m.reader.Seek(int64(headerSize) - 4, 1); err != nil {
			return err
		}
	}

	return nil
}

// parseID3v2Frames parses ID3v2 frames from an MP3 stream
func (m *MP3Parser) parseID3v2Frames() error {
	// Continuously loop and parse frames
	tagMap := map[string]string{}
	for {
		// Parse a frame title
		frameBuf := make([]byte, 4)
		if _, err := m.reader.Read(frameBuf); err != nil {
			return err
		}

		// Stop parsing frames when frame title is nil
		if frameBuf[0] == byte(0) {
			// Seek 8 bytes ahead to MP3 audio stream
			if _, err := m.reader.Seek(8, 1); err != nil {
				return err
			}

			// Break tag parsing loop
			break
		}

		// Parse the length of the frame data
		var frameLength uint32
		if err := binary.Read(m.reader, binary.BigEndian, &frameLength); err != nil {
			return err
		}

		// Skip over frame flags
		if _, err := m.reader.Seek(2, 1); err != nil {
			return err
		}

		// Parse the frame data tag
		tagBuf := make([]byte, frameLength)
		if _, err := m.reader.Read(tagBuf); err != nil {
			return err
		}

		// Map frame title to tag title, store frame data, stripping UTF-8 BOM
		tagMap[mp3ID3v2FrameToTag[string(frameBuf)]] = string(tagBuf[1:])
	}

	// Store tags in parser
	m.tags = tagMap
	return nil
}

// mp3ID3v2FrameToTag maps a MP3 ID3v2 frame title to its actual tag name
var mp3ID3v2FrameToTag = map[string]string{
	"COMM": tagComment,
	"TALB": tagAlbum,
	"TCON": tagGenre,
	"TDRC": tagDate,
	"TIT2": tagTitle,
	"TLEN": mp3TagLength,
	"TPE1": tagArtist,
	"TPE2": tagAlbumArtist,
	"TRCK": tagTrackNumber,
	"TSSE": mp3TagEncoder,
}

// mp3ID3v2Header represents the MP3 ID3v2 header section
type mp3ID3v2Header struct {
	MajorVersion      uint8
	MinorVersion      uint8
	Unsynchronization bool
	Extended          bool
	Experimental      bool
	Footer            bool
	Size              uint32
}

// mp3ID3v2ExtendedHeader reperesents the optional MP3 ID3v2 extended header section
type mp3ID3v2ExtendedHeader struct {
	HeaderSize   uint32
	CRC32Present bool
	PaddingSize  uint32
}

// parseMP3Header parses the MP3 header after the ID3 headers in a MP3 stream
func (m *MP3Parser) parseMP3Header() error {
	// Create and use a bit reader to parse the following fields
	//  11 - MP3 frame sync (all bits set)
	//   2 - MPEG audio version ID
	//   2 - Layer description
	//   1 - Protection bit (boolean)
	//   4 - Bitrate index
	fields, err := bit.NewReader(m.reader).ReadFields(11, 2, 2, 1, 4, 2, 1, 1, 2)
	if err != nil {
		return err
	}

	// Create output MP3 header
	m.mp3Header = &mp3Header{
		MPEGVersionID: uint8(fields[1]),
		MPEGLayerID:   uint8(fields[2]),
		Protected:     fields[3] == 0,
		Bitrate:       uint16(fields[4]),
		SampleRate:    uint16(fields[5]),
		Padding:       fields[6] == 1,
		Private:       fields[7] == 1,
		ChannelMode:   uint8(fields[8]),
	}

	return nil
}

// mp3Header represents a MP3 audio stream header, and contains information about the stream
type mp3Header struct {
	MPEGVersionID uint8
	MPEGLayerID   uint8
	Protected     bool
	Bitrate       uint16
	SampleRate    uint16
	Padding       bool
	Private       bool
	ChannelMode   uint8
}

// mp3BitrateMap maps MPEG Layer 3 Version 1 bitrate to its actual rate
var mp3BitrateMap = map[uint16]int{
	0:  0,
	1:  32,
	2:  40,
	3:  48,
	4:  56,
	5:  64,
	6:  80,
	7:  96,
	8:  112,
	9:  128,
	10: 160,
	11: 192,
	12: 224,
	13: 256,
	14: 320,
}

// mp3SampleRateMap maps MPEG Layer 3 Version 1 sample rate to its actual rate
var mp3SampleRateMap = map[uint16]int{
	0: 44100,
	1: 48000,
	2: 32000,
}

// mp3ChannelModeMap maps MPEG Layer 3 Version 1 channels to the number of channels
var mp3ChannelModeMap = map[uint8]int{
	0: 2,
	1: 2,
	3: 2,
	4: 1,
}
<|endoftext|>


<commit_before>package template

import (
	"github.com/kataras/go-errors"
	"github.com/valyala/bytebufferpool"
	"io"
	"path/filepath"
)

type (
	// Entries the template Engines with their loader
	Entries []*Entry

	// Entry contains a template Engine and its Loader
	Entry struct {
		Loader *Loader
		Engine Engine
	}

	// Mux is an optional feature, used when you want to use multiple template engines
	// It stores the loaders with each of the template engine,
	// the identifier of each template engine is the (loader's) Extension
	// the registry finds the correct template engine and executes the template
	// so you can use and render a template file by it's file extension
	Mux struct {
		// Reload reloads the template engine on each execute, used when the project is under development status
		// if true the template will reflect the runtime template files changes
		// defaults to false
		Reload bool

		// Entries the template Engines with their loader
		Entries Entries
		// SharedFuncs funcs that will be shared all over the supported template engines
		SharedFuncs map[string]interface{}

		buffer *bytebufferpool.Pool
	}
)

// LoadEngine loads the Engine using its registered loader
// Internal Note:
// Loader can be used without a mux because of this we have this type of function here which just pass itself's field into other itself's field
// which, normally, is not a smart choice.
func (entry *Entry) LoadEngine() error {
	return entry.Loader.LoadEngine(entry.Engine)
}

// LoadAll loads all template engines entries, returns the first error
func (entries Entries) LoadAll() error {
	for i, n := 0, len(entries); i < n; i++ {
		if err := entries[i].LoadEngine(); err != nil {
			return err
		}
	}
	return nil
}

// Find receives a filename, gets its extension and returns the template engine responsible for that file extension
func (entries Entries) Find(filename string) *Entry {
	extension := filepath.Ext(filename)
	// Read-Only no locks needed, at serve/runtime-time the library is not supposed to add new template engines
	for i, n := 0, len(entries); i < n; i++ {
		e := entries[i]
		if e.Loader.Extension == extension {
			return e
		}
	}
	return nil
}

var defaultMux = NewMux()

// NewMux returns a new Mux
// Mux is an optional feature, used when you want to use multiple template engines
// It stores the loaders with each of the template engine,
// the identifier of each template engine is the (loader's) Extension
// the registry finds the correct template engine and executes the template
// so you can use and render a template file by it's file extension
func NewMux(sharedFuncs ...map[string]interface{}) *Mux {
	m := &Mux{Reload: false, Entries: Entries{}, buffer: &bytebufferpool.Pool{}}
	if len(sharedFuncs) > 0 {
		m.SharedFuncs = sharedFuncs[0]
	}
	return m
}

// AddEngine adds but not loads a template engine, returns the entry's Loader
func AddEngine(e Engine) *Loader {
	return defaultMux.AddEngine(e)
}

// AddEngine adds but not loads a template engine, returns the entry's Loader
func (m *Mux) AddEngine(e Engine) *Loader {

	// add the shared  funcs
	if funcer, ok := e.(EngineFuncs); ok {
		if funcer.Funcs() != nil && m.SharedFuncs != nil {
			for k, v := range m.SharedFuncs {
				funcer.Funcs()[k] = v
			}
		}
	}
	entry := &Entry{Engine: e, Loader: NewLoader()}
	m.Entries = append(m.Entries, entry)
	// returns the entry's Loader(pointer)
	return entry.Loader
}

// Load loads all template engines entries, returns the first error
// it just calls and returns the Entries.LoadALl
func Load() error {
	return defaultMux.Load()
}

// Load loads all template engines entries, returns the first error
// it just calls and returns the Entries.LoadALl
func (m *Mux) Load() error {
	return m.Entries.LoadAll()
}

var errNoTemplateEngineForExt = errors.New("No template engine found for '%s'")

// ExecuteWriter calls the correct template Engine's ExecuteWriter func
func ExecuteWriter(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (err error) {
	return defaultMux.ExecuteWriter(out, name, binding, options...)
}

// ExecuteWriter calls the correct template Engine's ExecuteWriter func
func (m *Mux) ExecuteWriter(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (err error) {
	if m == nil {
		//file extension, but no template engine registered
		return errNoTemplateEngineForExt.Format(filepath.Ext(name))
	}

	entry := m.Entries.Find(name)

	if m.Reload {
		if err = entry.LoadEngine(); err != nil {
			return
		}
	}

	return entry.Engine.ExecuteWriter(out, name, binding, options...)
}

// ExecuteString executes a template from a specific template engine and returns its contents result as string, it doesn't renders
func ExecuteString(name string, binding interface{}, options ...map[string]interface{}) (result string, err error) {
	return defaultMux.ExecuteString(name, binding, options...)
}

// ExecuteString executes a template from a specific template engine and returns its contents result as string, it doesn't renders
func (m *Mux) ExecuteString(name string, binding interface{}, options ...map[string]interface{}) (result string, err error) {
	out := m.buffer.Get()
	defer m.buffer.Put(out)
	err = m.ExecuteWriter(out, name, binding, options...)
	if err == nil {
		result = out.String()
	}
	return
}

var errNoTemplateEngineSupportsRawParsing = errors.New("Not found a valid template engine found which supports raw parser")

// ExecuteRaw read moreon template.go:EngineRawParser
// parse with the first valid EngineRawParser
func ExecuteRaw(src string, wr io.Writer, binding interface{}) error {
	return defaultMux.ExecuteRaw(src, wr, binding)
}

// ExecuteRaw read moreon template.go:EngineRawParser
// parse with the first valid EngineRawParser
func (m *Mux) ExecuteRaw(src string, wr io.Writer, binding interface{}) error {
	if m == nil {
		//file extension, but no template engine registered
		return errNoTemplateEngineForExt.Format(src)
	}

	for _, e := range m.Entries {
		if p, is := e.Engine.(EngineRawExecutor); is {
			return p.ExecuteRaw(src, wr, binding)
		}
	}
	return errNoTemplateEngineSupportsRawParsing
}

// ExecuteRawString receives raw template source contents and returns it's result as string
func ExecuteRawString(src string, binding interface{}) (result string, err error) {
	return defaultMux.ExecuteRawString(src, binding)
}

// ExecuteRawString receives raw template source contents and returns it's result as string
func (m *Mux) ExecuteRawString(src string, binding interface{}) (result string, err error) {
	out := m.buffer.Get()
	defer m.buffer.Put(out)
	err = m.ExecuteRaw(src, out, binding)
	if err == nil {
		result = out.String()
	}
	return
}
<commit_msg>Add an error if template file didn't found ...<commit_after>package template

import (
	"github.com/kataras/go-errors"
	"github.com/valyala/bytebufferpool"
	"io"
	"path/filepath"
)

type (
	// Entries the template Engines with their loader
	Entries []*Entry

	// Entry contains a template Engine and its Loader
	Entry struct {
		Loader *Loader
		Engine Engine
	}

	// Mux is an optional feature, used when you want to use multiple template engines
	// It stores the loaders with each of the template engine,
	// the identifier of each template engine is the (loader's) Extension
	// the registry finds the correct template engine and executes the template
	// so you can use and render a template file by it's file extension
	Mux struct {
		// Reload reloads the template engine on each execute, used when the project is under development status
		// if true the template will reflect the runtime template files changes
		// defaults to false
		Reload bool

		// Entries the template Engines with their loader
		Entries Entries
		// SharedFuncs funcs that will be shared all over the supported template engines
		SharedFuncs map[string]interface{}

		buffer *bytebufferpool.Pool
	}
)

// LoadEngine loads the Engine using its registered loader
// Internal Note:
// Loader can be used without a mux because of this we have this type of function here which just pass itself's field into other itself's field
// which, normally, is not a smart choice.
func (entry *Entry) LoadEngine() error {
	return entry.Loader.LoadEngine(entry.Engine)
}

// LoadAll loads all template engines entries, returns the first error
func (entries Entries) LoadAll() error {
	for i, n := 0, len(entries); i < n; i++ {
		if err := entries[i].LoadEngine(); err != nil {
			return err
		}
	}
	return nil
}

// Find receives a filename, gets its extension and returns the template engine responsible for that file extension
func (entries Entries) Find(filename string) *Entry {
	extension := filepath.Ext(filename)
	// Read-Only no locks needed, at serve/runtime-time the library is not supposed to add new template engines
	for i, n := 0, len(entries); i < n; i++ {
		e := entries[i]
		if e.Loader.Extension == extension {
			return e
		}
	}
	return nil
}

var defaultMux = NewMux()

// NewMux returns a new Mux
// Mux is an optional feature, used when you want to use multiple template engines
// It stores the loaders with each of the template engine,
// the identifier of each template engine is the (loader's) Extension
// the registry finds the correct template engine and executes the template
// so you can use and render a template file by it's file extension
func NewMux(sharedFuncs ...map[string]interface{}) *Mux {
	m := &Mux{Reload: false, Entries: Entries{}, buffer: &bytebufferpool.Pool{}}
	if len(sharedFuncs) > 0 {
		m.SharedFuncs = sharedFuncs[0]
	}
	return m
}

// AddEngine adds but not loads a template engine, returns the entry's Loader
func AddEngine(e Engine) *Loader {
	return defaultMux.AddEngine(e)
}

// AddEngine adds but not loads a template engine, returns the entry's Loader
func (m *Mux) AddEngine(e Engine) *Loader {

	// add the shared  funcs
	if funcer, ok := e.(EngineFuncs); ok {
		if funcer.Funcs() != nil && m.SharedFuncs != nil {
			for k, v := range m.SharedFuncs {
				funcer.Funcs()[k] = v
			}
		}
	}
	entry := &Entry{Engine: e, Loader: NewLoader()}
	m.Entries = append(m.Entries, entry)
	// returns the entry's Loader(pointer)
	return entry.Loader
}

// Load loads all template engines entries, returns the first error
// it just calls and returns the Entries.LoadALl
func Load() error {
	return defaultMux.Load()
}

// Load loads all template engines entries, returns the first error
// it just calls and returns the Entries.LoadALl
func (m *Mux) Load() error {
	return m.Entries.LoadAll()
}

var (
	errNoTemplateEngineForExt = errors.New("No template engine found for '%s'")
	errTemplateNotFound       = errors.New("Template %s was not found")
)

// ExecuteWriter calls the correct template Engine's ExecuteWriter func
func ExecuteWriter(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (err error) {
	return defaultMux.ExecuteWriter(out, name, binding, options...)
}

// ExecuteWriter calls the correct template Engine's ExecuteWriter func
func (m *Mux) ExecuteWriter(out io.Writer, name string, binding interface{}, options ...map[string]interface{}) (err error) {
	if m == nil {
		//file extension, but no template engine registered
		return errNoTemplateEngineForExt.Format(filepath.Ext(name))
	}

	entry := m.Entries.Find(name)
	if entry == nil {
		return errTemplateNotFound.Format(name)
	}

	if m.Reload {
		if err = entry.LoadEngine(); err != nil {
			return
		}
	}

	return entry.Engine.ExecuteWriter(out, name, binding, options...)
}

// ExecuteString executes a template from a specific template engine and returns its contents result as string, it doesn't renders
func ExecuteString(name string, binding interface{}, options ...map[string]interface{}) (result string, err error) {
	return defaultMux.ExecuteString(name, binding, options...)
}

// ExecuteString executes a template from a specific template engine and returns its contents result as string, it doesn't renders
func (m *Mux) ExecuteString(name string, binding interface{}, options ...map[string]interface{}) (result string, err error) {
	out := m.buffer.Get()
	defer m.buffer.Put(out)
	err = m.ExecuteWriter(out, name, binding, options...)
	if err == nil {
		result = out.String()
	}
	return
}

var errNoTemplateEngineSupportsRawParsing = errors.New("Not found a valid template engine found which supports raw parser")

// ExecuteRaw read moreon template.go:EngineRawParser
// parse with the first valid EngineRawParser
func ExecuteRaw(src string, wr io.Writer, binding interface{}) error {
	return defaultMux.ExecuteRaw(src, wr, binding)
}

// ExecuteRaw read moreon template.go:EngineRawParser
// parse with the first valid EngineRawParser
func (m *Mux) ExecuteRaw(src string, wr io.Writer, binding interface{}) error {
	if m == nil {
		//file extension, but no template engine registered
		return errNoTemplateEngineForExt.Format(src)
	}

	for _, e := range m.Entries {
		if p, is := e.Engine.(EngineRawExecutor); is {
			return p.ExecuteRaw(src, wr, binding)
		}
	}
	return errNoTemplateEngineSupportsRawParsing
}

// ExecuteRawString receives raw template source contents and returns it's result as string
func ExecuteRawString(src string, binding interface{}) (result string, err error) {
	return defaultMux.ExecuteRawString(src, binding)
}

// ExecuteRawString receives raw template source contents and returns it's result as string
func (m *Mux) ExecuteRawString(src string, binding interface{}) (result string, err error) {
	out := m.buffer.Get()
	defer m.buffer.Put(out)
	err = m.ExecuteRaw(src, out, binding)
	if err == nil {
		result = out.String()
	}
	return
}
<|endoftext|>


<commit_before>// Ogo

package ogo

/* {{{ import
 */
import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/Odinman/ogo/libs/config"
	"github.com/Odinman/ogo/libs/logs"
	"github.com/Odinman/ogo/utils"
	omq "github.com/Odinman/omq/utils"
)

/* }}} */

/* {{{ type Environ struct
 */
type Environ struct {
	lock          *sync.RWMutex
	WorkPath      string         // working path(abs)
	AppPath       string         // application path
	ProcName      string         // proc name
	Worker        string         // worker name
	AppConfigPath string         // config file path
	RunMode       string         // run mode, "dev" or "prod"
	AccessPath    string         //acces log file path
	Daemonize     bool           // daemonize or not
	EnableGzip    bool           // enable gzip or not
	DebugLevel    int            // debug level
	PidFile       string         // pidfile abs path
	Port          string         // http port
	IndentJSON    bool           // indent JSON
	MaxMemory     int64          //max memory(form-data)
	Location      *time.Location // location
	initErr       error
}

/* }}} */

/* {{{ type Mux struct
 */
type Mux struct {
	env      *Environ               //环境参数
	cfg      config.ConfigContainer //配置信息
	logger   *logs.OLogger          //debug日志记录
	accessor *logs.OLogger          //日志
	omqpool  *omq.Pool              // omq连接池
	Workers  map[string]*Worker
	Routes   map[string]*Route
	Hooks    HStack
	TagHooks map[string]TagHook
}

/* }}} */

/* {{{ func New() *Mux
 */
func New() *Mux {
	return &Mux{
		Workers: make(map[string]*Worker),
		Routes:  make(map[string]*Route),
		Hooks: HStack{
			preHooks:  make([]OgoHook, 0),
			postHooks: make([]OgoHook, 0),
		},
		TagHooks: make(map[string]TagHook),
	}
}

/* }}} */

/* {{{ func (mux *Mux) PreHook(hook OgoHook)
 * 正式程序之前的钩子
 */
func (mux *Mux) PreHook(hook OgoHook) {
	mux.Hooks.preHooks = append(mux.Hooks.preHooks, hook)
}

/* }}} */

/* {{{ func (mux *Mux) PostHook(hook OgoHook)
 * 正式程序之后的钩子
 */
func (mux *Mux) PostHook(hook OgoHook) {
	mux.Hooks.postHooks = append(mux.Hooks.postHooks, hook)
}

/* }}} */

/* {{{ func (mux *Mux) AddTagHook(tag string, hook TagHook)
 * 正式程序之后的钩子
 */
func (mux *Mux) AddTagHook(tag string, hook TagHook) {
	mux.TagHooks[tag] = hook
}

/* }}} */

/* {{{ func (mux *Mux) NewRouter(c RouterInterface, endpoint string) RouterInterface
 * 这样做的目的是给rounter设置mux(mux可多个) -- mux=multiplexer,复用器
 */
func (mux *Mux) NewRouter(c RouterInterface, endpoint string) RouterInterface {
	c.New(mux, endpoint)
	return c
}

/* }}} */

/* {{{ func (mux *Mux) Env(path string) *Environ, error
 * 获取env变量
 */
func (mux *Mux) Env() (*Environ, error) {

	if mux.env == nil {
		env = &Environ{
			lock: new(sync.RWMutex),
		}

		// default value
		env.RunMode = "dev"     //default runmod
		env.Port = DEFAULT_PORT //default is 8001
		env.Daemonize = false
		env.DebugLevel = logs.LevelTrace //默认debug等级
		env.IndentJSON = false
		env.MaxMemory = 1 << 26                              //64MB
		env.Location, _ = time.LoadLocation("Asia/Shanghai") //默认上海时区

		workPath, _ := os.Getwd()
		env.WorkPath, _ = filepath.Abs(workPath)
		env.AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
		env.ProcName = filepath.Base(os.Args[0])   //程序名字
		env.Worker = strings.ToLower(env.ProcName) //worker默认为procname,小写
		env.AccessPath = "logs/access.log"         //默认access日志是程序目录下的logs/access.log

		//默认配置文件是 conf/{ProcName}.conf
		env.AppConfigPath = filepath.Join(env.AppPath, "conf", env.ProcName+".conf")
		if !utils.FileExists(env.AppConfigPath) {
			//不存在时指定为app.conf
			tp := filepath.Join(env.AppPath, "conf", "app.conf")
			if utils.FileExists(tp) {
				env.AppConfigPath = tp
			}
		}

		// pidfile depend AppPath
		env.PidFile = filepath.Join(env.AppPath, "run", env.ProcName+".pid")

		if env.WorkPath != env.AppPath {
			//切换工作目录
			os.Chdir(env.AppPath)
		}

		//配置文件必须存在
		if !utils.FileExists(env.AppConfigPath) {
			return nil, fmt.Errorf("config file not found: %s", env.AppConfigPath)
		}

		mux.env = env
	}

	return mux.env, nil
}

/* }}} */

/* {{{ func (mux *Mux) initEnv(cfg config.ConfigContainer) error
 * 设置环境变量
 */
func (mux *Mux) initEnv() (err error) {
	if env, err = mux.Env(); err != nil {
		return err
	}
	if cfg, err = mux.Config(); err != nil {
		return err
	}

	//根据配置设置环境变量
	env.lock.Lock()
	defer env.lock.Unlock()

	// location
	if tz := cfg.String("TimeZone"); tz != "" {
		if loc, err := time.LoadLocation(tz); err == nil {
			env.Location = loc
		}
	}

	if port := cfg.String("Port"); port != "" {
		env.Port = port
	}
	os.Setenv("PORT", env.Port) // pass to bind

	if runmode := cfg.String("RunMode"); runmode != "" {
		env.RunMode = runmode
	}

	if workerName := cfg.String("Worker"); workerName != "" {
		env.Worker = workerName
	}

	if daemonize, err := cfg.Bool("Daemonize"); err == nil {
		env.Daemonize = daemonize
	}
	if enablegzip, err := cfg.Bool("EnableGzip"); err == nil {
		env.EnableGzip = enablegzip
	}
	if indentJson, err := cfg.Bool("IndentJson"); err == nil {
		env.IndentJSON = indentJson
	}
	// 自定义pidfile
	if pidfile := cfg.String("PidFile"); pidfile != "" {
		// make sure pidfile is abs path
		if filepath.IsAbs(pidfile) {
			env.PidFile = pidfile
		} else {
			env.PidFile = filepath.Join(env.AppPath, pidfile)
		}
	}
	//自定义access 日志位置
	if apath := cfg.String("AccessPath"); apath != "" {
		env.AccessPath = apath
	}
	if level, err := cfg.Int("DebugLevel"); err == nil {
		env.DebugLevel = level
	}

	// logger init
	if logger, err = mux.Logger(); err != nil {
		return err
	}

	//access init
	if accessor, err = mux.Accessor(); err != nil {
		return err
	}

	//omq
	if omqpool, err = mux.OmqPool(); err != nil {
		Warn("omq error: %s", err)
	}

	//db init,目前只有mysql
	if dns := cfg.String("data::dns"); dns != "" {
		OpenDB(DBTAG, dns)
	}

	return nil
}

/* }}} */

/* {{{ func (mux *Mux) Config() (config.ConfigContainer, error)
 * 获取配置信息
 */
func (mux *Mux) Config() (config.ConfigContainer, error) {
	if mux.cfg == nil {
		if env, err := mux.Env(); err != nil {
			return nil, err
		} else {
			cp := env.AppConfigPath
			if cfg, err := config.NewConfig("ini", cp); err != nil {
				return nil, err
			} else {
				mux.cfg = cfg
			}
		}
	}
	return mux.cfg, nil
}

/* }}} */

/* {{{ func (mux *Mux) Logger() config.LoggerContainer
 *
 */
func (mux *Mux) Logger() (*logs.OLogger, error) {
	if mux.logger == nil {
		// init logger
		logger := logs.NewLogger(2046)
		var err error
		if mux.env.Daemonize {
			err = logger.SetLogger("file", `{"filename":"logs/debug.log"}`)
		} else {
			err = logger.SetLogger("console", "")
		}

		if err != nil {
			return nil, err
		}

		logger.EnableFuncCallDepth(true)
		logger.SetLogFuncCallDepth(4)
		logger.SetLevel(mux.env.DebugLevel)
		mux.logger = logger
	}

	return mux.logger, nil
}

/* }}} */

/* {{{ func (mux *Mux) Accessor() config.LoggerContainer
 *
 */
func (mux *Mux) Accessor() (*logs.OLogger, error) {
	if mux.accessor == nil {
		// init logger
		logger := logs.NewLogger(2046)
		var err error
		err = logger.SetLogger("access", fmt.Sprintf(`{"filename":"%s"}`, mux.env.AccessPath))

		if err != nil {
			return nil, err
		}

		mux.accessor = logger
	}

	return mux.accessor, nil
}

/* }}} */

/* {{{ func (mux *Mux) OmqPool() (*omq.Pool, error)
 *
 */
func (mux *Mux) OmqPool() (*omq.Pool, error) {
	if mux.omqpool == nil {
		if cfg, err := mux.Config(); err != nil {
			return nil, err
		} else {
			var omqHost, omqPort string
			var omqMax int
			if omqHost = cfg.String("omq::host"); omqHost != "" {
				if omqPort = cfg.String("omq::port"); omqPort == "" {
					omqPort = "7000"
				}
				if max, err := cfg.Int("omq::max"); err != nil {
					omqMax = max
				} else {
					omqMax = 100
				}
				Info("[omq][%s:%s]", omqHost, omqPort)
				mux.omqpool = omq.NewPool(omq.ReqNewer(fmt.Sprint("tcp://", omqHost, ":", omqPort)), omqMax, 60*time.Second)
			} else {
				return nil, fmt.Errorf("[omq]not found config info")
			}
		}
	}

	return mux.omqpool, nil
}

/* }}} */
<commit_msg>update<commit_after>// Ogo

package ogo

/* {{{ import
 */
import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"

	"github.com/Odinman/ogo/libs/config"
	"github.com/Odinman/ogo/libs/logs"
	"github.com/Odinman/ogo/utils"
	omq "github.com/Odinman/omq/utils"
)

/* }}} */

/* {{{ type Environ struct
 */
type Environ struct {
	lock          *sync.RWMutex
	WorkPath      string         // working path(abs)
	AppPath       string         // application path
	ProcName      string         // proc name
	Worker        string         // worker name
	AppConfigPath string         // config file path
	RunMode       string         // run mode, "dev" or "prod"
	AccessPath    string         //acces log file path
	Daemonize     bool           // daemonize or not
	EnableGzip    bool           // enable gzip or not
	DebugLevel    int            // debug level
	PidFile       string         // pidfile abs path
	Port          string         // http port
	IndentJSON    bool           // indent JSON
	MaxMemory     int64          //max memory(form-data)
	Location      *time.Location // location
	initErr       error
}

/* }}} */

/* {{{ type Mux struct
 */
type Mux struct {
	env      *Environ               //环境参数
	cfg      config.ConfigContainer //配置信息
	logger   *logs.OLogger          //debug日志记录
	accessor *logs.OLogger          //日志
	omqpool  *omq.Pool              // omq连接池
	Workers  map[string]*Worker
	Routes   map[string]*Route
	Hooks    HStack
	TagHooks map[string]TagHook
}

/* }}} */

/* {{{ func New() *Mux
 */
func New() *Mux {
	return &Mux{
		Workers: make(map[string]*Worker),
		Routes:  make(map[string]*Route),
		Hooks: HStack{
			preHooks:  make([]OgoHook, 0),
			postHooks: make([]OgoHook, 0),
		},
		TagHooks: make(map[string]TagHook),
	}
}

/* }}} */

/* {{{ func (mux *Mux) PreHook(hook OgoHook)
 * 正式程序之前的钩子
 */
func (mux *Mux) PreHook(hook OgoHook) {
	mux.Hooks.preHooks = append(mux.Hooks.preHooks, hook)
}

/* }}} */

/* {{{ func (mux *Mux) PostHook(hook OgoHook)
 * 正式程序之后的钩子
 */
func (mux *Mux) PostHook(hook OgoHook) {
	mux.Hooks.postHooks = append(mux.Hooks.postHooks, hook)
}

/* }}} */

/* {{{ func (mux *Mux) AddTagHook(tag string, hook TagHook)
 * 正式程序之后的钩子
 */
func (mux *Mux) AddTagHook(tag string, hook TagHook) {
	mux.TagHooks[tag] = hook
}

/* }}} */

/* {{{ func (mux *Mux) NewRouter(c RouterInterface, endpoint string) RouterInterface
 * 这样做的目的是给rounter设置mux(mux可多个) -- mux=multiplexer,复用器
 */
func (mux *Mux) NewRouter(c RouterInterface, endpoint string) RouterInterface {
	c.New(mux, endpoint)
	return c
}

/* }}} */

/* {{{ func (mux *Mux) Env(path string) *Environ, error
 * 获取env变量
 */
func (mux *Mux) Env() (*Environ, error) {

	if mux.env == nil {
		env = &Environ{
			lock: new(sync.RWMutex),
		}

		// default value
		env.RunMode = "dev"     //default runmod
		env.Port = DEFAULT_PORT //default is 8001
		env.Daemonize = false
		env.DebugLevel = logs.LevelTrace //默认debug等级
		env.IndentJSON = false
		env.MaxMemory = 1 << 26                              //64MB
		env.Location, _ = time.LoadLocation("Asia/Shanghai") //默认上海时区

		workPath, _ := os.Getwd()
		env.WorkPath, _ = filepath.Abs(workPath)
		env.AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
		env.ProcName = filepath.Base(os.Args[0])   //程序名字
		env.Worker = strings.ToLower(env.ProcName) //worker默认为procname,小写
		env.AccessPath = "logs/access.log"         //默认access日志是程序目录下的logs/access.log

		//默认配置文件是 conf/{ProcName}.conf
		env.AppConfigPath = filepath.Join(env.AppPath, "conf", env.ProcName+".conf")
		if !utils.FileExists(env.AppConfigPath) {
			//不存在时指定为app.conf
			tp := filepath.Join(env.AppPath, "conf", "app.conf")
			if utils.FileExists(tp) {
				env.AppConfigPath = tp
			}
		}

		// pidfile depend AppPath
		env.PidFile = filepath.Join(env.AppPath, "run", env.ProcName+".pid")

		if env.WorkPath != env.AppPath {
			//切换工作目录
			os.Chdir(env.AppPath)
		}

		//配置文件必须存在
		if !utils.FileExists(env.AppConfigPath) {
			return nil, fmt.Errorf("config file not found: %s", env.AppConfigPath)
		}

		mux.env = env
	}

	return mux.env, nil
}

/* }}} */

/* {{{ func (mux *Mux) initEnv(cfg config.ConfigContainer) error
 * 设置环境变量
 */
func (mux *Mux) initEnv() (err error) {
	if env, err = mux.Env(); err != nil {
		return err
	}
	if cfg, err = mux.Config(); err != nil {
		return err
	}

	//根据配置设置环境变量
	env.lock.Lock()
	defer env.lock.Unlock()

	// location
	if tz := cfg.String("TimeZone"); tz != "" {
		if loc, err := time.LoadLocation(tz); err == nil {
			env.Location = loc
		}
	}

	if port := cfg.String("Port"); port != "" {
		env.Port = port
	}
	os.Setenv("PORT", env.Port) // pass to bind

	if runmode := cfg.String("RunMode"); runmode != "" {
		env.RunMode = runmode
	}

	if workerName := cfg.String("Worker"); workerName != "" {
		env.Worker = workerName
	}

	if daemonize, err := cfg.Bool("Daemonize"); err == nil {
		env.Daemonize = daemonize
	}
	if enablegzip, err := cfg.Bool("EnableGzip"); err == nil {
		env.EnableGzip = enablegzip
	}
	if indentJson, err := cfg.Bool("IndentJson"); err == nil {
		env.IndentJSON = indentJson
	}
	// 自定义pidfile
	if pidfile := cfg.String("PidFile"); pidfile != "" {
		// make sure pidfile is abs path
		if filepath.IsAbs(pidfile) {
			env.PidFile = pidfile
		} else {
			env.PidFile = filepath.Join(env.AppPath, pidfile)
		}
	}
	//自定义access 日志位置
	if apath := cfg.String("AccessPath"); apath != "" {
		env.AccessPath = apath
	}
	if level, err := cfg.Int("DebugLevel"); err == nil {
		env.DebugLevel = level
	}

	// logger init
	if logger, err = mux.Logger(); err != nil {
		return err
	}

	//access init
	if accessor, err = mux.Accessor(); err != nil {
		return err
	}

	//omq
	if omqpool, err = mux.OmqPool(); err != nil {
		Warn("omq error: %s", err)
	}

	//db init,目前只有mysql
	if dns := cfg.String("data::dns"); dns != "" {
		OpenDB(DBTAG, dns)
	}

	return nil
}

/* }}} */

/* {{{ func (mux *Mux) Config() (config.ConfigContainer, error)
 * 获取配置信息
 */
func (mux *Mux) Config() (config.ConfigContainer, error) {
	if mux.cfg == nil {
		if env, err := mux.Env(); err != nil {
			return nil, err
		} else {
			cp := env.AppConfigPath
			if cfg, err := config.NewConfig("ini", cp); err != nil {
				return nil, err
			} else {
				mux.cfg = cfg
			}
		}
	}
	return mux.cfg, nil
}

/* }}} */

/* {{{ func (mux *Mux) Logger() config.LoggerContainer
 *
 */
func (mux *Mux) Logger() (*logs.OLogger, error) {
	if mux.logger == nil {
		// init logger
		logger := logs.NewLogger(2046)
		var err error
		if mux.env.Daemonize {
			err = logger.SetLogger("file", `{"filename":"logs/debug.log"}`)
		} else {
			err = logger.SetLogger("console", "")
		}

		if err != nil {
			return nil, err
		}

		logger.EnableFuncCallDepth(true)
		logger.SetLogFuncCallDepth(4)
		logger.SetLevel(mux.env.DebugLevel)
		mux.logger = logger
	}

	return mux.logger, nil
}

/* }}} */

/* {{{ func (mux *Mux) Accessor() config.LoggerContainer
 *
 */
func (mux *Mux) Accessor() (*logs.OLogger, error) {
	if mux.accessor == nil {
		// init logger
		logger := logs.NewLogger(2046)
		var err error
		err = logger.SetLogger("access", fmt.Sprintf(`{"filename":"%s"}`, mux.env.AccessPath))

		if err != nil {
			return nil, err
		}

		mux.accessor = logger
	}

	return mux.accessor, nil
}

/* }}} */

/* {{{ func (mux *Mux) OmqPool() (*omq.Pool, error)
 *
 */
func (mux *Mux) OmqPool() (*omq.Pool, error) {
	if mux.omqpool == nil {
		if cfg, err := mux.Config(); err != nil {
			return nil, err
		} else {
			var omqHost, omqPort string
			var omqMax int
			if omqHost = cfg.String("omq::host"); omqHost != "" {
				if omqPort = cfg.String("omq::port"); omqPort == "" {
					omqPort = "7000"
				}
				if max, err := cfg.Int("omq::max"); err != nil {
					omqMax = max
				} else {
					omqMax = 100
				}
				Info("[omq][%s:%s]", omqHost, omqPort)
				mux.omqpool = omq.NewPool(omq.ReqNewer(fmt.Sprint("tcp://", omqHost, ":", omqPort)), omqMax, 60*time.Second, Logger())
			} else {
				return nil, fmt.Errorf("[omq]not found config info")
			}
		}
	}

	return mux.omqpool, nil
}

/* }}} */
<|endoftext|>


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

package mux

import (
	"container/list"
	"net/http"
	"strings"
	"sync"

	"github.com/issue9/context"
)

// ServeMux 是 http.ServeMux 的升级版，可处理对 URL 的正则匹配和根据 METHOD 进行过滤。
//
// 用法如下：
//  m := mux.NewServeMux()
//  m.Get("www.example.com/abc", h1).              // 只匹配 www.example.com 域名下的路径
//    Post("/abc/"", h2).                          // 不限定域名的路径匹配
//    Add("/api/{version:\\d+}",h3, http.MethodGet, "POST") // 只匹配 GET 和 POST
//  http.ListenAndServe(m)
//
//
// 路由参数：
//
// 路由参数可通过context包获取：
//  ctx := context.Get(req)
//  params := ctx.Get("params") // 若不存在路由参数，则返回一个空值
// NOTE：记得在退出整个请求之前清除 context 中的内容：
//  context.Free(req)
//
//
// 匹配规则：
//
// 可能会出现多条记录与同一请求都匹配的情况，这种情况下，
// 系统会找到一条认为最匹配的路由来处理，判断规则如下：
//  1. 静态路由优先于正则路由判断；
//  2. 带域名的路由项优先于不带域名的路由项；
//  3. 完全匹配的路由项优先于部分匹配的路由项；
//  4. 正则只能是完全匹配；
//  5. 只有以/结尾的静态路由才有部分匹配功能；
//  6. 匹配顺序和插入顺序无关。
//
// 正则匹配语法：
//  /post/{id}     // 匹配 /post/ 开头的任意字符串，其后的字符串保存到 id 中；
//  /post/{id:\d+} // 同上，但 id 的值只能为 \d+；
//  /post/{:\d+}   // 同上，但是没有命名；
type ServeMux struct {
	// 同时处理 entries,base,options 三个的竟争问题
	mu sync.RWMutex

	// 路由项，键名为请求方法名称，键值为路由项的列表。
	entries map[string]*list.List

	// base 记录了对应的 entries 下的各个请求方法对应的路由项的基点，方便数据在各个方向插入。
	base map[string]*list.Element

	// 各个路由项已开通的方法，即 OPTIONS 请求方法对应的值。
	options map[string]int16
}

// NewServeMux 声明一个新的 ServeMux
func NewServeMux() *ServeMux {
	entries := make(map[string]*list.List, len(supportedMethods))
	for _, method := range supportedMethods {
		entries[method] = list.New()
	}

	return &ServeMux{
		entries: entries,
		base:    make(map[string]*list.Element, len(supportedMethods)),
		options: map[string]int16{},
	}
}

// 添加一个路由项。
func (mux *ServeMux) add(pattern string, h http.Handler, methods ...string) *ServeMux {
	if len(pattern) == 0 {
		panic("参数pattern不能为空")
	}

	if h == nil {
		panic("参数h不能为空")
	}

	if len(methods) == 0 {
		methods = supportedMethods
	}

	e := newEntry(pattern, h)
	for _, method := range methods {
		mux.addOne(e, pattern, strings.ToUpper(method))
	}

	mux.addOptions(pattern, methods)

	return mux
}

// 添加一条记录。
//
// 若路由项已经存在或是请求方法不支持，则直接 panic。
// 若 method 的值为 OPTIONS，则相同的路由项会被覆盖，而不是 panic。
func (mux *ServeMux) addOne(entry entryer, pattern string, method string) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if method != "OPTIONS" { // OPTIONS 则不检测是否已经存在，存在则执行覆盖操作
		entries, found := mux.entries[method]
		if !found {
			panic("不支持的请求方法：" + method)
		}

		for item := entries.Front(); item != nil; item = item.Next() {
			if e := item.Value.(entryer); e.getPattern() == pattern {
				panic("该路由项已经存在：[" + method + "]" + pattern)
			}
		}
	}

	switch {
	case mux.entries[method].Len() == 0:
		mux.base[method] = mux.entries[method].PushFront(entry)
	case pattern[0] != '/' && !entry.isRegexp(): // 带域名的静态路由，在前端插入
		mux.entries[method].PushFront(entry)
	case pattern[0] != '/' && entry.isRegexp(): // 带域名的正则路由，在后端插入
		mux.entries[method].InsertBefore(entry, mux.base[method])
	case pattern[0] == '/' && !entry.isRegexp(): // 不带域名的静态路由，在前端插入
		mux.entries[method].InsertAfter(entry, mux.base[method])
	case pattern[0] == '/' && entry.isRegexp(): // 不带域名的正则路由，在后端插入
		mux.entries[method].PushBack(entry)
	}
}

// 添加一条 OPTIONS 记录。
func (mux *ServeMux) addOptions(pattern string, methods []string) {
	list, found := mux.options[pattern]
	for _, method := range methods {
		list |= toint[method]
	}
	// 加上 options，若已经存在，也不会有影响
	mux.options[pattern] = (list | options)

	// 在未初始化该路由项的情况下，为其添加一个请求方法为 OPTIONS 的路由
	if !found && !inStringSlice(methods, "OPTIONS") {
		h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			r.Header.Set("Allow", getAllowString(mux.options[pattern]))
		})

		e := newEntry(pattern, h)
		mux.addOne(e, pattern, "OPTIONS")
	}
}

// Clean 清除所有的路由项
func (mux *ServeMux) Clean() *ServeMux {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	mux.options = map[string]int16{}

	mux.base = map[string]*list.Element{}

	for _, method := range supportedMethods {
		l, found := mux.entries[method]
		if found {
			l.Init()
		}
	}

	return mux
}

// 移除指定的路由项，通过路由表达式和 method 来匹配。
// 当未指定 methods 时，将删除所有 method 匹配的项。
// 指定错误的 methods 值，将自动忽略该值。
func (mux *ServeMux) Remove(pattern string, methods ...string) {
	if len(methods) == 0 { // 删除所有method 下匹配的项
		methods = supportedMethods
	}

	mux.mu.Lock()
	defer mux.mu.Unlock()

	// 清除路由项
	mux.options[pattern] = mux.options[pattern] & (^methodsToInt(methods...))
	if mux.options[pattern] == options { // 只剩下options了，则清空
		mux.options[pattern] = 0
		methods = append(methods, "OPTIONS")
	}

	for _, method := range methods {
		entries, found := mux.entries[method]
		if !found {
			continue
		}

		for item := entries.Front(); item != nil; item = item.Next() {
			e := item.Value.(entryer)
			if e.getPattern() != pattern {
				continue
			}

			// 清除mux.base相关的内容
			if item == mux.base[method] {
				switch {
				case item.Next() == nil:
					mux.base[method] = item.Next()
				case item.Prev() == nil:
					mux.base[method] = item.Prev()
				default:
					mux.base[method] = nil
				}
			}

			// 清除路由项
			entries.Remove(item)

			break // 最多只有一个匹配
		}
	} // end for methods
}

// Add 添加一条路由数据。
//
// pattern 为路由匹配模式，可以是正则匹配也可以是字符串匹配，
// 可以带上域名，当第一个字符为'/'当作是一个路径，否则就将'/'之前的当作域名或IP。
// methods 参数应该只能为 supportedMethods 中的字符串，若不指定，默认为所有，
// 当 h 或是 pattern 为空时，将触发 panic。
func (mux *ServeMux) Add(pattern string, h http.Handler, methods ...string) *ServeMux {
	return mux.add(pattern, h, methods...)
}

// 手动指定 OPTIONS 请求方法的值。
//
// 若无特殊需求，不用调用此方法，系统会自动计算符合当前路由的请求方法列表。
func (mux *ServeMux) Options(pattern string, allowMethods ...string) *ServeMux {
	mux.options[pattern] = methodsToInt(allowMethods...)
	return mux
}

// Get 相当于 ServeMux.Add(pattern, h, "GET") 的简易写法
func (mux *ServeMux) Get(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodGet)
}

// Post 相当于 ServeMux.Add(pattern, h, "POST") 的简易写法
func (mux *ServeMux) Post(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodPost)
}

// Delete 相当于 ServeMux.Add(pattern, h, "DELETE") 的简易写法
func (mux *ServeMux) Delete(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodDelete)
}

// Put 相当于 ServeMux.Add(pattern, h, "PUT") 的简易写法
func (mux *ServeMux) Put(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodPut)
}

// Patch 相当于 ServeMux.Add(pattern, h, "PATCH") 的简易写法
func (mux *ServeMux) Patch(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodPatch)
}

// Any 相当于 ServeMux.Add(pattern, h) 的简易写法
func (mux *ServeMux) Any(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, defaultMethods...)
}

func (mux *ServeMux) addFunc(pattern string, fun func(http.ResponseWriter, *http.Request), methods ...string) *ServeMux {
	return mux.add(pattern, http.HandlerFunc(fun), methods...)
}

// AddFunc 功能同 ServeMux.Add()，但是将第二个参数从 http.Handler 换成了 func(http.ResponseWriter, *http.Request)
func (mux *ServeMux) AddFunc(pattern string, fun func(http.ResponseWriter, *http.Request), methods ...string) *ServeMux {
	return mux.Add(pattern, http.HandlerFunc(fun), methods...)
}

// GetFunc 相当于 ServeMux.AddFunc(pattern, func, "GET") 的简易写法
func (mux *ServeMux) GetFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodGet)
}

// PutFunc 相当于 ServeMux.AddFunc(pattern, func, "PUT") 的简易写法
func (mux *ServeMux) PutFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodPut)
}

// PostFunc 相当于 ServeMux.AddFunc(pattern, func, "POST") 的简易写法
func (mux *ServeMux) PostFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodPost)
}

// DeleteFunc 相当于 ServeMux.AddFunc(pattern, func, "DELETE") 的简易写法
func (mux *ServeMux) DeleteFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodDelete)
}

// PatchFunc 相当于 ServeMux.AddFunc(pattern, func, "PATCH") 的简易写法
func (mux *ServeMux) PatchFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodPatch)
}

// AnyFunc 相当于 ServeMux.AddFunc(pattern, func) 的简易写法
func (mux *ServeMux) AnyFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, defaultMethods...)
}

// 查找最匹配的路由项
func (mux *ServeMux) match(r *http.Request) (p string, e entryer) {
	hostURL := r.Host + r.URL.Path
	size := -1 // 匹配度，0 表示完全匹配，负数表示完全不匹配，其它值越小匹配度越高

	mux.mu.RLock()
	defer mux.mu.RUnlock()

	for item := mux.entries[r.Method].Front(); item != nil; item = item.Next() {
		entry := item.Value.(entryer)

		if entry.getPattern()[0] != '/' {
			p = hostURL
		} else {
			p = r.URL.Path
		}

		s := entry.match(p)
		if s == -1 || (size > 0 && s >= size) { // 完全不匹配，或是匹配度没有当前的高
			continue
		}

		size = s
		e = entry

		if s == 0 { // 完全匹配，可以中止匹配过程
			return p, e
		}
	} // end for

	if size < 0 {
		return "", nil
	}
	return p, e
}

// 若没有找到匹配路由，返回 404
func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p, e := mux.match(r)

	if e == nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	params := e.getParams(p)
	if params != nil {
		ctx := context.Get(r)
		ctx.Set("params", params)
	}
	e.serveHTTP(w, r)
}
<commit_msg>使用 http.MethodOptions 代替字符串 OPTIONS<commit_after>// Copyright 2014 by caixw, All rights reserved.
// Use of this source code is governed by a MIT
// license that can be found in the LICENSE file.

package mux

import (
	"container/list"
	"net/http"
	"strings"
	"sync"

	"github.com/issue9/context"
)

// ServeMux 是 http.ServeMux 的升级版，可处理对 URL 的正则匹配和根据 METHOD 进行过滤。
//
// 用法如下：
//  m := mux.NewServeMux()
//  m.Get("www.example.com/abc", h1).              // 只匹配 www.example.com 域名下的路径
//    Post("/abc/"", h2).                          // 不限定域名的路径匹配
//    Add("/api/{version:\\d+}",h3, http.MethodGet, "POST") // 只匹配 GET 和 POST
//  http.ListenAndServe(m)
//
//
// 路由参数：
//
// 路由参数可通过context包获取：
//  ctx := context.Get(req)
//  params := ctx.Get("params") // 若不存在路由参数，则返回一个空值
// NOTE：记得在退出整个请求之前清除 context 中的内容：
//  context.Free(req)
//
//
// 匹配规则：
//
// 可能会出现多条记录与同一请求都匹配的情况，这种情况下，
// 系统会找到一条认为最匹配的路由来处理，判断规则如下：
//  1. 静态路由优先于正则路由判断；
//  2. 带域名的路由项优先于不带域名的路由项；
//  3. 完全匹配的路由项优先于部分匹配的路由项；
//  4. 正则只能是完全匹配；
//  5. 只有以/结尾的静态路由才有部分匹配功能；
//  6. 匹配顺序和插入顺序无关。
//
// 正则匹配语法：
//  /post/{id}     // 匹配 /post/ 开头的任意字符串，其后的字符串保存到 id 中；
//  /post/{id:\d+} // 同上，但 id 的值只能为 \d+；
//  /post/{:\d+}   // 同上，但是没有命名；
type ServeMux struct {
	// 同时处理 entries,base,options 三个的竟争问题
	mu sync.RWMutex

	// 路由项，键名为请求方法名称，键值为路由项的列表。
	entries map[string]*list.List

	// base 记录了对应的 entries 下的各个请求方法对应的路由项的基点，方便数据在各个方向插入。
	base map[string]*list.Element

	// 各个路由项已开通的方法，即 OPTIONS 请求方法对应的值。
	options map[string]int16
}

// NewServeMux 声明一个新的 ServeMux
func NewServeMux() *ServeMux {
	entries := make(map[string]*list.List, len(supportedMethods))
	for _, method := range supportedMethods {
		entries[method] = list.New()
	}

	return &ServeMux{
		entries: entries,
		base:    make(map[string]*list.Element, len(supportedMethods)),
		options: map[string]int16{},
	}
}

// 添加一个路由项。
func (mux *ServeMux) add(pattern string, h http.Handler, methods ...string) *ServeMux {
	if len(pattern) == 0 {
		panic("参数pattern不能为空")
	}

	if h == nil {
		panic("参数h不能为空")
	}

	if len(methods) == 0 {
		methods = supportedMethods
	}

	e := newEntry(pattern, h)
	for _, method := range methods {
		mux.addOne(e, pattern, strings.ToUpper(method))
	}

	mux.addOptions(pattern, methods)

	return mux
}

// 添加一条记录。
//
// 若路由项已经存在或是请求方法不支持，则直接 panic。
// 若 method 的值为 OPTIONS，则相同的路由项会被覆盖，而不是 panic。
func (mux *ServeMux) addOne(entry entryer, pattern string, method string) {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	if method != http.MethodOptions { // OPTIONS 则不检测是否已经存在，存在则执行覆盖操作
		entries, found := mux.entries[method]
		if !found {
			panic("不支持的请求方法：" + method)
		}

		for item := entries.Front(); item != nil; item = item.Next() {
			if e := item.Value.(entryer); e.getPattern() == pattern {
				panic("该路由项已经存在：[" + method + "]" + pattern)
			}
		}
	}

	switch {
	case mux.entries[method].Len() == 0:
		mux.base[method] = mux.entries[method].PushFront(entry)
	case pattern[0] != '/' && !entry.isRegexp(): // 带域名的静态路由，在前端插入
		mux.entries[method].PushFront(entry)
	case pattern[0] != '/' && entry.isRegexp(): // 带域名的正则路由，在后端插入
		mux.entries[method].InsertBefore(entry, mux.base[method])
	case pattern[0] == '/' && !entry.isRegexp(): // 不带域名的静态路由，在前端插入
		mux.entries[method].InsertAfter(entry, mux.base[method])
	case pattern[0] == '/' && entry.isRegexp(): // 不带域名的正则路由，在后端插入
		mux.entries[method].PushBack(entry)
	}
}

// 添加一条 OPTIONS 记录。
func (mux *ServeMux) addOptions(pattern string, methods []string) {
	list, found := mux.options[pattern]
	for _, method := range methods {
		list |= toint[method]
	}
	// 加上 options，若已经存在，也不会有影响
	mux.options[pattern] = (list | options)

	// 在未初始化该路由项的情况下，为其添加一个请求方法为 OPTIONS 的路由
	if !found && !inStringSlice(methods, http.MethodOptions) {
		h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			r.Header.Set("Allow", getAllowString(mux.options[pattern]))
		})

		e := newEntry(pattern, h)
		mux.addOne(e, pattern, http.MethodOptions)
	}
}

// Clean 清除所有的路由项
func (mux *ServeMux) Clean() *ServeMux {
	mux.mu.Lock()
	defer mux.mu.Unlock()

	mux.options = map[string]int16{}

	mux.base = map[string]*list.Element{}

	for _, method := range supportedMethods {
		l, found := mux.entries[method]
		if found {
			l.Init()
		}
	}

	return mux
}

// 移除指定的路由项，通过路由表达式和 method 来匹配。
// 当未指定 methods 时，将删除所有 method 匹配的项。
// 指定错误的 methods 值，将自动忽略该值。
func (mux *ServeMux) Remove(pattern string, methods ...string) {
	if len(methods) == 0 { // 删除所有method 下匹配的项
		methods = supportedMethods
	}

	mux.mu.Lock()
	defer mux.mu.Unlock()

	// 清除路由项
	mux.options[pattern] = mux.options[pattern] & (^methodsToInt(methods...))
	if mux.options[pattern] == options { // 只剩下options了，则清空
		mux.options[pattern] = 0
		methods = append(methods, http.MethodOptions)
	}

	for _, method := range methods {
		entries, found := mux.entries[method]
		if !found {
			continue
		}

		for item := entries.Front(); item != nil; item = item.Next() {
			e := item.Value.(entryer)
			if e.getPattern() != pattern {
				continue
			}

			// 清除mux.base相关的内容
			if item == mux.base[method] {
				switch {
				case item.Next() == nil:
					mux.base[method] = item.Next()
				case item.Prev() == nil:
					mux.base[method] = item.Prev()
				default:
					mux.base[method] = nil
				}
			}

			// 清除路由项
			entries.Remove(item)

			break // 最多只有一个匹配
		}
	} // end for methods
}

// Add 添加一条路由数据。
//
// pattern 为路由匹配模式，可以是正则匹配也可以是字符串匹配，
// 可以带上域名，当第一个字符为'/'当作是一个路径，否则就将'/'之前的当作域名或IP。
// methods 参数应该只能为 supportedMethods 中的字符串，若不指定，默认为所有，
// 当 h 或是 pattern 为空时，将触发 panic。
func (mux *ServeMux) Add(pattern string, h http.Handler, methods ...string) *ServeMux {
	return mux.add(pattern, h, methods...)
}

// 手动指定 OPTIONS 请求方法的值。
//
// 若无特殊需求，不用调用此方法，系统会自动计算符合当前路由的请求方法列表。
func (mux *ServeMux) Options(pattern string, allowMethods ...string) *ServeMux {
	mux.options[pattern] = methodsToInt(allowMethods...)
	return mux
}

// Get 相当于 ServeMux.Add(pattern, h, "GET") 的简易写法
func (mux *ServeMux) Get(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodGet)
}

// Post 相当于 ServeMux.Add(pattern, h, "POST") 的简易写法
func (mux *ServeMux) Post(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodPost)
}

// Delete 相当于 ServeMux.Add(pattern, h, "DELETE") 的简易写法
func (mux *ServeMux) Delete(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodDelete)
}

// Put 相当于 ServeMux.Add(pattern, h, "PUT") 的简易写法
func (mux *ServeMux) Put(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodPut)
}

// Patch 相当于 ServeMux.Add(pattern, h, "PATCH") 的简易写法
func (mux *ServeMux) Patch(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, http.MethodPatch)
}

// Any 相当于 ServeMux.Add(pattern, h) 的简易写法
func (mux *ServeMux) Any(pattern string, h http.Handler) *ServeMux {
	return mux.Add(pattern, h, defaultMethods...)
}

func (mux *ServeMux) addFunc(pattern string, fun func(http.ResponseWriter, *http.Request), methods ...string) *ServeMux {
	return mux.add(pattern, http.HandlerFunc(fun), methods...)
}

// AddFunc 功能同 ServeMux.Add()，但是将第二个参数从 http.Handler 换成了 func(http.ResponseWriter, *http.Request)
func (mux *ServeMux) AddFunc(pattern string, fun func(http.ResponseWriter, *http.Request), methods ...string) *ServeMux {
	return mux.Add(pattern, http.HandlerFunc(fun), methods...)
}

// GetFunc 相当于 ServeMux.AddFunc(pattern, func, "GET") 的简易写法
func (mux *ServeMux) GetFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodGet)
}

// PutFunc 相当于 ServeMux.AddFunc(pattern, func, "PUT") 的简易写法
func (mux *ServeMux) PutFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodPut)
}

// PostFunc 相当于 ServeMux.AddFunc(pattern, func, "POST") 的简易写法
func (mux *ServeMux) PostFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodPost)
}

// DeleteFunc 相当于 ServeMux.AddFunc(pattern, func, "DELETE") 的简易写法
func (mux *ServeMux) DeleteFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodDelete)
}

// PatchFunc 相当于 ServeMux.AddFunc(pattern, func, "PATCH") 的简易写法
func (mux *ServeMux) PatchFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, http.MethodPatch)
}

// AnyFunc 相当于 ServeMux.AddFunc(pattern, func) 的简易写法
func (mux *ServeMux) AnyFunc(pattern string, fun func(http.ResponseWriter, *http.Request)) *ServeMux {
	return mux.AddFunc(pattern, fun, defaultMethods...)
}

// 查找最匹配的路由项
func (mux *ServeMux) match(r *http.Request) (p string, e entryer) {
	hostURL := r.Host + r.URL.Path
	size := -1 // 匹配度，0 表示完全匹配，负数表示完全不匹配，其它值越小匹配度越高

	mux.mu.RLock()
	defer mux.mu.RUnlock()

	for item := mux.entries[r.Method].Front(); item != nil; item = item.Next() {
		entry := item.Value.(entryer)

		if entry.getPattern()[0] != '/' {
			p = hostURL
		} else {
			p = r.URL.Path
		}

		s := entry.match(p)
		if s == -1 || (size > 0 && s >= size) { // 完全不匹配，或是匹配度没有当前的高
			continue
		}

		size = s
		e = entry

		if s == 0 { // 完全匹配，可以中止匹配过程
			return p, e
		}
	} // end for

	if size < 0 {
		return "", nil
	}
	return p, e
}

// 若没有找到匹配路由，返回 404
func (mux *ServeMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	p, e := mux.match(r)

	if e == nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}

	params := e.getParams(p)
	if params != nil {
		ctx := context.Get(r)
		ctx.Set("params", params)
	}
	e.serveHTTP(w, r)
}
<|endoftext|>


<commit_before>package chromedp

import (
	"context"
	"errors"
	"sync"

	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/cdproto/page"
)

// Navigate navigates the current frame.
func Navigate(urlstr string) Action {
	return ActionFunc(func(ctx context.Context) error {
		frameID, _, _, err := page.Navigate(urlstr).Do(ctx)
		if err != nil {
			return err
		}
		waitLoaded(ctx, frameID)
		return nil
	})
}

func waitLoaded(ctx context.Context, frameID cdp.FrameID) {
	ctx, cancel := context.WithCancel(ctx)
	var wg sync.WaitGroup
	wg.Add(1)
	ListenTarget(ctx, func(ev interface{}) {
		evs, ok := ev.(*page.EventFrameStoppedLoading)
		if ok && evs.FrameID == frameID {
			cancel()
			wg.Done()
		}
	})
	wg.Wait()
}

// NavigationEntries is an action to retrieve the page's navigation history
// entries.
func NavigationEntries(currentIndex *int64, entries *[]*page.NavigationEntry) Action {
	if currentIndex == nil || entries == nil {
		panic("currentIndex and entries cannot be nil")
	}

	return ActionFunc(func(ctx context.Context) error {
		var err error
		*currentIndex, *entries, err = page.GetNavigationHistory().Do(ctx)
		return err
	})
}

// NavigateToHistoryEntry is an action to navigate to the specified navigation
// entry.
func NavigateToHistoryEntry(entryID int64) Action {
	return ActionFunc(func(ctx context.Context) error {
		frameID := FromContext(ctx).Target.cur.ID
		if err := page.NavigateToHistoryEntry(entryID).Do(ctx); err != nil {
			return err
		}
		waitLoaded(ctx, frameID)
		return nil
	})
}

// NavigateBack navigates the current frame backwards in its history.
func NavigateBack() Action {
	return ActionFunc(func(ctx context.Context) error {
		cur, entries, err := page.GetNavigationHistory().Do(ctx)
		if err != nil {
			return err
		}

		if cur <= 0 || cur > int64(len(entries)-1) {
			return errors.New("invalid navigation entry")
		}

		frameID := FromContext(ctx).Target.cur.ID
		entryID := entries[cur-1].ID
		if err := page.NavigateToHistoryEntry(entryID).Do(ctx); err != nil {
			return err
		}
		waitLoaded(ctx, frameID)
		return nil
	})
}

// NavigateForward navigates the current frame forwards in its history.
func NavigateForward() Action {
	return ActionFunc(func(ctx context.Context) error {
		cur, entries, err := page.GetNavigationHistory().Do(ctx)
		if err != nil {
			return err
		}

		if cur < 0 || cur >= int64(len(entries)-1) {
			return errors.New("invalid navigation entry")
		}

		frameID := FromContext(ctx).Target.cur.ID
		entryID := entries[cur+1].ID
		if err := page.NavigateToHistoryEntry(entryID).Do(ctx); err != nil {
			return err
		}
		waitLoaded(ctx, frameID)
		return nil
	})
}

// Reload reloads the current page.
func Reload() Action {
	return ActionFunc(func(ctx context.Context) error {
		frameID := FromContext(ctx).Target.cur.ID
		if err := page.Reload().Do(ctx); err != nil {
			return err
		}
		waitLoaded(ctx, frameID)
		return nil
	})
}

// Stop stops all navigation and pending resource retrieval.
func Stop() Action {
	return page.StopLoading()
}

// CaptureScreenshot captures takes a screenshot of the current viewport.
//
// Note: this an alias for page.CaptureScreenshot.
func CaptureScreenshot(res *[]byte) Action {
	if res == nil {
		panic("res cannot be nil")
	}

	return ActionFunc(func(ctx context.Context) error {
		var err error
		*res, err = page.CaptureScreenshot().Do(ctx)
		return err
	})
}

// AddOnLoadScript adds a script to evaluate on page load.
/*func AddOnLoadScript(source string, id *page.ScriptIdentifier) Action {
	if id == nil {
		panic("id cannot be nil")
	}

	return ActionFunc(func(ctx context.Context) error {
		var err error
		*id, err = page.AddScriptToEvaluateOnLoad(source).Do(ctx)
		return err
	})
}

// RemoveOnLoadScript removes a script to evaluate on page load.
func RemoveOnLoadScript(id page.ScriptIdentifier) Action {
	return page.RemoveScriptToEvaluateOnLoad(id)
}*/

// Location retrieves the document location.
func Location(urlstr *string) Action {
	if urlstr == nil {
		panic("urlstr cannot be nil")
	}

	return EvaluateAsDevTools(`document.location.toString()`, urlstr)
}

// Title retrieves the document title.
func Title(title *string) Action {
	if title == nil {
		panic("title cannot be nil")
	}

	return EvaluateAsDevTools(`document.title`, title)
}
<commit_msg>set up load event listeners before running cmds<commit_after>package chromedp

import (
	"context"
	"errors"

	"github.com/chromedp/cdproto/cdp"
	"github.com/chromedp/cdproto/page"
)

// Navigate navigates the current frame.
func Navigate(urlstr string) Action {
	return ActionFunc(func(ctx context.Context) error {
		ch := listenLoaded(ctx)
		frameID, _, _, err := page.Navigate(urlstr).Do(ctx)
		if err != nil {
			return err
		}
		ch <- frameID
		<-ch
		return nil
	})
}

// listenLoaded sets up a listener before running an action that will load a
// frame, so that later we can block until said frame has finished loading. A
// channel is used to receive the frame ID to wait for and to block, since
// page.Navigate returns the ID, but the listener must be set up before.
func listenLoaded(ctx context.Context) chan cdp.FrameID {
	ch := make(chan cdp.FrameID)
	ctx, cancel := context.WithCancel(ctx)
	var frameID cdp.FrameID
	ListenTarget(ctx, func(ev interface{}) {
		evs, ok := ev.(*page.EventFrameStoppedLoading)
		if !ok {
			return
		}
		if frameID == "" {
			frameID = <-ch
		}
		if evs.FrameID == frameID {
			cancel()
			close(ch)
		}
	})
	return ch
}

// NavigationEntries is an action to retrieve the page's navigation history
// entries.
func NavigationEntries(currentIndex *int64, entries *[]*page.NavigationEntry) Action {
	if currentIndex == nil || entries == nil {
		panic("currentIndex and entries cannot be nil")
	}

	return ActionFunc(func(ctx context.Context) error {
		var err error
		*currentIndex, *entries, err = page.GetNavigationHistory().Do(ctx)
		return err
	})
}

// NavigateToHistoryEntry is an action to navigate to the specified navigation
// entry.
func NavigateToHistoryEntry(entryID int64) Action {
	return ActionFunc(func(ctx context.Context) error {
		ch := listenLoaded(ctx)
		frameID := FromContext(ctx).Target.cur.ID
		if err := page.NavigateToHistoryEntry(entryID).Do(ctx); err != nil {
			return err
		}
		ch <- frameID
		<-ch
		return nil
	})
}

// NavigateBack navigates the current frame backwards in its history.
func NavigateBack() Action {
	return ActionFunc(func(ctx context.Context) error {
		cur, entries, err := page.GetNavigationHistory().Do(ctx)
		if err != nil {
			return err
		}

		if cur <= 0 || cur > int64(len(entries)-1) {
			return errors.New("invalid navigation entry")
		}

		ch := listenLoaded(ctx)
		frameID := FromContext(ctx).Target.cur.ID
		entryID := entries[cur-1].ID
		if err := page.NavigateToHistoryEntry(entryID).Do(ctx); err != nil {
			return err
		}
		ch <- frameID
		<-ch
		return nil
	})
}

// NavigateForward navigates the current frame forwards in its history.
func NavigateForward() Action {
	return ActionFunc(func(ctx context.Context) error {
		cur, entries, err := page.GetNavigationHistory().Do(ctx)
		if err != nil {
			return err
		}

		if cur < 0 || cur >= int64(len(entries)-1) {
			return errors.New("invalid navigation entry")
		}

		ch := listenLoaded(ctx)
		frameID := FromContext(ctx).Target.cur.ID
		entryID := entries[cur+1].ID
		if err := page.NavigateToHistoryEntry(entryID).Do(ctx); err != nil {
			return err
		}
		ch <- frameID
		<-ch
		return nil
	})
}

// Reload reloads the current page.
func Reload() Action {
	return ActionFunc(func(ctx context.Context) error {
		ch := listenLoaded(ctx)
		frameID := FromContext(ctx).Target.cur.ID
		if err := page.Reload().Do(ctx); err != nil {
			return err
		}
		ch <- frameID
		<-ch
		return nil
	})
}

// Stop stops all navigation and pending resource retrieval.
func Stop() Action {
	return page.StopLoading()
}

// CaptureScreenshot captures takes a screenshot of the current viewport.
//
// Note: this an alias for page.CaptureScreenshot.
func CaptureScreenshot(res *[]byte) Action {
	if res == nil {
		panic("res cannot be nil")
	}

	return ActionFunc(func(ctx context.Context) error {
		var err error
		*res, err = page.CaptureScreenshot().Do(ctx)
		return err
	})
}

// AddOnLoadScript adds a script to evaluate on page load.
/*func AddOnLoadScript(source string, id *page.ScriptIdentifier) Action {
	if id == nil {
		panic("id cannot be nil")
	}

	return ActionFunc(func(ctx context.Context) error {
		var err error
		*id, err = page.AddScriptToEvaluateOnLoad(source).Do(ctx)
		return err
	})
}

// RemoveOnLoadScript removes a script to evaluate on page load.
func RemoveOnLoadScript(id page.ScriptIdentifier) Action {
	return page.RemoveScriptToEvaluateOnLoad(id)
}*/

// Location retrieves the document location.
func Location(urlstr *string) Action {
	if urlstr == nil {
		panic("urlstr cannot be nil")
	}

	return EvaluateAsDevTools(`document.location.toString()`, urlstr)
}

// Title retrieves the document title.
func Title(title *string) Action {
	if title == nil {
		panic("title cannot be nil")
	}

	return EvaluateAsDevTools(`document.title`, title)
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Che Wei, Lin
//
// 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 tinynet

import (
	"fmt"

	log "github.com/sirupsen/logrus"
)

// AddHost will add a host to topology.
func AddHost(name string, addr string, docker bool) (*Host, error) {

	if docker {
		// Create a docker container
		h, err := NewContainer(name, "library/busybox")
		if err != nil {
			log.Fatal("failed to NewContainer: ", err)
		}
	} else {
		// Create a network namespace
		h, err := NewHost(name)
		if err != nil {
			log.Fatal("failed to NewHost: ", err)
		}
	}
	// setup a veth pair
	_, err := h.setupVeth("eth2", 1500)
	if err != nil {
		log.Fatal("failed to open netns: ", err)
	}
	// setup a IP for host
	h.setIfaceIP(addr)
	if err != nil {
		log.Fatalf("failed to setIfaceIP for %s: %v", h.Name, err)
		return nil, err
	}
	return h, nil
}

// AddSwitch will add a switch to topology.
func AddSwitch(params ...string) (*OVSSwitch, error) {
	// params[0] for brName
	// params[1] for controller remote IP and port
	// Create a Open vSwitch bridge
	sw, err := NewOVSSwitch(params[0])
	if err != nil {
		log.Fatal("failed to NewOVSSwitch: ", err)
	}
	if len(params) == 2 {
		if err := sw.setCtrl(params[1]); err != nil {
			log.Warnf("failed to setCtrl for %s: %v", sw.BridgeName, err)
			return nil, err
		}
	} else if len(params) > 2 {
		return nil, fmt.Errorf("Too many arguments")
	}
	return sw, nil
}

// AddLink will add a link between switch to switch or host to switch.
func AddLink(n1, n2 interface{}) error {
	// log.Info(n1.(*OVSSwitch).nodeType)
	// log.Info(n2.(*Host).nodeType)
	var err error
	switch n1.(type) {
	case *OVSSwitch:
		switch n2.(type) {
		case *OVSSwitch:
			tap0, tap1, err := makeVethPair("tap0", "tap1")
			if err != nil {
				log.Fatalf("failed to makeVethPair: %v", err)
			}
			err = n1.(*OVSSwitch).addPort(tap0.Name)
			if err != nil {
				log.Fatalf("failed to addPortswitch - switch: %v", err)
			}
			err = n2.(*OVSSwitch).addPort(tap1.Name)
			if err != nil {
				log.Fatalf("failed to addPort switch - switch: %v", err)
			}
			log.Infof("Adding a link: (%s, %s)", n1.(*OVSSwitch).BridgeName, n2.(*OVSSwitch).BridgeName)
		case *Host:
			err = n1.(*OVSSwitch).addPort(n2.(*Host).vethName)
			if err != nil {
				log.Fatalf("failed to addPort switch - host: %v", err)
			}
			log.Infof("Adding a link: (%s, %s)", n1.(*OVSSwitch).BridgeName, n2.(*Host).Name)
		default:
			log.Fatalf("Type Error")
		}
	case *Host:
		switch n2.(type) {
		case *OVSSwitch:
			err = n2.(*OVSSwitch).addPort(n1.(*Host).vethName)
			if err != nil {
				log.Fatalf("failed to addPort host - switch : %v", err)
			}
			log.Infof("Adding a link: (%s, %s)", n1.(*Host).Name, n2.(*OVSSwitch).BridgeName)
		case *Host:
			log.Fatalf("Type Error: host can not connect to host")
		default:
			log.Fatalf("Type Error")
		}
	default:
		log.Fatalf("Type Error")
	}
	return nil
}

// GetIPs will get IP address from a CIDR notation
// Return an array with IP address. negative num for getting all IP addresses
// (network address and broadcast address are not included)
func GetIPs(cidr string, num int) ([]string, error) {
	ips, err := getAllIPsfromCIDR(cidr)
	if err != nil {
		return nil, err
	}
	if num < 0 {
		return ips, nil
	}
	return ips[0:num], nil
}
<commit_msg>fixed<commit_after>// Copyright (c) 2017 Che Wei, Lin
//
// 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 tinynet

import (
	"fmt"

	log "github.com/sirupsen/logrus"
)

// AddHost will add a host to topology.
func AddHost(name string, addr string, docker bool) (*Host, error) {
	var h *Host
	if docker {
		// Create a docker container
		h, err = NewContainer(name, "library/busybox")
		if err != nil {
			log.Fatal("failed to NewContainer: ", err)
		}
	} else {
		// Create a network namespace
		h, err = NewHost(name)
		if err != nil {
			log.Fatal("failed to NewHost: ", err)
		}
	}
	// setup a veth pair
	_, err := h.setupVeth("eth2", 1500)
	if err != nil {
		log.Fatal("failed to open netns: ", err)
	}
	// setup a IP for host
	h.setIfaceIP(addr)
	if err != nil {
		log.Fatalf("failed to setIfaceIP for %s: %v", h.Name, err)
		return nil, err
	}
	return h, nil
}

// AddSwitch will add a switch to topology.
func AddSwitch(params ...string) (*OVSSwitch, error) {
	// params[0] for brName
	// params[1] for controller remote IP and port
	// Create a Open vSwitch bridge
	sw, err := NewOVSSwitch(params[0])
	if err != nil {
		log.Fatal("failed to NewOVSSwitch: ", err)
	}
	if len(params) == 2 {
		if err := sw.setCtrl(params[1]); err != nil {
			log.Warnf("failed to setCtrl for %s: %v", sw.BridgeName, err)
			return nil, err
		}
	} else if len(params) > 2 {
		return nil, fmt.Errorf("Too many arguments")
	}
	return sw, nil
}

// AddLink will add a link between switch to switch or host to switch.
func AddLink(n1, n2 interface{}) error {
	// log.Info(n1.(*OVSSwitch).nodeType)
	// log.Info(n2.(*Host).nodeType)
	var err error
	switch n1.(type) {
	case *OVSSwitch:
		switch n2.(type) {
		case *OVSSwitch:
			tap0, tap1, err := makeVethPair("tap0", "tap1")
			if err != nil {
				log.Fatalf("failed to makeVethPair: %v", err)
			}
			err = n1.(*OVSSwitch).addPort(tap0.Name)
			if err != nil {
				log.Fatalf("failed to addPortswitch - switch: %v", err)
			}
			err = n2.(*OVSSwitch).addPort(tap1.Name)
			if err != nil {
				log.Fatalf("failed to addPort switch - switch: %v", err)
			}
			log.Infof("Adding a link: (%s, %s)", n1.(*OVSSwitch).BridgeName, n2.(*OVSSwitch).BridgeName)
		case *Host:
			err = n1.(*OVSSwitch).addPort(n2.(*Host).vethName)
			if err != nil {
				log.Fatalf("failed to addPort switch - host: %v", err)
			}
			log.Infof("Adding a link: (%s, %s)", n1.(*OVSSwitch).BridgeName, n2.(*Host).Name)
		default:
			log.Fatalf("Type Error")
		}
	case *Host:
		switch n2.(type) {
		case *OVSSwitch:
			err = n2.(*OVSSwitch).addPort(n1.(*Host).vethName)
			if err != nil {
				log.Fatalf("failed to addPort host - switch : %v", err)
			}
			log.Infof("Adding a link: (%s, %s)", n1.(*Host).Name, n2.(*OVSSwitch).BridgeName)
		case *Host:
			log.Fatalf("Type Error: host can not connect to host")
		default:
			log.Fatalf("Type Error")
		}
	default:
		log.Fatalf("Type Error")
	}
	return nil
}

// GetIPs will get IP address from a CIDR notation
// Return an array with IP address. negative num for getting all IP addresses
// (network address and broadcast address are not included)
func GetIPs(cidr string, num int) ([]string, error) {
	ips, err := getAllIPsfromCIDR(cidr)
	if err != nil {
		return nil, err
	}
	if num < 0 {
		return ips, nil
	}
	return ips[0:num], nil
}
<|endoftext|>


<commit_before>package configmanager

// StrOpt is a string option
type StrOpt struct {
	Name     string
	Help     string
	Short    string
	Required bool
	Default  interface{}
}

var _ Opt = StrOpt{}

// NewStrOpt return a new string option.
//
// Notice: the type of the default value must be string or nil.
// If no default, it's nil.
func NewStrOpt(short, name string, _default interface{}, required bool, help string) StrOpt {
	if _default != nil {
		_default = _default.(string)
	}

	return StrOpt{
		Short:    short,
		Name:     name,
		Help:     help,
		Required: required,
		Default:  _default,
	}
}

// GetName returns the name of the option.
func (o StrOpt) GetName() string {
	return o.Name
}

// GetShort returns the shorthand name of the option.
func (o StrOpt) GetShort() string {
	return o.Short
}

// IsRequired returns ture if the option must have the value, or false.
func (o StrOpt) IsRequired() bool {
	return o.Required
}

// GetDefault returns the default value of the option.
func (o StrOpt) GetDefault() interface{} {
	if o.Default != nil {
		return o.Default.(string)
	}
	return nil
}

// GetHelp returns the help doc of the option.
func (o StrOpt) GetHelp() string {
	return o.Help
}

// Parse parses the value of the option to string.
func (o StrOpt) Parse(data string) (interface{}, error) {
	return ToString(data)
}

// IntOpt is a int option
type IntOpt struct {
	Name     string
	Help     string
	Short    string
	Required bool
	Default  interface{}
}

var _ Opt = IntOpt{}

// NewIntOpt return a new int option.
//
// Notice: the type of the default value must be int or nil.
// If no default, it's nil.
func NewIntOpt(short, name string, _default interface{}, required bool, help string) IntOpt {
	if _default != nil {
		_default = _default.(int)
	}

	return IntOpt{
		Short:    short,
		Name:     name,
		Help:     help,
		Required: required,
		Default:  _default,
	}
}

// GetName returns the name of the option.
func (o IntOpt) GetName() string {
	return o.Name
}

// GetShort returns the shorthand name of the option.
func (o IntOpt) GetShort() string {
	return o.Short
}

// IsRequired returns ture if the option must have the value, or false.
func (o IntOpt) IsRequired() bool {
	return o.Required
}

// GetDefault returns the default value of the option.
func (o IntOpt) GetDefault() interface{} {
	if o.Default != nil {
		return o.Default.(int)
	}
	return nil
}

// GetHelp returns the help doc of the option.
func (o IntOpt) GetHelp() string {
	return o.Help
}

// Parse parses the value of the option to int.
func (o IntOpt) Parse(data string) (interface{}, error) {
	v, err := ToInt64(data)
	if err != nil {
		return nil, err
	}
	return int(v), nil
}
<commit_msg>Update the opt type.<commit_after>package configmanager

import "fmt"

type optType int

func (ot optType) String() string {
	return optTypeMap[ot]
}

const (
	noneType optType = iota
	stringType
	intType
	int8Type
	int16Type
	int32Type
	int64Type
	uintType
	uint8Type
	uint16Type
	uint32Type
	uint64Type
	float32Type
	float64Type

	stringsType
	intsType
	int64sType
	uintsType
	uint64sType
)

var optTypeMap = map[optType]string{
	noneType:    "none",
	stringType:  "string",
	intType:     "int",
	int8Type:    "int8",
	int16Type:   "int16",
	int32Type:   "int32",
	int64Type:   "int64",
	uintType:    "uint",
	uint8Type:   "uint8",
	uint16Type:  "uint16",
	uint32Type:  "uint32",
	uint64Type:  "uint64",
	float32Type: "float32",
	float64Type: "float64",

	stringsType: "[]string",
	intsType:    "[]int",
	int64sType:  "[]int64",
	uintsType:   "[]uint",
	uint64sType: "[]uint64",
}

type baseOpt struct {
	Name     string
	Help     string
	Short    string
	Required bool
	Default  interface{}

	_type optType
}

func newBaseOpt(short, name string, _default interface{}, required bool,
	help string, optType optType) baseOpt {
	o := baseOpt{
		Short:    short,
		Name:     name,
		Help:     help,
		Required: required,
		Default:  _default,
		_type:    optType,
	}
	o.GetDefault()
	return o
}

// GetName returns the name of the option.
func (o baseOpt) GetName() string {
	return o.Name
}

// GetShort returns the shorthand name of the option.
func (o baseOpt) GetShort() string {
	return o.Short
}

// IsRequired returns ture if the option must have the value, or false.
func (o baseOpt) IsRequired() bool {
	return o.Required
}

// GetHelp returns the help doc of the option.
func (o baseOpt) GetHelp() string {
	return o.Help
}

// GetDefault returns the default value of the option.
func (o baseOpt) GetDefault() interface{} {
	if o.Default == nil {
		return nil
	}

	switch o._type {
	case stringType:
		return o.Default.(string)
	case intType:
		return o.Default.(int)
	default:
		panic(fmt.Errorf("don't support the type '%s'", o._type))
	}
}

// Parse parses the value of the option to a certain type.
func (o baseOpt) Parse(data string) (interface{}, error) {
	switch o._type {
	case stringType:
		return ToString(data)
	case intType:
		_v, err := ToInt64(data)
		if err != nil {
			return nil, err
		}
		return int(_v), nil
	default:
		panic(fmt.Errorf("don't support the type '%s'", o._type))
	}
}

// strOpt is a string option
type strOpt struct {
	baseOpt
}

var _ Opt = strOpt{}

// NewStrOpt return a new string option.
//
// Notice: the type of the default value must be string or nil.
// If no default, it's nil.
func NewStrOpt(short, name string, _default interface{}, required bool, help string) Opt {
	return strOpt{newBaseOpt(short, name, _default, required, help, stringType)}
}

// intOpt is a int option
type intOpt struct {
	baseOpt
}

var _ Opt = intOpt{}

// NewIntOpt return a new int option.
//
// Notice: the type of the default value must be int or nil.
// If no default, it's nil.
func NewIntOpt(short, name string, _default interface{}, required bool, help string) Opt {
	return intOpt{newBaseOpt(short, name, _default, required, help, stringType)}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"html/template"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os/exec"
	"strconv"
	"strings"
)

type album struct {
	Title, Artist, Folder string
}

var albums []album

var html *template.Template

var mplayer io.Writer

func handler(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/favicon.ico" {
		http.NotFound(w, r)
		return
	}

	if r.URL.RawQuery == "" {
		html.Execute(w, albums)
		return
	}

	if r.FormValue("u") != "" {
		mplayer.Write([]byte("loadlist '" + r.FormValue("u") + "'\n"))
	} else if r.FormValue("f") != "" {
		mplayer.Write([]byte("loadfile '" + r.FormValue("f") + "'\n"))
	} else if r.FormValue("d") != "" {
		folder := r.FormValue("d")
		cmd := exec.Command("find", folder, "-type", "f")
		var out bytes.Buffer
		cmd.Stdout = &out
		cmd.Run()
		playlist, _ := ioutil.TempFile("", "jukebox")
		ioutil.WriteFile(playlist.Name(), []byte(out.String()), 0644)
		mplayer.Write([]byte("loadlist '" + playlist.Name() + "'\n"))
	} else if r.FormValue("c") != "" {
		mplayer.Write([]byte(r.FormValue("c") + "\n"))
	}
	http.Redirect(w, r, "", http.StatusFound)
}

func buildTemplates() {
	const _html = `<!DOCTYPE html>
<title>jukebox</title><meta name="viewport" content="width=device-width, initial-scale=1.0">

[stop]

Radio 4

Radio 4 long wave

Radio 5 live

Radio 5 live sports extra

Groove Salad

<form><input name="f" placeholder="URL"></form>
  {{range .}}
{{.Artist}} - {{.Title}}{{end}}

`
	html = template.Must(template.New("html").Parse(_html))
}

func findAlbums(root string) {
	cmd := exec.Command("find", root, "-mindepth", "2", "-maxdepth", "2", "-type", "d")
	output, err := cmd.Output()
	if err != nil {
		log.Fatal("find: ", err)
	}
	lines := strings.Split(string(output), "\n")

	albums = make([]album, len(lines)-1)
	for i, line := range lines {
		if len(line) > 0 {
			parts := strings.Split(line, "/")
			albums[i] = album{
				parts[len(parts)-1],
				parts[len(parts)-2],
				line,
			}
		}
	}
}

func startMPlayer() {
	cmd := exec.Command("mplayer", "-slave", "-really-quiet", "-cache", "32", "-idle")
	mplayer, _ = cmd.StdinPipe()
	if err := cmd.Start(); err != nil {
		log.Fatal(err)
	}
}

func main() {
	port := flag.Int("port", 80, "port")
	root := flag.String("root", "", "root")
	flag.Parse()
	if len(*root) == 0 {
		panic("root required")
	}

	buildTemplates()
	findAlbums(*root)
	startMPlayer()

	http.HandleFunc("/", handler)
	if err := http.ListenAndServe(":"+strconv.Itoa(*port), nil); err != nil {
		log.Fatal("http: ", err)
	}
}
<commit_msg>Remove erroneous file<commit_after><|endoftext|>


<commit_before>package command

import (
	"fmt"
	"sort"
	"strings"

	"github.com/posener/complete"
)

type AgentInfoCommand struct {
	Meta
}

func (c *AgentInfoCommand) Help() string {
	helpText := `
Usage: nomad agent-info [options]

  Display status information about the local agent.

  When ACLs are enabled, this command requires a token with the 'agent:read'
  capability.

General Options:

  ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `

Agent Info Options:

  -json
    Output the node in its JSON format.

-t
    Format and display node using a Go template.
`
	return strings.TrimSpace(helpText)
}

func (c *AgentInfoCommand) Synopsis() string {
	return "Display status information about the local agent"
}

func (c *AgentInfoCommand) AutocompleteFlags() complete.Flags {
	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
		complete.Flags{
			"-json": complete.PredictNothing,
			"-t":    complete.PredictAnything,
		})
}

func (c *AgentInfoCommand) AutocompleteArgs() complete.Predictor {
	return complete.PredictNothing
}

func (c *AgentInfoCommand) Name() string { return "agent-info" }

func (c *AgentInfoCommand) Run(args []string) int {
	var json bool
	var tmpl string

	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&json, "json", false, "")
	flags.StringVar(&tmpl, "t", "", "")

	if err := flags.Parse(args); err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing flags: %s", err))
		return 1
	}

	// Check that we got no arguments
	args = flags.Args()
	if len(args) > 0 {
		c.Ui.Error("This command takes no arguments")
		c.Ui.Error(commandErrorText(c))
		return 1
	}

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	// Query the agent info
	info, err := client.Agent().Self()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying agent info: %s", err))
		return 1
	}

	// If output format is specified, format and output the agent info
	if json || len(tmpl) > 0 {
		out, err := Format(json, tmpl, info)
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error formatting output: %s", err))
			return 1
		}

		c.Ui.Output(out)
		return 0
	}

	// Sort and output agent info
	statsKeys := make([]string, 0, len(info.Stats))
	for key := range info.Stats {
		statsKeys = append(statsKeys, key)
	}
	sort.Strings(statsKeys)

	for _, key := range statsKeys {
		c.Ui.Output(key)
		statsData := info.Stats[key]
		statsDataKeys := make([]string, len(statsData))
		i := 0
		for key := range statsData {
			statsDataKeys[i] = key
			i++
		}
		sort.Strings(statsDataKeys)

		for _, key := range statsDataKeys {
			c.Ui.Output(fmt.Sprintf("  %s = %v", key, statsData[key]))
		}
	}

	return 0
}
<commit_msg>fix agent-info help message formatting (#10747)<commit_after>package command

import (
	"fmt"
	"sort"
	"strings"

	"github.com/posener/complete"
)

type AgentInfoCommand struct {
	Meta
}

func (c *AgentInfoCommand) Help() string {
	helpText := `
Usage: nomad agent-info [options]

  Display status information about the local agent.

  When ACLs are enabled, this command requires a token with the 'agent:read'
  capability.

General Options:

  ` + generalOptionsUsage(usageOptsDefault|usageOptsNoNamespace) + `

Agent Info Options:

  -json
    Output the node in its JSON format.

  -t
    Format and display node using a Go template.
`
	return strings.TrimSpace(helpText)
}

func (c *AgentInfoCommand) Synopsis() string {
	return "Display status information about the local agent"
}

func (c *AgentInfoCommand) AutocompleteFlags() complete.Flags {
	return mergeAutocompleteFlags(c.Meta.AutocompleteFlags(FlagSetClient),
		complete.Flags{
			"-json": complete.PredictNothing,
			"-t":    complete.PredictAnything,
		})
}

func (c *AgentInfoCommand) AutocompleteArgs() complete.Predictor {
	return complete.PredictNothing
}

func (c *AgentInfoCommand) Name() string { return "agent-info" }

func (c *AgentInfoCommand) Run(args []string) int {
	var json bool
	var tmpl string

	flags := c.Meta.FlagSet(c.Name(), FlagSetClient)
	flags.Usage = func() { c.Ui.Output(c.Help()) }
	flags.BoolVar(&json, "json", false, "")
	flags.StringVar(&tmpl, "t", "", "")

	if err := flags.Parse(args); err != nil {
		c.Ui.Error(fmt.Sprintf("Error parsing flags: %s", err))
		return 1
	}

	// Check that we got no arguments
	args = flags.Args()
	if len(args) > 0 {
		c.Ui.Error("This command takes no arguments")
		c.Ui.Error(commandErrorText(c))
		return 1
	}

	// Get the HTTP client
	client, err := c.Meta.Client()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
		return 1
	}

	// Query the agent info
	info, err := client.Agent().Self()
	if err != nil {
		c.Ui.Error(fmt.Sprintf("Error querying agent info: %s", err))
		return 1
	}

	// If output format is specified, format and output the agent info
	if json || len(tmpl) > 0 {
		out, err := Format(json, tmpl, info)
		if err != nil {
			c.Ui.Error(fmt.Sprintf("Error formatting output: %s", err))
			return 1
		}

		c.Ui.Output(out)
		return 0
	}

	// Sort and output agent info
	statsKeys := make([]string, 0, len(info.Stats))
	for key := range info.Stats {
		statsKeys = append(statsKeys, key)
	}
	sort.Strings(statsKeys)

	for _, key := range statsKeys {
		c.Ui.Output(key)
		statsData := info.Stats[key]
		statsDataKeys := make([]string, len(statsData))
		i := 0
		for key := range statsData {
			statsDataKeys[i] = key
			i++
		}
		sort.Strings(statsDataKeys)

		for _, key := range statsDataKeys {
			c.Ui.Output(fmt.Sprintf("  %s = %v", key, statsData[key]))
		}
	}

	return 0
}
<|endoftext|>