<commit_before>package gwlog

import (
	"runtime/debug"

	"strings"

	"encoding/json"

	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var (
	// DebugLevel level
	DebugLevel Level = Level(zap.DebugLevel)
	// InfoLevel level
	InfoLevel Level = Level(zap.InfoLevel)
	// WarnLevel level
	WarnLevel Level = Level(zap.WarnLevel)
	// ErrorLevel level
	ErrorLevel Level = Level(zap.ErrorLevel)
	// PanicLevel level
	PanicLevel Level = Level(zap.PanicLevel)
	// FatalLevel level
	FatalLevel Level = Level(zap.FatalLevel)

	//// Debugf logs formatted debug message
	//Debugf logFormatFunc
	//// Infof logs formatted info message
	//Infof logFormatFunc
	//// Warnf logs formatted warn message
	//Warnf logFormatFunc
	//// Errorf logs formatted error message
	//Errorf logFormatFunc
	//Panicf logFormatFunc
	//Fatalf logFormatFunc
	//Error  func(args ...interface{})
	//Fatal  func(args ...interface{})
	//Panic  func(args ...interface{})
)

type logFormatFunc func(format string, args ...interface{})

// Level is type of log levels
type Level = zapcore.Level

var (
	cfg          zap.Config
	logger       *zap.Logger
	sugar        *zap.SugaredLogger
	source       string
	currentLevel Level
)

func init() {
	var err error
	cfgJson := []byte(`{
		"level": "debug",
		"outputPaths": ["stderr"],
		"errorOutputPaths": ["stderr"],
		"encoding": "console",
		"encoderConfig": {
			"messageKey": "message",
			"levelKey": "level",
			"levelEncoder": "lowercase"
		}
	}`)
	currentLevel = DebugLevel

	if err = json.Unmarshal(cfgJson, &cfg); err != nil {
		panic(err)
	}
	cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	rebuildLoggerFromCfg()
}

// SetSource sets the component name (dispatcher/gate/game) of gwlog module
func SetSource(source_ string) {
	source = source_
	rebuildLoggerFromCfg()
}

// SetLevel sets the log level
func SetLevel(lv Level) {
	currentLevel = lv
	cfg.Level.SetLevel(lv)
}

// GetLevel get the current log level
func GetLevel() Level {
	return currentLevel
}

// TraceError prints the stack and error
func TraceError(format string, args ...interface{}) {
	Error(string(debug.Stack()))
	Errorf(format, args...)
}

// SetOutput sets the output writer
func SetOutput(outputs []string) {
	cfg.OutputPaths = outputs
	rebuildLoggerFromCfg()
}

// ParseLevel converts string to Levels
func ParseLevel(s string) Level {
	if strings.ToLower(s) == "debug" {
		return DebugLevel
	} else if strings.ToLower(s) == "info" {
		return InfoLevel
	} else if strings.ToLower(s) == "warn" || strings.ToLower(s) == "warning" {
		return WarnLevel
	} else if strings.ToLower(s) == "error" {
		return ErrorLevel
	} else if strings.ToLower(s) == "panic" {
		return PanicLevel
	} else if strings.ToLower(s) == "fatal" {
		return FatalLevel
	}
	Errorf("ParseLevel: unknown level: %s", s)
	return DebugLevel
}

func rebuildLoggerFromCfg() {
	if newLogger, err := cfg.Build(); err == nil {
		if logger != nil {
			logger.Sync()
		}
		logger = newLogger
		//logger = logger.With(zap.Time("ts", time.Now()))
		if source != "" {
			logger = logger.With(zap.String("source", source))
		}
		setSugar(logger.Sugar())
	} else {
		panic(err)
	}
}

func Debugf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Debugf(format, args...)
}

func Infof(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Infof(format, args...)
}

func Warnf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Warnf(format, args...)
}

func Errorf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Errorf(format, args...)
}

func Panicf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Panicf(format, args...)
}

func Fatalf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Fatalf(format, args...)
}

func Error(args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Error(args...)
}

func Panic(args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Panic(args...)
}

func Fatal(args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Fatal(args...)
}

func setSugar(sugar_ *zap.SugaredLogger) {
	sugar = sugar_
}
<commit_msg>clear code<commit_after>package gwlog

import (
	"runtime/debug"

	"strings"

	"encoding/json"

	"time"

	"go.uber.org/zap"
	"go.uber.org/zap/zapcore"
)

var (
	// DebugLevel level
	DebugLevel Level = Level(zap.DebugLevel)
	// InfoLevel level
	InfoLevel Level = Level(zap.InfoLevel)
	// WarnLevel level
	WarnLevel Level = Level(zap.WarnLevel)
	// ErrorLevel level
	ErrorLevel Level = Level(zap.ErrorLevel)
	// PanicLevel level
	PanicLevel Level = Level(zap.PanicLevel)
	// FatalLevel level
	FatalLevel Level = Level(zap.FatalLevel)
)

type logFormatFunc func(format string, args ...interface{})

// Level is type of log levels
type Level = zapcore.Level

var (
	cfg          zap.Config
	logger       *zap.Logger
	sugar        *zap.SugaredLogger
	source       string
	currentLevel Level
)

func init() {
	var err error
	cfgJson := []byte(`{
		"level": "debug",
		"outputPaths": ["stderr"],
		"errorOutputPaths": ["stderr"],
		"encoding": "console",
		"encoderConfig": {
			"messageKey": "message",
			"levelKey": "level",
			"levelEncoder": "lowercase"
		}
	}`)
	currentLevel = DebugLevel

	if err = json.Unmarshal(cfgJson, &cfg); err != nil {
		panic(err)
	}
	cfg.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
	rebuildLoggerFromCfg()
}

// SetSource sets the component name (dispatcher/gate/game) of gwlog module
func SetSource(source_ string) {
	source = source_
	rebuildLoggerFromCfg()
}

// SetLevel sets the log level
func SetLevel(lv Level) {
	currentLevel = lv
	cfg.Level.SetLevel(lv)
}

// GetLevel get the current log level
func GetLevel() Level {
	return currentLevel
}

// TraceError prints the stack and error
func TraceError(format string, args ...interface{}) {
	Error(string(debug.Stack()))
	Errorf(format, args...)
}

// SetOutput sets the output writer
func SetOutput(outputs []string) {
	cfg.OutputPaths = outputs
	rebuildLoggerFromCfg()
}

// ParseLevel converts string to Levels
func ParseLevel(s string) Level {
	if strings.ToLower(s) == "debug" {
		return DebugLevel
	} else if strings.ToLower(s) == "info" {
		return InfoLevel
	} else if strings.ToLower(s) == "warn" || strings.ToLower(s) == "warning" {
		return WarnLevel
	} else if strings.ToLower(s) == "error" {
		return ErrorLevel
	} else if strings.ToLower(s) == "panic" {
		return PanicLevel
	} else if strings.ToLower(s) == "fatal" {
		return FatalLevel
	}
	Errorf("ParseLevel: unknown level: %s", s)
	return DebugLevel
}

func rebuildLoggerFromCfg() {
	if newLogger, err := cfg.Build(); err == nil {
		if logger != nil {
			logger.Sync()
		}
		logger = newLogger
		//logger = logger.With(zap.Time("ts", time.Now()))
		if source != "" {
			logger = logger.With(zap.String("source", source))
		}
		setSugar(logger.Sugar())
	} else {
		panic(err)
	}
}

func Debugf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Debugf(format, args...)
}

func Infof(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Infof(format, args...)
}

func Warnf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Warnf(format, args...)
}

func Errorf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Errorf(format, args...)
}

func Panicf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Panicf(format, args...)
}

func Fatalf(format string, args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Fatalf(format, args...)
}

func Error(args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Error(args...)
}

func Panic(args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Panic(args...)
}

func Fatal(args ...interface{}) {
	sugar.With(zap.Time("ts", time.Now())).Fatal(args...)
}

func setSugar(sugar_ *zap.SugaredLogger) {
	sugar = sugar_
}
<|endoftext|>


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

package tpl

import (
	"fmt"

	"github.com/nicksnyder/go-i18n/i18n/bundle"
	"github.com/spf13/hugo/helpers"
	jww "github.com/spf13/jwalterweatherman"
	"github.com/spf13/viper"
)

var (
	Logi18nWarnings   bool
	i18nWarningLogger = helpers.NewDistinctFeedbackLogger()
)

type translate struct {
	translateFuncs map[string]bundle.TranslateFunc

	current bundle.TranslateFunc
}

var translator *translate

// SetTranslateLang sets the translations language to use during template processing.
// This construction is unfortunate, but the template system is currently global.
func SetTranslateLang(lang string) error {
	if f, ok := translator.translateFuncs[lang]; ok {
		translator.current = f
	} else {
		jww.WARN.Printf("Translation func for language %v not found, use default.", lang)
		translator.current = translator.translateFuncs[viper.GetString("DefaultContentLanguage")]
	}
	return nil
}

func SetI18nTfuncs(bndl *bundle.Bundle) {
	translator = &translate{translateFuncs: make(map[string]bundle.TranslateFunc)}
	defaultContentLanguage := viper.GetString("DefaultContentLanguage")
	var (
		defaultT bundle.TranslateFunc
		err      error
	)

	defaultT, err = bndl.Tfunc(defaultContentLanguage)

	if err != nil {
		jww.WARN.Printf("No translation bundle found for default language %q", defaultContentLanguage)
	}

	for _, lang := range bndl.LanguageTags() {
		currentLang := lang
		tFunc, err := bndl.Tfunc(currentLang)

		if err != nil {
			jww.WARN.Printf("could not load translations for language %q (%s), will use default content language.\n", lang, err)
			translator.translateFuncs[currentLang] = defaultT
			continue
		}
		translator.translateFuncs[currentLang] = func(translationID string, args ...interface{}) string {
			if translated := tFunc(translationID, args...); translated != translationID {
				return translated
			}
			if Logi18nWarnings {
				i18nWarningLogger.Printf("i18n|MISSING_TRANSLATION|%s|%s", currentLang, translationID)
			}
			if defaultT != nil {
				return defaultT(translationID, args...)
			}
			return fmt.Sprintf("[i18n] %s", translationID)
		}
	}
}

func I18nTranslate(id string, args ...interface{}) (string, error) {
	if translator == nil || translator.current == nil {
		helpers.DistinctErrorLog.Printf("i18n not initialized, check that you have language file (in i18n) that matches the site language or the default language.")
		return "", nil
	}
	return translator.current(id, args...), nil
}
<commit_msg>tpl: Make it more clear on  missing language string<commit_after>// Copyright 2015 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package tpl

import (
	"fmt"

	"github.com/nicksnyder/go-i18n/i18n/bundle"
	"github.com/spf13/hugo/helpers"
	jww "github.com/spf13/jwalterweatherman"
	"github.com/spf13/viper"
)

var (
	Logi18nWarnings   bool
	i18nWarningLogger = helpers.NewDistinctFeedbackLogger()
)

type translate struct {
	translateFuncs map[string]bundle.TranslateFunc

	current bundle.TranslateFunc
}

var translator *translate

// SetTranslateLang sets the translations language to use during template processing.
// This construction is unfortunate, but the template system is currently global.
func SetTranslateLang(lang string) error {
	if f, ok := translator.translateFuncs[lang]; ok {
		translator.current = f
	} else {
		jww.WARN.Printf("Translation func for language %v not found, use default.", lang)
		translator.current = translator.translateFuncs[viper.GetString("DefaultContentLanguage")]
	}
	return nil
}

func SetI18nTfuncs(bndl *bundle.Bundle) {
	translator = &translate{translateFuncs: make(map[string]bundle.TranslateFunc)}
	defaultContentLanguage := viper.GetString("DefaultContentLanguage")
	var (
		defaultT bundle.TranslateFunc
		err      error
	)

	defaultT, err = bndl.Tfunc(defaultContentLanguage)

	if err != nil {
		jww.WARN.Printf("No translation bundle found for default language %q", defaultContentLanguage)
	}

	for _, lang := range bndl.LanguageTags() {
		currentLang := lang
		tFunc, err := bndl.Tfunc(currentLang)

		if err != nil {
			jww.WARN.Printf("could not load translations for language %q (%s), will use default content language.\n", lang, err)
			translator.translateFuncs[currentLang] = defaultT
			continue
		}
		translator.translateFuncs[currentLang] = func(translationID string, args ...interface{}) string {
			if translated := tFunc(translationID, args...); translated != translationID {
				return translated
			}
			if Logi18nWarnings {
				i18nWarningLogger.Printf("i18n|MISSING_TRANSLATION|%s|%s", currentLang, translationID)
			}
			if defaultT != nil {
				if translated := defaultT(translationID, args...); translated != translationID {
					return translated
				}
			}
			return fmt.Sprintf("[i18n] %s", translationID)
		}
	}
}

func I18nTranslate(id string, args ...interface{}) (string, error) {
	if translator == nil || translator.current == nil {
		helpers.DistinctErrorLog.Printf("i18n not initialized, check that you have language file (in i18n) that matches the site language or the default language.")
		return "", nil
	}
	return translator.current(id, args...), nil
}
<|endoftext|>


<commit_before>package image

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/cenkalti/backoff"
)

const (
	imageAPIRequestContentType = "application/x-www-form-urlencoded; boundary=NL"
)

type APISelector struct {
	baseURL *url.URL

	maxInterval    time.Duration
	maxElapsedTime time.Duration
}

func NewAPISelector(u *url.URL) *APISelector {
	return &APISelector{
		baseURL: u,

		maxInterval:    10 * time.Second,
		maxElapsedTime: time.Minute,
	}
}

func (as *APISelector) Select(params *Params) (string, error) {
	imageName, err := as.queryWithTags(params.Infra, as.buildCandidateTags(params))
	if err != nil {
		return "default", err
	}

	if imageName != "" {
		return imageName, nil
	}

	return "default", nil
}

func (as *APISelector) queryWithTags(infra string, tags []*tagSet) (string, error) {
	bodyLines := []string{}
	lastJobID := uint64(0)
	lastRepo := ""

	for _, ts := range tags {
		qs := url.Values{}
		qs.Set("infra", infra)
		qs.Set("fields[images]", "name")
		qs.Set("limit", "1")
		qs.Set("job_id", fmt.Sprintf("%v", ts.JobID))
		qs.Set("repo", ts.Repo)
		qs.Set("is_default", fmt.Sprintf("%v", ts.IsDefault))
		if len(ts.Tags) > 0 {
			qs.Set("tags", strings.Join(ts.Tags, ","))
		}

		bodyLines = append(bodyLines, qs.Encode())
		lastJobID = ts.JobID
		lastRepo = ts.Repo
	}

	qs := url.Values{}
	qs.Set("infra", infra)
	qs.Set("is_default", "true")
	qs.Set("fields[images]", "name")
	qs.Set("limit", "1")
	qs.Set("job_id", fmt.Sprintf("%v", lastJobID))
	qs.Set("repo", lastRepo)

	bodyLines = append(bodyLines, qs.Encode())

	u, err := url.Parse(as.baseURL.String())
	if err != nil {
		return "", err
	}

	imageResp, err := as.makeImageRequest(u.String(), bodyLines)
	if err != nil {
		return "", err
	}

	if len(imageResp.Data) == 0 {
		return "", nil
	}

	return imageResp.Data[0].Name, nil
}

func (as *APISelector) makeImageRequest(urlString string, bodyLines []string) (*apiSelectorImageResponse, error) {
	var responseBody []byte

	b := backoff.NewExponentialBackOff()
	b.MaxInterval = 10 * time.Second
	b.MaxElapsedTime = time.Minute

	err := backoff.Retry(func() (err error) {
		resp, err := http.Post(urlString, imageAPIRequestContentType,
			strings.NewReader(strings.Join(bodyLines, "\n")+"\n"))

		if err != nil {
			return err
		}
		defer resp.Body.Close()
		responseBody, err = ioutil.ReadAll(resp.Body)
		return
	}, b)

	if err != nil {
		return nil, err
	}

	imageResp := &apiSelectorImageResponse{
		Data: []*apiSelectorImageRef{},
	}

	err = json.Unmarshal(responseBody, imageResp)
	if err != nil {
		return nil, err
	}

	return imageResp, nil
}

type tagSet struct {
	Tags      []string
	IsDefault bool

	JobID uint64
	Repo  string
}

func (ts *tagSet) GoString() string {
	return fmt.Sprintf("&image.tagSet{IsDefault: %v, Tags: %#v}", ts.IsDefault, ts.Tags)
}

func (as *APISelector) buildCandidateTags(params *Params) []*tagSet {
	fullTagSet := &tagSet{
		Tags:  []string{},
		JobID: params.JobID,
		Repo:  params.Repo,
	}
	candidateTags := []*tagSet{}

	addDefaultTag := func(tag string) {
		fullTagSet.Tags = append(fullTagSet.Tags, tag)
		candidateTags = append(candidateTags,
			&tagSet{
				IsDefault: true,
				Tags:      []string{tag},
				JobID:     params.JobID,
				Repo:      params.Repo,
			})
	}

	addTags := func(tags ...string) {
		candidateTags = append(candidateTags,
			&tagSet{
				IsDefault: false,
				Tags:      tags,
				JobID:     params.JobID,
				Repo:      params.Repo,
			})
	}

	hasLang := params.Language != ""

	if params.OS == "osx" && params.OsxImage != "" {
		addTags("osx_image:"+params.OsxImage, "os:osx")
	}

	if params.Dist != "" && params.Group != "" && hasLang {
		addTags("dist:"+params.Dist, "group:"+params.Group, "language_"+params.Language+":true")
	}

	if params.Dist != "" && hasLang {
		addTags("dist:"+params.Dist, "language_"+params.Language+":true")
	}

	if params.Group != "" && hasLang {
		addTags("group:"+params.Group, "language_"+params.Language+":true")
	}

	if params.OS != "" && hasLang {
		addTags("os:"+params.OS, "language_"+params.Language+":true")
	}

	if hasLang {
		addDefaultTag("language_" + params.Language + ":true")
	}

	if params.OS == "osx" && params.OsxImage != "" {
		addDefaultTag("osx_image:" + params.OsxImage)
	}

	if params.Dist != "" {
		addDefaultTag("dist:" + params.Dist)
	}

	if params.Group != "" {
		addDefaultTag("group:" + params.Group)
	}

	if params.OS != "" {
		addDefaultTag("os:" + params.OS)
	}

	result := append([]*tagSet{fullTagSet}, candidateTags...)
	for _, ts := range result {
		sort.Strings(ts.Tags)
	}

	return result
}

type apiSelectorImageResponse struct {
	Data []*apiSelectorImageRef `json:"data"`
}

type apiSelectorImageRef struct {
	ID        int               `json:"id"`
	Infra     string            `json:"infra"`
	Name      string            `json:"name"`
	Tags      map[string]string `json:"tags"`
	IsDefault bool              `json:"is_default"`
	CreatedAt string            `json:"created_at"`
	UpdatedAt string            `json:"updated_at"`
}
<commit_msg>check job-board response status code on image selection<commit_after>package image

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"sort"
	"strings"
	"time"

	"github.com/cenkalti/backoff"
	"github.com/pkg/errors"
)

const (
	imageAPIRequestContentType = "application/x-www-form-urlencoded; boundary=NL"
)

type APISelector struct {
	baseURL *url.URL

	maxInterval    time.Duration
	maxElapsedTime time.Duration
}

func NewAPISelector(u *url.URL) *APISelector {
	return &APISelector{
		baseURL: u,

		maxInterval:    10 * time.Second,
		maxElapsedTime: time.Minute,
	}
}

func (as *APISelector) Select(params *Params) (string, error) {
	imageName, err := as.queryWithTags(params.Infra, as.buildCandidateTags(params))
	if err != nil {
		return "default", err
	}

	if imageName != "" {
		return imageName, nil
	}

	return "default", nil
}

func (as *APISelector) queryWithTags(infra string, tags []*tagSet) (string, error) {
	bodyLines := []string{}
	lastJobID := uint64(0)
	lastRepo := ""

	for _, ts := range tags {
		qs := url.Values{}
		qs.Set("infra", infra)
		qs.Set("fields[images]", "name")
		qs.Set("limit", "1")
		qs.Set("job_id", fmt.Sprintf("%v", ts.JobID))
		qs.Set("repo", ts.Repo)
		qs.Set("is_default", fmt.Sprintf("%v", ts.IsDefault))
		if len(ts.Tags) > 0 {
			qs.Set("tags", strings.Join(ts.Tags, ","))
		}

		bodyLines = append(bodyLines, qs.Encode())
		lastJobID = ts.JobID
		lastRepo = ts.Repo
	}

	qs := url.Values{}
	qs.Set("infra", infra)
	qs.Set("is_default", "true")
	qs.Set("fields[images]", "name")
	qs.Set("limit", "1")
	qs.Set("job_id", fmt.Sprintf("%v", lastJobID))
	qs.Set("repo", lastRepo)

	bodyLines = append(bodyLines, qs.Encode())

	u, err := url.Parse(as.baseURL.String())
	if err != nil {
		return "", err
	}

	imageResp, err := as.makeImageRequest(u.String(), bodyLines)
	if err != nil {
		return "", err
	}

	if len(imageResp.Data) == 0 {
		return "", nil
	}

	return imageResp.Data[0].Name, nil
}

func (as *APISelector) makeImageRequest(urlString string, bodyLines []string) (*apiSelectorImageResponse, error) {
	var responseBody []byte

	b := backoff.NewExponentialBackOff()
	b.MaxInterval = 10 * time.Second
	b.MaxElapsedTime = time.Minute

	err := backoff.Retry(func() error {
		resp, err := http.Post(urlString, imageAPIRequestContentType,
			strings.NewReader(strings.Join(bodyLines, "\n")+"\n"))

		if err != nil {
			return err
		}
		defer resp.Body.Close()

		responseBody, err = ioutil.ReadAll(resp.Body)
		if err != nil {
			return err
		}

		if resp.StatusCode != 200 {
			return errors.Errorf("expected 200 status code, received status=%d body=%q",
				resp.StatusCode,
				responseBody)
		}

		return nil
	}, b)

	if err != nil {
		return nil, err
	}

	imageResp := &apiSelectorImageResponse{
		Data: []*apiSelectorImageRef{},
	}

	err = json.Unmarshal(responseBody, imageResp)
	if err != nil {
		return nil, err
	}

	return imageResp, nil
}

type tagSet struct {
	Tags      []string
	IsDefault bool

	JobID uint64
	Repo  string
}

func (ts *tagSet) GoString() string {
	return fmt.Sprintf("&image.tagSet{IsDefault: %v, Tags: %#v}", ts.IsDefault, ts.Tags)
}

func (as *APISelector) buildCandidateTags(params *Params) []*tagSet {
	fullTagSet := &tagSet{
		Tags:  []string{},
		JobID: params.JobID,
		Repo:  params.Repo,
	}
	candidateTags := []*tagSet{}

	addDefaultTag := func(tag string) {
		fullTagSet.Tags = append(fullTagSet.Tags, tag)
		candidateTags = append(candidateTags,
			&tagSet{
				IsDefault: true,
				Tags:      []string{tag},
				JobID:     params.JobID,
				Repo:      params.Repo,
			})
	}

	addTags := func(tags ...string) {
		candidateTags = append(candidateTags,
			&tagSet{
				IsDefault: false,
				Tags:      tags,
				JobID:     params.JobID,
				Repo:      params.Repo,
			})
	}

	hasLang := params.Language != ""

	if params.OS == "osx" && params.OsxImage != "" {
		addTags("osx_image:"+params.OsxImage, "os:osx")
	}

	if params.Dist != "" && params.Group != "" && hasLang {
		addTags("dist:"+params.Dist, "group:"+params.Group, "language_"+params.Language+":true")
	}

	if params.Dist != "" && hasLang {
		addTags("dist:"+params.Dist, "language_"+params.Language+":true")
	}

	if params.Group != "" && hasLang {
		addTags("group:"+params.Group, "language_"+params.Language+":true")
	}

	if params.OS != "" && hasLang {
		addTags("os:"+params.OS, "language_"+params.Language+":true")
	}

	if hasLang {
		addDefaultTag("language_" + params.Language + ":true")
	}

	if params.OS == "osx" && params.OsxImage != "" {
		addDefaultTag("osx_image:" + params.OsxImage)
	}

	if params.Dist != "" {
		addDefaultTag("dist:" + params.Dist)
	}

	if params.Group != "" {
		addDefaultTag("group:" + params.Group)
	}

	if params.OS != "" {
		addDefaultTag("os:" + params.OS)
	}

	result := append([]*tagSet{fullTagSet}, candidateTags...)
	for _, ts := range result {
		sort.Strings(ts.Tags)
	}

	return result
}

type apiSelectorImageResponse struct {
	Data []*apiSelectorImageRef `json:"data"`
}

type apiSelectorImageRef struct {
	ID        int               `json:"id"`
	Infra     string            `json:"infra"`
	Name      string            `json:"name"`
	Tags      map[string]string `json:"tags"`
	IsDefault bool              `json:"is_default"`
	CreatedAt string            `json:"created_at"`
	UpdatedAt string            `json:"updated_at"`
}
<|endoftext|>


<commit_before>package importer

import (
	"io"

	u "github.com/jbenet/go-ipfs/util"
)

type BlockSplitter interface {
	Split(io.Reader) chan []byte
}

type SizeSplitter struct {
	Size int
}

func (ss *SizeSplitter) Split(r io.Reader) chan []byte {
	out := make(chan []byte)
	go func() {
		defer close(out)
		for {
			chunk := make([]byte, ss.Size)
			nread, err := r.Read(chunk)
			if err != nil {
				if err == io.EOF {
					return
				}
				u.PErr("block split error: %v\n", err)
				return
			}
			if nread < ss.Size {
				chunk = chunk[:nread]
			}
			out <- chunk
		}
	}()
	return out
}
<commit_msg>return the read bytes when EOF is reached<commit_after>package importer

import (
	"io"

	u "github.com/jbenet/go-ipfs/util"
)

type BlockSplitter interface {
	Split(io.Reader) chan []byte
}

type SizeSplitter struct {
	Size int
}

func (ss *SizeSplitter) Split(r io.Reader) chan []byte {
	out := make(chan []byte)
	go func() {
		defer close(out)
		for {
			chunk := make([]byte, ss.Size)
			nread, err := r.Read(chunk)
			if err != nil {
				if err == io.EOF {
					if nread > 0 {
						out <- chunk[:nread]
					}
					return
				}
				u.PErr("block split error: %v\n", err)
				return
			}
			if nread < ss.Size {
				chunk = chunk[:nread]
			}
			out <- chunk
		}
	}()
	return out
}
<|endoftext|>


<commit_before>// +build !windows

package main

import "syscall"

func init() {
	// *nix systems support SIGTERM so handle shutdown on that too
	RegisterShutdownSignal(syscall.SIGTERM)
}
<commit_msg>License boilerplate<commit_after>// +build !windows

/*
* Copyright 2014 Jason Woods.
*
* 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 main

import "syscall"

func init() {
	// *nix systems support SIGTERM so handle shutdown on that too
	RegisterShutdownSignal(syscall.SIGTERM)
}
<|endoftext|>


<commit_before>// Package transfer collects together adapters for uploading and downloading LFS content
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
package transfer

import (
	"sync"

	"github.com/github/git-lfs/progress"
)

type Direction int

const (
	Upload   = Direction(iota)
	Download = Direction(iota)
)

var (
	adapterMutex     sync.Mutex
	downloadAdapters = make(map[string]TransferAdapter)
	uploadAdapters   = make(map[string]TransferAdapter)
)

// TransferAdapter is implemented by types which can upload and/or download LFS
// file content to a remote store. Each TransferAdapter accepts one or more requests
// which it may schedule and parallelise in whatever way it chooses, clients of
// this interface will receive notifications of progress and completion asynchronously.
// TransferAdapters support transfers in one direction; if an implementation
// provides support for upload and download, it should be instantiated twice,
// advertising support for each direction separately.
type TransferAdapter interface {
	// Name returns the identifier of this adapter, must be unique within a Direction
	// (separate sets for upload and download so may be an entry in both)
	Name() string
	// Direction returns whether this instance is an upload or download instance
	// TransferAdapter instances can only be one or the other, although the same
	// type may be instantiated once for each direction
	Direction() Direction
	// Begin a new batch of uploads or downloads. Call this first, followed by
	// one or more Add calls. The passed in callback will receive updates on
	// progress, and the completion channel will receive completion notifications
	// Either argument may be nil if not required by the client
	Begin(cb progress.CopyCallback, completion chan TransferResult) error
	// Add queues a download/upload, which will complete asynchronously and
	// notify the callbacks given to Begin()
	Add(t Transfer)
	// Indicate that all transfers have been scheduled and resources can be released
	// once the queued items have completed.
	// This call blocks until all items have been processed
	End() error
	// ClearTempStorage clears any temporary files, such as unfinished downloads that
	// would otherwise be resumed
	ClearTempStorage() error
}

// General struct for both uploads and downloads
type Transfer struct {
	// Name of the file that triggered this transfer
	Name string
	// oid identifier
	Oid string
	// size of file to be transferred
	Size int64
	// link which api provided as source/dest for this transfer
	Link string
	// Path for uploads is the source of data to send, for downloads is the
	// location to place the final result
	Path string
}

// Result of a transfer returned through CompletionChannel()
type TransferResult struct {
	*Transfer
	// This will be non-nil if there was an error transferring this item
	Error error
}

// GetAdapters returns a list of registered adapters for the given direction
func GetAdapters(dir Direction) []TransferAdapter {
	switch dir {
	case Upload:
		return GetUploadAdapters()
	case Download:
		return GetDownloadAdapters()
	}
	return nil
}

// GetDownloadAdapters returns a list of registered adapters able to perform downloads
func GetDownloadAdapters() []TransferAdapter {
	adapterMutex.Lock()
	defer adapterMutex.Unlock()

	ret := make([]TransferAdapter, 0, len(downloadAdapters))
	for _, a := range downloadAdapters {
		ret = append(ret, a)
	}
	return ret
}

// GetUploadAdapters returns a list of registered adapters able to perform uploads
func GetUploadAdapters() []TransferAdapter {
	adapterMutex.Lock()
	defer adapterMutex.Unlock()

	ret := make([]TransferAdapter, 0, len(uploadAdapters))
	for _, a := range uploadAdapters {
		ret = append(ret, a)
	}
	return ret
}

// RegisterAdapter registers an upload or download adapter. If an adapter is
// already registered for that direction with the same name, it is overridden
func RegisterAdapter(adapter TransferAdapter) {
	adapterMutex.Lock()
	defer adapterMutex.Unlock()

	switch adapter.Direction() {
	case Upload:
		uploadAdapters[adapter.Name()] = adapter
	case Download:
		downloadAdapters[adapter.Name()] = adapter
	}
}

// Get a specific adapter by name and direction
func GetAdapter(name string, dir Direction) (TransferAdapter, bool) {
	adapterMutex.Lock()
	defer adapterMutex.Unlock()

	switch dir {
	case Upload:
		if u, ok := uploadAdapters[name]; ok {
			return u, true
		}
	case Download:
		if d, ok := downloadAdapters[name]; ok {
			return d, true
		}
	}
	return nil, false
}
<commit_msg>Refine transfer interface<commit_after>// Package transfer collects together adapters for uploading and downloading LFS content
// NOTE: Subject to change, do not rely on this package from outside git-lfs source
package transfer

import (
	"sync"

	"github.com/github/git-lfs/api"

	"github.com/github/git-lfs/progress"
)

type Direction int

const (
	Upload   = Direction(iota)
	Download = Direction(iota)
)

var (
	adapterMutex     sync.Mutex
	downloadAdapters = make(map[string]TransferAdapter)
	uploadAdapters   = make(map[string]TransferAdapter)
)

// TransferAdapter is implemented by types which can upload and/or download LFS
// file content to a remote store. Each TransferAdapter accepts one or more requests
// which it may schedule and parallelise in whatever way it chooses, clients of
// this interface will receive notifications of progress and completion asynchronously.
// TransferAdapters support transfers in one direction; if an implementation
// provides support for upload and download, it should be instantiated twice,
// advertising support for each direction separately.
// Note that TransferAdapter only implements the actual upload/download of content
// itself; organising the wider process including calling the API to get URLs,
// handling progress reporting and retries is the job of the core TransferQueue.
// This is so that the orchestration remains core & standard but TransferAdapter
// can be changed to physically transfer to different hosts with less code.
type TransferAdapter interface {
	// Name returns the identifier of this adapter, must be unique within a Direction
	// (separate sets for upload and download so may be an entry in both)
	Name() string
	// Direction returns whether this instance is an upload or download instance
	// TransferAdapter instances can only be one or the other, although the same
	// type may be instantiated once for each direction
	Direction() Direction
	// Begin a new batch of uploads or downloads. Call this first, followed by
	// one or more Add calls. The passed in callback will receive updates on
	// progress, and the completion channel will receive completion notifications
	// Either argument may be nil if not required by the client
	Begin(cb progress.CopyCallback, completion chan TransferResult) error
	// Add queues a download/upload, which will complete asynchronously and
	// notify the callbacks given to Begin()
	Add(t *Transfer)
	// Indicate that all transfers have been scheduled and resources can be released
	// once the queued items have completed.
	// This call blocks until all items have been processed
	End()
	// ClearTempStorage clears any temporary files, such as unfinished downloads that
	// would otherwise be resumed
	ClearTempStorage() error
}

// General struct for both uploads and downloads
type Transfer struct {
	// Name of the file that triggered this transfer
	Name string
	// Object from API which provides the core data for this transfer
	Object *api.ObjectResource
	// Path for uploads is the source of data to send, for downloads is the
	// location to place the final result
	Path string
}

func NewTransfer(name string, obj *api.ObjectResource, path string) *Transfer {
	return &Transfer{name, obj, path}
}

// Result of a transfer returned through CompletionChannel()
type TransferResult struct {
	Transfer *Transfer
	// This will be non-nil if there was an error transferring this item
	Error error
}

// GetAdapters returns a list of registered adapters for the given direction
func GetAdapters(dir Direction) []TransferAdapter {
	switch dir {
	case Upload:
		return GetUploadAdapters()
	case Download:
		return GetDownloadAdapters()
	}
	return nil
}

// GetDownloadAdapters returns a list of registered adapters able to perform downloads
func GetDownloadAdapters() []TransferAdapter {
	adapterMutex.Lock()
	defer adapterMutex.Unlock()

	ret := make([]TransferAdapter, 0, len(downloadAdapters))
	for _, a := range downloadAdapters {
		ret = append(ret, a)
	}
	return ret
}

// GetUploadAdapters returns a list of registered adapters able to perform uploads
func GetUploadAdapters() []TransferAdapter {
	adapterMutex.Lock()
	defer adapterMutex.Unlock()

	ret := make([]TransferAdapter, 0, len(uploadAdapters))
	for _, a := range uploadAdapters {
		ret = append(ret, a)
	}
	return ret
}

// RegisterAdapter registers an upload or download adapter. If an adapter is
// already registered for that direction with the same name, it is overridden
func RegisterAdapter(adapter TransferAdapter) {
	adapterMutex.Lock()
	defer adapterMutex.Unlock()

	switch adapter.Direction() {
	case Upload:
		uploadAdapters[adapter.Name()] = adapter
	case Download:
		downloadAdapters[adapter.Name()] = adapter
	}
}

// Get a specific adapter by name and direction
func GetAdapter(name string, dir Direction) (TransferAdapter, bool) {
	adapterMutex.Lock()
	defer adapterMutex.Unlock()

	switch dir {
	case Upload:
		if u, ok := uploadAdapters[name]; ok {
			return u, true
		}
	case Download:
		if d, ok := downloadAdapters[name]; ok {
			return d, true
		}
	}
	return nil, false
}
<|endoftext|>


<commit_before>package intervals

func coveredIntervals(intervals [][]int) int {
	return -1
}
<commit_msg>solve 1228 use sorting<commit_after>package intervals

import "sort"

func coveredIntervals(intervals [][]int) int {
	return useSorting(intervals)
}

// useSorting time complexity O(N*logN), space complexity O(1)
func useSorting(intervals [][]int) int {
	// sort intervals based on the start time
	sort.Slice(intervals, func(i, j int) bool {
		return intervals[i][0] < intervals[j][0]
	})
	n := len(intervals)
	if n <= 1 {
		return n
	}
	left, right := intervals[0][0], intervals[0][1]
	var ans int
	for i := 1; i < n; i++ {
		if intervals[i][0] >= left && intervals[i][1] <= right {
			ans++
		} else {
			if intervals[i][0] <= left && intervals[i][1] >= right {
				ans++
			}
			left, right = intervals[i][0], intervals[i][1]
		}
	}
	return n - ans

}
<|endoftext|>


<commit_before>// Package lantern provides an embeddable client-side web proxy
package lantern

import (
	"sync"

	"github.com/getlantern/flashlight"
)

// Start starts a client proxy at a random address. It blocks until the proxy
// is listening and returns the address at which it is listening.
func Start() (string, error) {

}

func start(addr string) {

}
<commit_msg>Added basic lantern API<commit_after>// Package lantern provides an embeddable client-side web proxy
package lantern

import (
	"fmt"
	"sync"
	"time"

	"github.com/getlantern/appdir"
	"github.com/getlantern/flashlight"
	"github.com/getlantern/flashlight/client"
	"github.com/getlantern/flashlight/config"
	"github.com/getlantern/golog"
)

var (
	log = golog.LoggerFor("lantern")

	startOnce sync.Once
)

// Start starts a client proxy at a random address. It blocks up till the given
// timeout waiting for the proxy to listen, and returns the address at which it
// is listening.
func Start(appName string, timeout time.Duration) (string, error) {
	startOnce.Do(func() {
		go run(appName)
	})
	addr, ok := client.Addr(timeout)
	if !ok {
		return "", fmt.Errorf("Proxy didn't start within given timeout")
	}
	return addr.(string), nil
}

func run(appName string) {
	flashlight.Start(appdir.General("lantern_"+appName),
		false,
		func() bool { return true },
		make(map[string]interface{}),
		func(cfg *config.Config) bool { return true },
		func(cfg *config.Config) {},
		func(cfg *config.Config) {},
		func(err error) {},
	)
}
<|endoftext|>


<commit_before>package closuretable

import (
	"fmt"
	"github.com/carbocation/util.git/datatypes/binarytree"
	"math/rand"
	"strconv"
	"testing"
)

func TestClosureConversion(t *testing.T) {
	// Make some sample entries based on a skeleton
	entries := map[int64]int{
		0: 0, 10: 10, 20: 20, 30: 30, 40: 40, 50: 50, 60: 60,
	}

	// Create a closure table to represent the relationships among the entries
	// In reality, you'd probably directly import the closure table data into the ClosureTable class
	closuretable := New(0)
	closuretable.AddChild(Child{Parent: 0, Child: 10})
	closuretable.AddChild(Child{Parent: 0, Child: 20})
	closuretable.AddChild(Child{Parent: 10, Child: 30})
	closuretable.AddChild(Child{Parent: 30, Child: 40})
	closuretable.AddChild(Child{Parent: 20, Child: 50})
	closuretable.AddChild(Child{Parent: 0, Child: 60})

	// Obligatory boxing step
	// Convert to interface type so the generic TableToTree method can be called on these entries
	interfaceEntries := map[int64]interface{}{}
	for k, v := range entries {
		interfaceEntries[k] = v
	}

	//Build a tree out of the entries based on the closure table's instructions.
	tree := closuretable.TableToTree(interfaceEntries)
	result := sumInts(tree)
	expected := 210
	if result != expected {
		t.Errorf("walkBody(tree) yielded %s, expected %s. Have you made a change that caused the iteration order to become indeterminate, e.g., using a map instead of a slice?", result, expected)
	}

	sExpected := "0103040205060"
	sResult := stringInts(tree)
	if sResult != sExpected {
		t.Errorf("walkBody(tree) yielded %s, expected %s. Have you made a change that caused the iteration order to become indeterminate, e.g., using a map instead of a slice?", sResult, sExpected)
	}

}

func sumInts(el *binarytree.Tree) int {
	if el == nil {
		return 0
	}

	out := 0
	out += el.Value.(int)
	out += sumInts(el.Left())
	out += sumInts(el.Right())

	return out
}

func stringInts(el *binarytree.Tree) string {
	if el == nil {
		return ""
	}

	out := ""
	out += strconv.Itoa(el.Value.(int))
	out += stringInts(el.Left())
	out += stringInts(el.Right())

	return out
}

func buildClosureTable(N int) ClosureTable {
	// Create the closure table with a single progenitor
	ct := ClosureTable{Relationship{Ancestor: 0, Descendant: 0, Depth: 0}}

	for i := 1; i < N; i++ {
		// Create a place for entry #i, making it the child of a random entry j<i
		err := ct.AddChild(Child{Parent: rand.Int63n(int64(i)), Child: int64(i)})
		if err != nil {
			fmt.Println(err)
			break
		}
	}

	return ct
}
<commit_msg>Fixed test since TableToTree now returns an error value, too.<commit_after>package closuretable

import (
	"fmt"
	"github.com/carbocation/util.git/datatypes/binarytree"
	"math/rand"
	"strconv"
	"testing"
)

func TestClosureConversion(t *testing.T) {
	// Make some sample entries based on a skeleton
	entries := map[int64]int{
		0: 0, 10: 10, 20: 20, 30: 30, 40: 40, 50: 50, 60: 60,
	}

	// Create a closure table to represent the relationships among the entries
	// In reality, you'd probably directly import the closure table data into the ClosureTable class
	closuretable := New(0)
	closuretable.AddChild(Child{Parent: 0, Child: 10})
	closuretable.AddChild(Child{Parent: 0, Child: 20})
	closuretable.AddChild(Child{Parent: 10, Child: 30})
	closuretable.AddChild(Child{Parent: 30, Child: 40})
	closuretable.AddChild(Child{Parent: 20, Child: 50})
	closuretable.AddChild(Child{Parent: 0, Child: 60})

	// Obligatory boxing step
	// Convert to interface type so the generic TableToTree method can be called on these entries
	interfaceEntries := map[int64]interface{}{}
	for k, v := range entries {
		interfaceEntries[k] = v
	}

	//Build a tree out of the entries based on the closure table's instructions.
	tree, err := closuretable.TableToTree(interfaceEntries); if err != nil {
		t.Errorf("%s",err)
	}

	result := sumInts(tree)
	expected := 210
	if result != expected {
		t.Errorf("walkBody(tree) yielded %s, expected %s. Have you made a change that caused the iteration order to become indeterminate, e.g., using a map instead of a slice?", result, expected)
	}

	sExpected := "0103040205060"
	sResult := stringInts(tree)
	if sResult != sExpected {
		t.Errorf("walkBody(tree) yielded %s, expected %s. Have you made a change that caused the iteration order to become indeterminate, e.g., using a map instead of a slice?", sResult, sExpected)
	}

}

func sumInts(el *binarytree.Tree) int {
	if el == nil {
		return 0
	}

	out := 0
	out += el.Value.(int)
	out += sumInts(el.Left())
	out += sumInts(el.Right())

	return out
}

func stringInts(el *binarytree.Tree) string {
	if el == nil {
		return ""
	}

	out := ""
	out += strconv.Itoa(el.Value.(int))
	out += stringInts(el.Left())
	out += stringInts(el.Right())

	return out
}

func buildClosureTable(N int) ClosureTable {
	// Create the closure table with a single progenitor
	ct := ClosureTable{Relationship{Ancestor: 0, Descendant: 0, Depth: 0}}

	for i := 1; i < N; i++ {
		// Create a place for entry #i, making it the child of a random entry j<i
		err := ct.AddChild(Child{Parent: rand.Int63n(int64(i)), Child: int64(i)})
		if err != nil {
			fmt.Println(err)
			break
		}
	}

	return ct
}
<|endoftext|>


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

package instance

import (
	"fmt"
	"strings"

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

const (
	// MachineScope is a special scope name that is used
	// for machine placement directives (e.g. --to 0).
	MachineScope = "#"
)

// Placement defines a placement directive, which has a scope
// and a value that is scope-specific.
type Placement struct {
	// Scope is the scope of the placement directive. Scope may
	// be a container type (lxc, kvm), instance.MachineScope, or
	// an environment name.
	//
	// If Scope is empty, then it must be inferred from the context.
	Scope string

	// Value is a scope-specific placement value.
	//
	// For MachineScope or a container scope, this may be empty or
	// the ID of an existing machine.
	Value string
}

func (p *Placement) String() string {
	return fmt.Sprintf("%s:%s", p.Scope, p.Value)
}

func isContainerType(s string) bool {
	_, err := ParseContainerType(s)
	return err == nil
}

// ParsePlacement attempts to parse the specified string and create a
// corresponding Placement structure.
func ParsePlacement(directive string) (*Placement, error) {
	if directive == "" {
		return nil, nil
	}
	if colon := strings.IndexRune(directive, ':'); colon != -1 {
		scope, value := directive[:colon], directive[colon+1:]
		// Sanity check: machine/container scopes require a machine ID as the value.
		if (scope == MachineScope || isContainerType(scope)) && !names.IsMachine(value) {
			return nil, fmt.Errorf("invalid value %q for %q scope: expected machine-id", value, scope)
		}
		return &Placement{Scope: scope, Value: value}, nil
	}
	if names.IsMachine(directive) {
		return &Placement{Scope: MachineScope, Value: directive}, nil
	}
	if isContainerType(directive) {
		return &Placement{Scope: directive}, nil
	}
	// Empty scope, caller must infer the scope from context.
	return &Placement{Value: directive}, nil
}

// ParsePlacement attempts to parse the specified string and create a
// corresponding Placement structure, panicking if an error occurs.
func MustParsePlacement(directive string) *Placement {
    placement, err := ParsePlacement(directive)
    if err != nil {
        panic(err)
    }
    return placement
}
<commit_msg>gofmt<commit_after>// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package instance

import (
	"fmt"
	"strings"

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

const (
	// MachineScope is a special scope name that is used
	// for machine placement directives (e.g. --to 0).
	MachineScope = "#"
)

// Placement defines a placement directive, which has a scope
// and a value that is scope-specific.
type Placement struct {
	// Scope is the scope of the placement directive. Scope may
	// be a container type (lxc, kvm), instance.MachineScope, or
	// an environment name.
	//
	// If Scope is empty, then it must be inferred from the context.
	Scope string

	// Value is a scope-specific placement value.
	//
	// For MachineScope or a container scope, this may be empty or
	// the ID of an existing machine.
	Value string
}

func (p *Placement) String() string {
	return fmt.Sprintf("%s:%s", p.Scope, p.Value)
}

func isContainerType(s string) bool {
	_, err := ParseContainerType(s)
	return err == nil
}

// ParsePlacement attempts to parse the specified string and create a
// corresponding Placement structure.
func ParsePlacement(directive string) (*Placement, error) {
	if directive == "" {
		return nil, nil
	}
	if colon := strings.IndexRune(directive, ':'); colon != -1 {
		scope, value := directive[:colon], directive[colon+1:]
		// Sanity check: machine/container scopes require a machine ID as the value.
		if (scope == MachineScope || isContainerType(scope)) && !names.IsMachine(value) {
			return nil, fmt.Errorf("invalid value %q for %q scope: expected machine-id", value, scope)
		}
		return &Placement{Scope: scope, Value: value}, nil
	}
	if names.IsMachine(directive) {
		return &Placement{Scope: MachineScope, Value: directive}, nil
	}
	if isContainerType(directive) {
		return &Placement{Scope: directive}, nil
	}
	// Empty scope, caller must infer the scope from context.
	return &Placement{Value: directive}, nil
}

// ParsePlacement attempts to parse the specified string and create a
// corresponding Placement structure, panicking if an error occurs.
func MustParsePlacement(directive string) *Placement {
	placement, err := ParsePlacement(directive)
	if err != nil {
		panic(err)
	}
	return placement
}
<|endoftext|>


<commit_before>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package k8sapi

// Where possible, json tags match the cli argument names.
// Top level config objects and all values required for proper functioning are not "omitempty".  Any truly optional piece of config is allowed to be omitted.

// Config holds the information needed to build connect to remote kubernetes clusters as a given user
type Config struct {
	// Legacy field from pkg/api/types.go TypeMeta.
	// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
	Kind string `json:"kind,omitempty"`
	// Deprecated: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
	// Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify
	// a single value for the cluster version.
	// This field isn't really needed anyway, so we are deprecating it without replacement.
	// It will be ignored if it is present.
	APIVersion string `json:"apiVersion,omitempty"`
	// Preferences holds general information to be use for cli interactions
	Preferences Preferences `json:"preferences"`
	// Clusters is a map of referencable names to cluster configs
	Clusters []NamedCluster `json:"clusters"`
	// AuthInfos is a map of referencable names to user configs
	AuthInfos []NamedAuthInfo `json:"users"`
	// Contexts is a map of referencable names to context configs
	Contexts []NamedContext `json:"contexts"`
	// CurrentContext is the name of the context that you would like to use by default
	CurrentContext string `json:"current-context"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// Preferences contains information about the users command line experience preferences.
type Preferences struct {
	Colors bool `json:"colors,omitempty"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// Cluster contains information about how to communicate with a kubernetes cluster
type Cluster struct {
	// Server is the address of the kubernetes cluster (https://hostname:port).
	Server string `json:"server"`
	// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
	APIVersion string `json:"api-version,omitempty"`
	// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
	InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
	// CertificateAuthority is the path to a cert file for the certificate authority.
	CertificateAuthority string `json:"certificate-authority,omitempty"`
	// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
	//
	// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
	CertificateAuthorityData string `json:"certificate-authority-data,omitempty"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// AuthInfo contains information that describes identity information.  This is use to tell the kubernetes cluster who you are.
type AuthInfo struct {
	// ClientCertificate is the path to a client cert file for TLS.
	ClientCertificate string `json:"client-certificate,omitempty"`
	// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
	//
	// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
	ClientCertificateData string `json:"client-certificate-data,omitempty"`
	// ClientKey is the path to a client key file for TLS.
	ClientKey string `json:"client-key,omitempty"`
	// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
	//
	// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
	ClientKeyData string `json:"client-key-data,omitempty"`
	// Token is the bearer token for authentication to the kubernetes cluster.
	Token string `json:"token,omitempty"`
	// Impersonate is the username to impersonate.  The name matches the flag.
	Impersonate string `json:"as,omitempty"`
	// Username is the username for basic authentication to the kubernetes cluster.
	Username string `json:"username,omitempty"`
	// Password is the password for basic authentication to the kubernetes cluster.
	Password string `json:"password,omitempty"`
	// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
	AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
type Context struct {
	// Cluster is the name of the cluster for this context
	Cluster string `json:"cluster"`
	// AuthInfo is the name of the authInfo for this context
	AuthInfo string `json:"user"`
	// Namespace is the default namespace to use on unspecified requests
	Namespace string `json:"namespace,omitempty"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// NamedCluster relates nicknames to cluster information
type NamedCluster struct {
	// Name is the nickname for this Cluster
	Name string `json:"name"`
	// Cluster holds the cluster information
	Cluster Cluster `json:"cluster"`
}

// NamedContext relates nicknames to context information
type NamedContext struct {
	// Name is the nickname for this Context
	Name string `json:"name"`
	// Context holds the context information
	Context Context `json:"context"`
}

// NamedAuthInfo relates nicknames to auth information
type NamedAuthInfo struct {
	// Name is the nickname for this AuthInfo
	Name string `json:"name"`
	// AuthInfo holds the auth information
	AuthInfo AuthInfo `json:"user"`
}

// NamedExtension relates nicknames to extension information
type NamedExtension struct {
	// Name is the nickname for this Extension
	Name string `json:"name"`
}

// AuthProviderConfig holds the configuration for a specified auth provider.
type AuthProviderConfig struct {
	Name   string            `json:"name"`
	Config map[string]string `json:"config"`
}
<commit_msg>spelling: referenceable<commit_after>/*
Copyright 2014 The Kubernetes Authors All rights reserved.

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

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

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

package k8sapi

// Where possible, json tags match the cli argument names.
// Top level config objects and all values required for proper functioning are not "omitempty".  Any truly optional piece of config is allowed to be omitted.

// Config holds the information needed to build connect to remote kubernetes clusters as a given user
type Config struct {
	// Legacy field from pkg/api/types.go TypeMeta.
	// TODO(jlowdermilk): remove this after eliminating downstream dependencies.
	Kind string `json:"kind,omitempty"`
	// Deprecated: APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
	// Because a cluster can run multiple API groups and potentially multiple versions of each, it no longer makes sense to specify
	// a single value for the cluster version.
	// This field isn't really needed anyway, so we are deprecating it without replacement.
	// It will be ignored if it is present.
	APIVersion string `json:"apiVersion,omitempty"`
	// Preferences holds general information to be use for cli interactions
	Preferences Preferences `json:"preferences"`
	// Clusters is a map of referenceable names to cluster configs
	Clusters []NamedCluster `json:"clusters"`
	// AuthInfos is a map of referenceable names to user configs
	AuthInfos []NamedAuthInfo `json:"users"`
	// Contexts is a map of referenceable names to context configs
	Contexts []NamedContext `json:"contexts"`
	// CurrentContext is the name of the context that you would like to use by default
	CurrentContext string `json:"current-context"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// Preferences contains information about the users command line experience preferences.
type Preferences struct {
	Colors bool `json:"colors,omitempty"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// Cluster contains information about how to communicate with a kubernetes cluster
type Cluster struct {
	// Server is the address of the kubernetes cluster (https://hostname:port).
	Server string `json:"server"`
	// APIVersion is the preferred api version for communicating with the kubernetes cluster (v1, v2, etc).
	APIVersion string `json:"api-version,omitempty"`
	// InsecureSkipTLSVerify skips the validity check for the server's certificate. This will make your HTTPS connections insecure.
	InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify,omitempty"`
	// CertificateAuthority is the path to a cert file for the certificate authority.
	CertificateAuthority string `json:"certificate-authority,omitempty"`
	// CertificateAuthorityData contains PEM-encoded certificate authority certificates. Overrides CertificateAuthority
	//
	// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
	CertificateAuthorityData string `json:"certificate-authority-data,omitempty"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// AuthInfo contains information that describes identity information.  This is use to tell the kubernetes cluster who you are.
type AuthInfo struct {
	// ClientCertificate is the path to a client cert file for TLS.
	ClientCertificate string `json:"client-certificate,omitempty"`
	// ClientCertificateData contains PEM-encoded data from a client cert file for TLS. Overrides ClientCertificate
	//
	// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
	ClientCertificateData string `json:"client-certificate-data,omitempty"`
	// ClientKey is the path to a client key file for TLS.
	ClientKey string `json:"client-key,omitempty"`
	// ClientKeyData contains PEM-encoded data from a client key file for TLS. Overrides ClientKey
	//
	// NOTE(ericchiang): Our yaml parser doesn't assume []byte is a base64 encoded string.
	ClientKeyData string `json:"client-key-data,omitempty"`
	// Token is the bearer token for authentication to the kubernetes cluster.
	Token string `json:"token,omitempty"`
	// Impersonate is the username to impersonate.  The name matches the flag.
	Impersonate string `json:"as,omitempty"`
	// Username is the username for basic authentication to the kubernetes cluster.
	Username string `json:"username,omitempty"`
	// Password is the password for basic authentication to the kubernetes cluster.
	Password string `json:"password,omitempty"`
	// AuthProvider specifies a custom authentication plugin for the kubernetes cluster.
	AuthProvider *AuthProviderConfig `json:"auth-provider,omitempty"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// Context is a tuple of references to a cluster (how do I communicate with a kubernetes cluster), a user (how do I identify myself), and a namespace (what subset of resources do I want to work with)
type Context struct {
	// Cluster is the name of the cluster for this context
	Cluster string `json:"cluster"`
	// AuthInfo is the name of the authInfo for this context
	AuthInfo string `json:"user"`
	// Namespace is the default namespace to use on unspecified requests
	Namespace string `json:"namespace,omitempty"`
	// Extensions holds additional information. This is useful for extenders so that reads and writes don't clobber unknown fields
	Extensions []NamedExtension `json:"extensions,omitempty"`
}

// NamedCluster relates nicknames to cluster information
type NamedCluster struct {
	// Name is the nickname for this Cluster
	Name string `json:"name"`
	// Cluster holds the cluster information
	Cluster Cluster `json:"cluster"`
}

// NamedContext relates nicknames to context information
type NamedContext struct {
	// Name is the nickname for this Context
	Name string `json:"name"`
	// Context holds the context information
	Context Context `json:"context"`
}

// NamedAuthInfo relates nicknames to auth information
type NamedAuthInfo struct {
	// Name is the nickname for this AuthInfo
	Name string `json:"name"`
	// AuthInfo holds the auth information
	AuthInfo AuthInfo `json:"user"`
}

// NamedExtension relates nicknames to extension information
type NamedExtension struct {
	// Name is the nickname for this Extension
	Name string `json:"name"`
}

// AuthProviderConfig holds the configuration for a specified auth provider.
type AuthProviderConfig struct {
	Name   string            `json:"name"`
	Config map[string]string `json:"config"`
}
<|endoftext|>


<commit_before>// Package llog implements a simple logger on top of stdlib's log with two log levels.
package llog

import (
	"log"
)

type Logger struct {
	*log.Logger
	dbg bool
}

func NewLogger(logger *log.Logger, debug bool) *Logger {
	return &Logger{logger, debug}
}

func (l *Logger) Debugf(format string, args ...interface{}) {
	if l.dbg {
		log.Printf(format, args...)
	}
}

func (l *Logger) Debugln(args ...interface{}) {
	if l.dbg {
		log.Println(args...)
	}
}
<commit_msg>cleanup blah<commit_after>// Package llog implements a simple logger on top of stdlib's log with two levels.
package llog

import (
	"log"
)

type Logger struct {
	*log.Logger
	dbg bool
}

func NewLogger(logger *log.Logger, debug bool) *Logger {
	return &Logger{logger, debug}
}

func (l *Logger) Debugf(format string, args ...interface{}) {
	if l.dbg {
		log.Printf(format, args...)
	}
}

func (l *Logger) Debugln(args ...interface{}) {
	if l.dbg {
		log.Println(args...)
	}
}
<|endoftext|>


<commit_before>// Copyright 2017 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 tree

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"regexp/syntax"
	"sort"
	"strings"

	"github.com/issue9/mux/internal/tree/handlers"
	ts "github.com/issue9/mux/internal/tree/syntax"
)

// Node 表示路由中的节点。多段路由项，会提取其中的相同的内容组成树状结构的节点。
// 比如以下路由项：
//  /posts/{id}/author
//  /posts/{id}/author/emails
//  /posts/{id}/author/profile
//  /posts/1/author
// 会被转换成以下结构
//  /posts
//     |
//     +---- 1/author
//     |
//     +---- {id}/author
//               |
//               +---- profile
//               |
//               +---- emails
type Node struct {
	parent   *Node
	nodeType ts.Type
	children []*Node
	pattern  string
	handlers *handlers.Handlers

	// 用于表示当前节点是否为终点，仅对 nodeType 为 TypeRegexp 和 TypeNamed 有用。
	// 此值为 true，该节点的优先级会比同类型的节点低，以便优先对比其它非最终节点。
	endpoint bool

	// 命名参数特有的参数
	name   string // 缓存着名称
	suffix string // 保存着命名之后的字符串内容

	// 正则特有的参数
	expr       *regexp.Regexp
	syntaxExpr *syntax.Regexp
}

// 当前节点的优先级，根据节点类型来判断，
// 若类型相同时，则有子节点的优先级低一些，但不会超过不同节点类型。
func (n *Node) priority() int {
	p := int(n.nodeType)

	if len(n.children) > 0 {
		p++
	}
	if n.endpoint {
		p++
	}

	return p
}

// 添加一条路由，当 methods 为空时，表示仅添加节点，而不添加任何处理函数。
func (n *Node) add(segments []*ts.Segment, h http.Handler, methods ...string) error {
	child, err := n.addSegment(segments[0])
	if err != nil {
		return err
	}

	if len(segments) == 1 { // 最后一个节点
		if child.handlers == nil {
			child.handlers = handlers.New()
		}
		return child.handlers.Add(h, methods...)
	}
	return child.add(segments[1:], h, methods...)
}

// 添加一条 ts.Segment 到当前路由项，并返回其最后的节点
func (n *Node) addSegment(s *ts.Segment) (*Node, error) {
	var child *Node // 找到的最匹配节点
	var l int       // 最大的匹配字符数量

	// 提取两者的共同前缀
	for _, c := range n.children {
		if c.endpoint != s.Endpoint {
			continue
		}

		// 有完全相同的节点
		if c.endpoint == s.Endpoint &&
			c.pattern == s.Value &&
			c.nodeType == s.Type {
			return c, nil
		}

		if l1 := ts.PrefixLen(c.pattern, s.Value); l1 > l {
			l = l1
			child = c
		}
	}

	// 没有共同前缀，声明一个新的加入到当前节点
	if l <= 0 {
		return n.newChild(s)
	}

	parent, err := splitNode(child, l)
	if err != nil {
		return nil, err
	}

	if len(s.Value) == l {
		return parent, nil
	}
	return parent.addSegment(ts.NewSegment(s.Value[l:]))
}

// 根据 seg 内容为当前节点产生一个子节点
func (n *Node) newChild(seg *ts.Segment) (*Node, error) {
	child := &Node{
		parent:   n,
		pattern:  seg.Value,
		nodeType: seg.Type,
	}

	switch seg.Type {
	case ts.TypeNamed:
		endIndex := strings.IndexByte(seg.Value, ts.NameEnd)
		if endIndex == -1 { // TODO 由 ts.Segment 保存语法是有效的，是否更佳？
			return nil, fmt.Errorf("无效的路由语法：%s", seg.Value)
		}
		child.suffix = seg.Value[endIndex+1:]
		child.name = seg.Value[1:endIndex]
		child.endpoint = seg.Endpoint
	case ts.TypeRegexp:
		reg := ts.Regexp(seg.Value)
		expr, err := regexp.Compile(reg)
		if err != nil {
			return nil, err
		}

		syntaxExpr, err := syntax.Parse(reg, syntax.Perl)
		if err != nil {
			return nil, err
		}
		child.expr = expr
		child.syntaxExpr = syntaxExpr
		child.endpoint = seg.Endpoint
	} // end switch

	n.children = append(n.children, child)
	sort.SliceStable(n.children, func(i, j int) bool {
		return n.children[i].priority() < n.children[j].priority()
	})

	return child, nil
}

// 查找路由项，不存在返回 nil
func (n *Node) find(pattern string) *Node {
	for _, child := range n.children {
		if len(child.pattern) < len(pattern) {
			if !strings.HasPrefix(pattern, child.pattern) {
				continue
			}

			nn := child.find(pattern[len(child.pattern):])
			if nn != nil {
				return nn
			}
		}

		if child.pattern == pattern {
			return child
		}
	}

	return nil
}

// Clean 清除路由项
func (n *Node) Clean(prefix string) {
	if len(prefix) == 0 {
		n.children = n.children[:0]
		return
	}

	dels := make([]string, 0, len(n.children))
	for _, child := range n.children {
		if len(child.pattern) < len(prefix) {
			if strings.HasPrefix(prefix, child.pattern) {
				child.Clean(prefix[len(child.pattern):])
			}
		}

		if strings.HasPrefix(child.pattern, prefix) {
			dels = append(dels, child.pattern)
		}
	}

	for _, del := range dels {
		n.children = removeNodes(n.children, del)
	}
}

// Remove 移除路由项
func (n *Node) Remove(pattern string, methods ...string) error {
	child := n.find(pattern)

	if child == nil {
		return fmt.Errorf("不存在的节点 %v", pattern)
	}

	if child.handlers == nil {
		return nil
	}

	if child.handlers.Remove(methods...) {
		child.parent.children = removeNodes(child.parent.children, child.pattern)
	}
	return nil
}

// Match 从子节点中查找与当前路径匹配的节点，若找不到，则返回 nil
func (n *Node) Match(path string) *Node {
	if len(n.children) == 0 && len(path) == 0 {
		return n
	}

	for _, node := range n.children {
		matched := false
		newPath := path

		switch node.nodeType {
		case ts.TypeBasic:
			matched = strings.HasPrefix(path, node.pattern)
			if matched {
				newPath = path[len(node.pattern):]
			}
		case ts.TypeNamed:
			if node.endpoint {
				matched = true
				newPath = path[:0]
			} else {
				index := strings.Index(path, node.suffix)
				if index > 0 { // 为零说明前面没有命名参数，肯定不正确
					matched = true
					newPath = path[index+len(node.suffix):]
				}
			}
		case ts.TypeRegexp:
			loc := node.expr.FindStringIndex(path)
			if loc != nil && loc[0] == 0 {
				matched = true
				if loc[1] == len(path) {
					newPath = path[:0]
				} else {
					newPath = path[loc[1]+1:]
				}
			}
		default:
			// nodeType 错误，肯定是代码级别的错误，直接 panic
			panic("无效的 nodeType 值")
		}

		if matched {
			// 即使 newPath 为空，也有可能子节点正好可以匹配空的内容。
			// 比如 /posts/{path:\\w*} 后面的 path 即为空节点。
			if nn := node.Match(newPath); nn != nil {
				return nn
			}
			if len(newPath) == 0 && node.handlers != nil && node.handlers.Len() > 0 {
				return node
			}
		}
	} // end for

	return nil
}

// Params 由调用方确保能正常匹配 path
func (n *Node) Params(path string) map[string]string {
	nodes := n.getParents()
	params := make(map[string]string, 10)

	for i := len(nodes) - 1; i >= 0; i-- {
		node := nodes[i]
		switch node.nodeType {
		case ts.TypeBasic:
			path = path[len(node.pattern):]
		case ts.TypeNamed:
			if node.endpoint {
				params[node.name] = path
				path = path[:0]
			} else {
				index := strings.Index(path, node.suffix)
				if index > 0 { // 为零说明前面没有命名参数，肯定不正确
					params[node.name] = path[:index]
					path = path[index+len(node.suffix):]
				}
			}
		case ts.TypeRegexp:
			// 正确匹配正则表达式，则获相关的正则表达式命名变量。
			subexps := node.expr.SubexpNames()
			args := node.expr.FindStringSubmatch(path)
			for index, name := range subexps {
				if len(name) > 0 && index < len(args) {
					params[name] = args[index]
				}
			}

			path = path[len(args[0]):]
		}
	}

	return params
}

// URL 根据参数生成地址
func (n *Node) URL(params map[string]string) (string, error) {
	nodes := n.getParents()
	buf := new(bytes.Buffer)

	for i := len(nodes) - 1; i >= 0; i-- {
		node := nodes[i]
		switch node.nodeType {
		case ts.TypeBasic:
			buf.WriteString(node.pattern)
		case ts.TypeNamed:
			param, exists := params[node.name]
			if !exists {
				return "", fmt.Errorf("未找到参数 %s 的值", node.name)
			}
			buf.WriteString(param)
			buf.WriteString(node.suffix) // 如果是 endpoint suffix 肯定为空
		case ts.TypeRegexp:
			url := node.syntaxExpr.String()
			subs := append(node.syntaxExpr.Sub, node.syntaxExpr)
			for _, sub := range subs {
				if len(sub.Name) == 0 {
					continue
				}

				param, exists := params[sub.Name]
				if !exists {
					return "", fmt.Errorf("未找到参数 %v 的值", sub.Name)
				}
				url = strings.Replace(url, sub.String(), param, -1)
			}

			buf.WriteString(url)
		}
	}

	return buf.String(), nil
}

// 逐级向上获取父节点，包含当前节点。
func (n *Node) getParents() []*Node {
	nodes := make([]*Node, 0, 10) // 从尾部向上开始获取节点

	for curr := n; curr != nil; curr = curr.parent {
		nodes = append(nodes, curr)
	}

	return nodes
}

// SetAllow 设置当前节点的 allow 报头
func (n *Node) SetAllow(allow string) {
	if n.handlers == nil {
		n.handlers = handlers.New()
	}

	n.handlers.SetAllow(allow)
}

// Handler 获取该节点下与参数相对应的处理函数
func (n *Node) Handler(method string) http.Handler {
	if n.handlers == nil {
		return nil
	}

	return n.handlers.Handler(method)
}

// 向客户端打印节点的树状结构
func (n *Node) print(deep int) {
	fmt.Println(strings.Repeat(" ", deep*4), n.pattern)

	for _, child := range n.children {
		child.print(deep + 1)
	}
}

// 获取路由数量
func (n *Node) len() int {
	var cnt int
	for _, child := range n.children {
		cnt += child.len()
	}

	if n.handlers != nil && n.handlers.Len() > 0 {
		cnt++
	}

	return cnt
}

func removeNodes(nodes []*Node, pattern string) []*Node {
	lastIndex := len(nodes) - 1
	for index, n := range nodes {
		if n.pattern != pattern {
			continue
		}

		switch {
		case len(nodes) == 1: // 只有一个元素
			return nodes[:0]
		case index == lastIndex: // 最后一个元素
			return nodes[:lastIndex]
		default:
			return append(nodes[:index], nodes[index+1:]...)
		}
	} // end for

	return nodes
}

// 将节点 n 从 pos 位置进行拆分。后一段作为当前段的子节点，并返回当前节点。
// 若 pos 大于或等于 n.pattern 的长度，则直接返回 n 不会拆分。
//
// NOTE: 调用者需确保 pos 位置是可拆分的。
func splitNode(n *Node, pos int) (*Node, error) {
	if len(n.pattern) <= pos { // 不需要拆分
		return n, nil
	}

	p := n.parent
	if p == nil {
		return nil, errors.New("split:节点必须要有一个有效的父节点，才能进行拆分")
	}

	// 先从父节点中删除老的 n
	p.children = removeNodes(p.children, n.pattern)

	ret, err := p.newChild(ts.NewSegment(n.pattern[:pos]))
	if err != nil {
		return nil, err
	}

	c, err := ret.newChild(ts.NewSegment(n.pattern[pos:]))
	if err != nil {
		return nil, err
	}
	c.handlers = n.handlers
	c.children = n.children
	for _, item := range c.children {
		item.parent = c
	}

	return ret, nil
}
<commit_msg>[internal/tree] 去掉 Node.Params 中不必要的判断<commit_after>// Copyright 2017 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 tree

import (
	"bytes"
	"errors"
	"fmt"
	"net/http"
	"regexp"
	"regexp/syntax"
	"sort"
	"strings"

	"github.com/issue9/mux/internal/tree/handlers"
	ts "github.com/issue9/mux/internal/tree/syntax"
)

// Node 表示路由中的节点。多段路由项，会提取其中的相同的内容组成树状结构的节点。
// 比如以下路由项：
//  /posts/{id}/author
//  /posts/{id}/author/emails
//  /posts/{id}/author/profile
//  /posts/1/author
// 会被转换成以下结构
//  /posts
//     |
//     +---- 1/author
//     |
//     +---- {id}/author
//               |
//               +---- profile
//               |
//               +---- emails
type Node struct {
	parent   *Node
	nodeType ts.Type
	children []*Node
	pattern  string
	handlers *handlers.Handlers

	// 用于表示当前节点是否为终点，仅对 nodeType 为 TypeRegexp 和 TypeNamed 有用。
	// 此值为 true，该节点的优先级会比同类型的节点低，以便优先对比其它非最终节点。
	endpoint bool

	// 命名参数特有的参数
	name   string // 缓存着名称
	suffix string // 保存着命名之后的字符串内容

	// 正则特有的参数
	expr       *regexp.Regexp
	syntaxExpr *syntax.Regexp
}

// 当前节点的优先级，根据节点类型来判断，
// 若类型相同时，则有子节点的优先级低一些，但不会超过不同节点类型。
func (n *Node) priority() int {
	p := int(n.nodeType)

	if len(n.children) > 0 {
		p++
	}
	if n.endpoint {
		p++
	}

	return p
}

// 添加一条路由，当 methods 为空时，表示仅添加节点，而不添加任何处理函数。
func (n *Node) add(segments []*ts.Segment, h http.Handler, methods ...string) error {
	child, err := n.addSegment(segments[0])
	if err != nil {
		return err
	}

	if len(segments) == 1 { // 最后一个节点
		if child.handlers == nil {
			child.handlers = handlers.New()
		}
		return child.handlers.Add(h, methods...)
	}
	return child.add(segments[1:], h, methods...)
}

// 添加一条 ts.Segment 到当前路由项，并返回其最后的节点
func (n *Node) addSegment(s *ts.Segment) (*Node, error) {
	var child *Node // 找到的最匹配节点
	var l int       // 最大的匹配字符数量

	// 提取两者的共同前缀
	for _, c := range n.children {
		if c.endpoint != s.Endpoint {
			continue
		}

		// 有完全相同的节点
		if c.endpoint == s.Endpoint &&
			c.pattern == s.Value &&
			c.nodeType == s.Type {
			return c, nil
		}

		if l1 := ts.PrefixLen(c.pattern, s.Value); l1 > l {
			l = l1
			child = c
		}
	}

	// 没有共同前缀，声明一个新的加入到当前节点
	if l <= 0 {
		return n.newChild(s)
	}

	parent, err := splitNode(child, l)
	if err != nil {
		return nil, err
	}

	if len(s.Value) == l {
		return parent, nil
	}
	return parent.addSegment(ts.NewSegment(s.Value[l:]))
}

// 根据 seg 内容为当前节点产生一个子节点
func (n *Node) newChild(seg *ts.Segment) (*Node, error) {
	child := &Node{
		parent:   n,
		pattern:  seg.Value,
		nodeType: seg.Type,
	}

	switch seg.Type {
	case ts.TypeNamed:
		endIndex := strings.IndexByte(seg.Value, ts.NameEnd)
		if endIndex == -1 { // TODO 由 ts.Segment 保存语法是有效的，是否更佳？
			return nil, fmt.Errorf("无效的路由语法：%s", seg.Value)
		}
		child.suffix = seg.Value[endIndex+1:]
		child.name = seg.Value[1:endIndex]
		child.endpoint = seg.Endpoint
	case ts.TypeRegexp:
		reg := ts.Regexp(seg.Value)
		expr, err := regexp.Compile(reg)
		if err != nil {
			return nil, err
		}

		syntaxExpr, err := syntax.Parse(reg, syntax.Perl)
		if err != nil {
			return nil, err
		}
		child.expr = expr
		child.syntaxExpr = syntaxExpr
		child.endpoint = seg.Endpoint
	} // end switch

	n.children = append(n.children, child)
	sort.SliceStable(n.children, func(i, j int) bool {
		return n.children[i].priority() < n.children[j].priority()
	})

	return child, nil
}

// 查找路由项，不存在返回 nil
func (n *Node) find(pattern string) *Node {
	for _, child := range n.children {
		if len(child.pattern) < len(pattern) {
			if !strings.HasPrefix(pattern, child.pattern) {
				continue
			}

			nn := child.find(pattern[len(child.pattern):])
			if nn != nil {
				return nn
			}
		}

		if child.pattern == pattern {
			return child
		}
	}

	return nil
}

// Clean 清除路由项
func (n *Node) Clean(prefix string) {
	if len(prefix) == 0 {
		n.children = n.children[:0]
		return
	}

	dels := make([]string, 0, len(n.children))
	for _, child := range n.children {
		if len(child.pattern) < len(prefix) {
			if strings.HasPrefix(prefix, child.pattern) {
				child.Clean(prefix[len(child.pattern):])
			}
		}

		if strings.HasPrefix(child.pattern, prefix) {
			dels = append(dels, child.pattern)
		}
	}

	for _, del := range dels {
		n.children = removeNodes(n.children, del)
	}
}

// Remove 移除路由项
func (n *Node) Remove(pattern string, methods ...string) error {
	child := n.find(pattern)

	if child == nil {
		return fmt.Errorf("不存在的节点 %v", pattern)
	}

	if child.handlers == nil {
		return nil
	}

	if child.handlers.Remove(methods...) {
		child.parent.children = removeNodes(child.parent.children, child.pattern)
	}
	return nil
}

// Match 从子节点中查找与当前路径匹配的节点，若找不到，则返回 nil
func (n *Node) Match(path string) *Node {
	if len(n.children) == 0 && len(path) == 0 {
		return n
	}

	for _, node := range n.children {
		matched := false
		newPath := path

		switch node.nodeType {
		case ts.TypeBasic:
			matched = strings.HasPrefix(path, node.pattern)
			if matched {
				newPath = path[len(node.pattern):]
			}
		case ts.TypeNamed:
			if node.endpoint {
				matched = true
				newPath = path[:0]
			} else {
				index := strings.Index(path, node.suffix)
				if index > 0 { // 为零说明前面没有命名参数，肯定不正确
					matched = true
					newPath = path[index+len(node.suffix):]
				}
			}
		case ts.TypeRegexp:
			loc := node.expr.FindStringIndex(path)
			if loc != nil && loc[0] == 0 {
				matched = true
				if loc[1] == len(path) {
					newPath = path[:0]
				} else {
					newPath = path[loc[1]+1:]
				}
			}
		default:
			// nodeType 错误，肯定是代码级别的错误，直接 panic
			panic("无效的 nodeType 值")
		}

		if matched {
			// 即使 newPath 为空，也有可能子节点正好可以匹配空的内容。
			// 比如 /posts/{path:\\w*} 后面的 path 即为空节点。
			if nn := node.Match(newPath); nn != nil {
				return nn
			}
			if len(newPath) == 0 && node.handlers != nil && node.handlers.Len() > 0 {
				return node
			}
		}
	} // end for

	return nil
}

// Params 由调用方确保能正常匹配 path
//
// 调用方需确保 path 与 n.Match 中传递的是相同的值，此函数中，不再作验证。
func (n *Node) Params(path string) map[string]string {
	nodes := n.getParents()
	params := make(map[string]string, 10)

LOOP:
	for i := len(nodes) - 1; i >= 0; i-- {
		node := nodes[i]
		switch node.nodeType {
		case ts.TypeBasic:
			path = path[len(node.pattern):]
		case ts.TypeNamed:
			if node.endpoint {
				params[node.name] = path
				break LOOP
			}

			index := strings.Index(path, node.suffix)
			params[node.name] = path[:index]
			path = path[index+len(node.suffix):]
		case ts.TypeRegexp:
			subexps := node.expr.SubexpNames()
			args := node.expr.FindStringSubmatch(path)
			for index, name := range subexps {
				if len(name) > 0 && index < len(args) {
					params[name] = args[index]
				}
			}

			path = path[len(args[0]):]
		}
	}

	return params
}

// URL 根据参数生成地址
func (n *Node) URL(params map[string]string) (string, error) {
	nodes := n.getParents()
	buf := new(bytes.Buffer)

	for i := len(nodes) - 1; i >= 0; i-- {
		node := nodes[i]
		switch node.nodeType {
		case ts.TypeBasic:
			buf.WriteString(node.pattern)
		case ts.TypeNamed:
			param, exists := params[node.name]
			if !exists {
				return "", fmt.Errorf("未找到参数 %s 的值", node.name)
			}
			buf.WriteString(param)
			buf.WriteString(node.suffix) // 如果是 endpoint suffix 肯定为空
		case ts.TypeRegexp:
			url := node.syntaxExpr.String()
			subs := append(node.syntaxExpr.Sub, node.syntaxExpr)
			for _, sub := range subs {
				if len(sub.Name) == 0 {
					continue
				}

				param, exists := params[sub.Name]
				if !exists {
					return "", fmt.Errorf("未找到参数 %v 的值", sub.Name)
				}
				url = strings.Replace(url, sub.String(), param, -1)
			}

			buf.WriteString(url)
		}
	}

	return buf.String(), nil
}

// 逐级向上获取父节点，包含当前节点。
func (n *Node) getParents() []*Node {
	nodes := make([]*Node, 0, 10) // 从尾部向上开始获取节点

	for curr := n; curr != nil; curr = curr.parent {
		nodes = append(nodes, curr)
	}

	return nodes
}

// SetAllow 设置当前节点的 allow 报头
func (n *Node) SetAllow(allow string) {
	if n.handlers == nil {
		n.handlers = handlers.New()
	}

	n.handlers.SetAllow(allow)
}

// Handler 获取该节点下与参数相对应的处理函数
func (n *Node) Handler(method string) http.Handler {
	if n.handlers == nil {
		return nil
	}

	return n.handlers.Handler(method)
}

// 向客户端打印节点的树状结构
func (n *Node) print(deep int) {
	fmt.Println(strings.Repeat(" ", deep*4), n.pattern)

	for _, child := range n.children {
		child.print(deep + 1)
	}
}

// 获取路由数量
func (n *Node) len() int {
	var cnt int
	for _, child := range n.children {
		cnt += child.len()
	}

	if n.handlers != nil && n.handlers.Len() > 0 {
		cnt++
	}

	return cnt
}

func removeNodes(nodes []*Node, pattern string) []*Node {
	lastIndex := len(nodes) - 1
	for index, n := range nodes {
		if n.pattern != pattern {
			continue
		}

		switch {
		case len(nodes) == 1: // 只有一个元素
			return nodes[:0]
		case index == lastIndex: // 最后一个元素
			return nodes[:lastIndex]
		default:
			return append(nodes[:index], nodes[index+1:]...)
		}
	} // end for

	return nodes
}

// 将节点 n 从 pos 位置进行拆分。后一段作为当前段的子节点，并返回当前节点。
// 若 pos 大于或等于 n.pattern 的长度，则直接返回 n 不会拆分。
//
// NOTE: 调用者需确保 pos 位置是可拆分的。
func splitNode(n *Node, pos int) (*Node, error) {
	if len(n.pattern) <= pos { // 不需要拆分
		return n, nil
	}

	p := n.parent
	if p == nil {
		return nil, errors.New("split:节点必须要有一个有效的父节点，才能进行拆分")
	}

	// 先从父节点中删除老的 n
	p.children = removeNodes(p.children, n.pattern)

	ret, err := p.newChild(ts.NewSegment(n.pattern[:pos]))
	if err != nil {
		return nil, err
	}

	c, err := ret.newChild(ts.NewSegment(n.pattern[pos:]))
	if err != nil {
		return nil, err
	}
	c.handlers = n.handlers
	c.children = n.children
	for _, item := range c.children {
		item.parent = c
	}

	return ret, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 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.

package transport

import (
	"io"
	"time"
)

// Request is the low level request representation.
type Request struct {
	Caller    string
	Service   string
	Encoding  Encoding
	Procedure string
	Headers   Headers
	Body      io.Reader
	TTL       time.Duration
}

// Encoding represents an encoding format for requests.
type Encoding string
<commit_msg>transport/Request: more docs<commit_after>// Copyright (c) 2016 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.

package transport

import (
	"io"
	"time"
)

// Request is the low level request representation.
type Request struct {
	// Name of the service making the request.
	Caller string

	// Name of the service to which the request is being made.
	Service string

	// Name of the encoding used for the request body.
	Encoding Encoding

	// Amount of time within which this request must finish.
	TTL time.Duration

	// Name of the procedure being called.
	Procedure string

	// Headers for the request.
	Headers Headers

	// Request payload.
	Body io.Reader
}

// Encoding represents an encoding format for requests.
type Encoding string
<|endoftext|>