<commit_before>package prompts

import (
	"fmt"
	"sort"
	"strings"

	"github.com/asaskevich/govalidator"
	"github.com/manifoldco/go-manifold"
	"github.com/manifoldco/torus-cli/promptui"
	"github.com/rhymond/go-money"
	"github.com/stripe/stripe-go"
	"github.com/stripe/stripe-go/token"

	"github.com/manifoldco/manifold-cli/errs"

	cModels "github.com/manifoldco/manifold-cli/generated/catalog/models"
	mModels "github.com/manifoldco/manifold-cli/generated/marketplace/models"
)

const namePattern = "^[a-zA-Z\\s,\\.'\\-pL]{1,64}$"

// NumberMask is the character used to mask number inputs
const NumberMask = '#'

var errBad = promptui.NewValidationError("Bad Value")

type plansSortByCost []*cModels.Plan

func (p plansSortByCost) Len() int {
	return len(p)
}

func (p plansSortByCost) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func (p plansSortByCost) Less(i, j int) bool {
	return *p[i].Body.Cost < *p[j].Body.Cost
}

type productsSortByName []*cModels.Product

func (p productsSortByName) Len() int {
	return len(p)
}

func (p productsSortByName) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func (p productsSortByName) Less(i, j int) bool {
	return strings.Compare(
		strings.ToLower(fmt.Sprintf("%s", p[i].Body.Name)),
		strings.ToLower(fmt.Sprintf("%s", p[j].Body.Name)),
	) < 0
}

// SelectProduct prompts the user to select a product from the given list.
func SelectProduct(products []*cModels.Product, label string) (int, string, error) {
	line := func(p *cModels.Product) string {
		return fmt.Sprintf("%s (%s)", p.Body.Name, p.Body.Label)
	}

	var idx int
	if label != "" {
		found := false
		for i, p := range products {
			if string(p.Body.Label) == label {
				idx = i
				found = true
				break
			}
		}

		if !found {
			fmt.Println(promptui.FailedValue("Product", label))
			return 0, "", errs.ErrProductNotFound
		}

		fmt.Println(promptui.SuccessfulValue("Product", line(products[idx])))
		return idx, label, nil
	}

	sort.Sort(productsSortByName(products))
	labels := make([]string, len(products))
	for i, p := range products {
		labels[i] = line(p)
	}

	prompt := promptui.Select{
		Label: "Select Product",
		Items: labels,
	}

	return prompt.Run()
}

// SelectPlan prompts the user to select a plan from the given list.
func SelectPlan(plans []*cModels.Plan, label string, filterLabelTop bool) (int, string, error) {
	line := func(p *cModels.Plan) string {
		return fmt.Sprintf("%s (%s) - %s", p.Body.Name, p.Body.Label, getPlanCost(p))
	}

	var idx int
	var fp *cModels.Plan
	if label != "" {
		found := false
		for i, p := range plans {
			if string(p.Body.Label) == label {
				idx = i
				found = true
				break
			}
		}

		fp = plans[idx]
		if !found {
			fmt.Println(promptui.FailedValue("Plan", label))
			return 0, "", errs.ErrPlanNotFound
		}

		if !filterLabelTop {
			fmt.Println(promptui.SuccessfulValue("Plan", line(fp)))
			return idx, label, nil
		}
	}

	sort.Sort(plansSortByCost(plans))
	labels := make([]string, len(plans))

	var selectedIdx int
	for i, p := range plans {
		labels[i] = line(p)
		if p == fp {
			selectedIdx = i
		}
	}

	if filterLabelTop {
		labels[0], labels[selectedIdx] = labels[selectedIdx], labels[0]
	}

	prompt := promptui.Select{
		Label: "Select Plan",
		Items: labels,
	}

	return prompt.Run()
}

// SelectResource promps the user to select a provisioned resource from the given list
func SelectResource(resources []*mModels.Resource, label string) (int, string, error) {
	line := func(r *mModels.Resource) string {
		return fmt.Sprintf("%s (%s) %s", r.Body.Name, r.Body.Label, r.Body.AppName)
	}

	var idx int
	if label != "" {
		found := false
		for i, p := range resources {
			if string(p.Body.Label) == label {
				idx = i
				found = true
				break
			}
		}

		r := resources[idx]
		if !found {
			fmt.Println(promptui.FailedValue("Resource", label))
			return 0, "", errs.ErrResourceNotFound
		}

		fmt.Println(promptui.SuccessfulValue("Resource", line(r)))
		return idx, label, nil
	}

	labels := make([]string, len(resources))
	for i, r := range resources {
		labels[i] = line(r)
	}

	prompt := promptui.Select{
		Label: "Select Resource",
		Items: labels,
	}

	return prompt.Run()
}

// SelectRegion prompts the user to select a region from the given list.
func SelectRegion(regions []*cModels.Region) (int, string, error) {
	line := func(r *cModels.Region) string {
		return fmt.Sprintf("%s (%s::%s)", r.Body.Name, *r.Body.Platform, *r.Body.Location)
	}

	labels := make([]string, len(regions))
	for i, r := range regions {
		labels[i] = line(r)
	}

	// TODO: Build "auto" resolve into promptui in case of only one item
	if len(regions) == 1 {
		fmt.Println(promptui.SuccessfulValue("Region", line(regions[0])))
		return 0, string(regions[0].Body.Name), nil
	}

	prompt := promptui.Select{
		Label: "Select Region",
		Items: labels,
	}

	return prompt.Run()
}

// SelectCreateAppName prompts the user to select or create an application
// name, a -1 idx will be returned if the app name requires creation.
func SelectCreateAppName(names []string, label string, filterToTop bool) (int, string, error) {
	labels := make([]string, len(names))

	var idx int
	for i, n := range names {
		labels[i] = n

		if label != "" && labels[i] == label {
			if !filterToTop {
				fmt.Println(promptui.SuccessfulValue("App Name", label))
				return i, label, nil
			}

			idx = i
		}
	}

	if label != "" && !filterToTop {
		prompt := promptui.Prompt{
			Label:   "App Name",
			Default: label,
		}

		value, err := prompt.Run()
		return -1, value, err
	}

	if filterToTop {
		labels[0], labels[idx] = labels[idx], labels[0]
	}

	prompt := promptui.SelectWithAdd{
		Label:    "App Name",
		Items:    labels,
		AddLabel: "Create a New App",
		Validate: func(input string) error {
			n := manifold.Name(input)
			if err := n.Validate(nil); err != nil {
				return promptui.NewValidationError("Please provide a valid App Name")
			}

			return nil
		},
	}

	return prompt.Run()
}

// ResourceName prompts the user to provide a resource name or to accept empty
// to let the system generate one.
func ResourceName(defaultValue string, autoSelect bool) (string, error) {
	validate := func(input string) error {
		if len(input) == 0 {
			return nil
		}

		n := manifold.Name(input)
		if err := n.Validate(nil); err != nil {
			return promptui.NewValidationError("Please provide a valid App Name")
		}

		return nil
	}

	label := "Resource Name (one will be generated if left blank)"

	if autoSelect {
		err := validate(defaultValue)
		if err != nil {
			fmt.Println(promptui.FailedValue(label, defaultValue))
		} else {
			fmt.Println(promptui.SuccessfulValue(label, defaultValue))
		}
		return defaultValue, err
	}

	p := promptui.Prompt{
		Label:    label,
		Default:  defaultValue,
		Validate: validate,
	}

	return p.Run()
}

// ResourceLabel prompts the user to provide a label name
func ResourceLabel(defaultValue string, autoSelect bool) (string, error) {
	validate := func(input string) error {
		if len(input) == 0 {
			return promptui.NewValidationError("Please provide a resource label")
		}

		l := manifold.Label(input)
		if err := l.Validate(nil); err != nil {
			return promptui.NewValidationError("Please provide a valid resource label")
		}

		return nil
	}

	label := "Resource Label"

	if autoSelect {
		err := validate(defaultValue)
		if err != nil {
			fmt.Println(promptui.FailedValue(label, defaultValue))
		} else {
			fmt.Println(promptui.SuccessfulValue(label, defaultValue))
		}

		return defaultValue, err
	}

	p := promptui.Prompt{
		Label:    label,
		Default:  defaultValue,
		Validate: validate,
	}

	return p.Run()
}

// TeamName prompts the user to enter a new Team name
func TeamName(defaultValue string, autoSelect bool) (string, error) {
	validate := func(input string) error {
		if len(input) == 0 {
			return promptui.NewValidationError("Please provide a valid team name")
		}

		l := manifold.Name(input)
		if err := l.Validate(nil); err != nil {
			return promptui.NewValidationError("Please provide a valid team name")
		}

		return nil
	}

	label := "Team Name"

	if autoSelect {
		err := validate(defaultValue)
		if err != nil {
			fmt.Println(promptui.FailedValue(label, defaultValue))
		} else {
			fmt.Println(promptui.SuccessfulValue(label, defaultValue))
		}
		return defaultValue, err
	}

	p := promptui.Prompt{
		Label:    label,
		Default:  defaultValue,
		Validate: validate,
	}

	return p.Run()
}

// Email prompts the user to provide an email *or* accepted the default
// email value
func Email(defaultValue string) (string, error) {
	p := promptui.Prompt{
		Label: "Email",
		Validate: func(input string) error {
			valid := govalidator.IsEmail(input)
			if valid {
				return nil
			}

			return promptui.NewValidationError("Please enter a valid email address")
		},
	}

	if defaultValue != "" {
		p.Default = defaultValue
	}

	return p.Run()
}

// FullName prompts the user to input a person's name
func FullName(defaultValue string) (string, error) {
	p := promptui.Prompt{
		Label: "Name",
		Validate: func(input string) error {
			if govalidator.StringMatches(input, namePattern) {
				return nil
			}
			return promptui.NewValidationError("Please enter a valid name")
		},
	}
	if defaultValue != "" {
		p.Default = defaultValue
	}

	return p.Run()
}

// PasswordMask is the character used to mask password inputs
const PasswordMask = '●'

// Password prompts the user to input a password value
func Password() (string, error) {
	prompt := promptui.Prompt{
		Label: "Password",
		Mask:  PasswordMask,
		Validate: func(input string) error {
			if len(input) < 8 {
				return promptui.NewValidationError(
					"Passwords must be greater than 8 characters")
			}

			return nil
		},
	}

	return prompt.Run()
}

// Confirm is a confirmation prompt
func Confirm(msg string) (string, error) {
	p := promptui.Prompt{
		Label:     msg,
		IsConfirm: true,
	}

	return p.Run()
}

// HandleSelectError returns a cli error if the error is not an EOF or
// Interrupt
func HandleSelectError(err error, generic string) error {
	if err == promptui.ErrEOF || err == promptui.ErrInterrupt {
		return err
	}

	return errs.NewErrorExitError(generic, err)
}

func getPlanCost(p *cModels.Plan) string {
	if p.Body.Cost == nil {
		return "Free!"
	}

	c := *p.Body.Cost
	if c == 0 {
		return "Free!"
	}

	return money.New(c, "USD").Display()
}

func isCard(raw string) error {
	if govalidator.StringLength(raw, "16", "16") && govalidator.IsNumeric(raw) {
		return nil
	}

	return errBad
}

func isExpiry(raw string) error {
	if govalidator.StringLength(raw, "5", "5") {
		return nil
	}

	return errBad
}

func isCVV(raw string) error {
	if govalidator.StringLength(raw, "3", "3") && govalidator.IsNumeric(raw) {
		return nil
	}

	return errBad
}

// CreditCard handles receiving and tokenizing payment information
func CreditCard() (*stripe.Token, error) {
	rCrd, err := (&promptui.Prompt{
		Label:    "💳  Card Number",
		Validate: isCard,
	}).Run()
	if err != nil {
		return nil, err
	}

	rExp, err := (&promptui.Prompt{
		Label:    "📅  Expiry (MM/YY)",
		Validate: isExpiry,
	}).Run()
	if err != nil {
		return nil, err
	}

	rCVV, err := (&promptui.Prompt{
		Label:    "🔒  CVV",
		Mask:     NumberMask,
		Validate: isCVV,
	}).Run()
	if err != nil {
		return nil, err
	}

	parts := strings.Split(rExp, "/")
	year, month := "20"+parts[1], parts[0]
	tkn, err := token.New(&stripe.TokenParams{Card: &stripe.CardParams{
		Number: rCrd,
		Month:  month,
		Year:   year,
		CVC:    rCVV,
	}})
	if err != nil {
		return nil, errs.NewStripeError(err)
	}

	return tkn, nil
}
<commit_msg>Add SelectTeam prompt for team selection<commit_after>package prompts

import (
	"fmt"
	"sort"
	"strings"

	"github.com/asaskevich/govalidator"
	"github.com/manifoldco/go-manifold"
	"github.com/manifoldco/torus-cli/promptui"
	"github.com/rhymond/go-money"
	"github.com/stripe/stripe-go"
	"github.com/stripe/stripe-go/token"

	"github.com/manifoldco/manifold-cli/errs"

	cModels "github.com/manifoldco/manifold-cli/generated/catalog/models"
	iModels "github.com/manifoldco/manifold-cli/generated/identity/models"
	mModels "github.com/manifoldco/manifold-cli/generated/marketplace/models"
)

const namePattern = "^[a-zA-Z\\s,\\.'\\-pL]{1,64}$"

// NumberMask is the character used to mask number inputs
const NumberMask = '#'

var errBad = promptui.NewValidationError("Bad Value")

type plansSortByCost []*cModels.Plan

func (p plansSortByCost) Len() int {
	return len(p)
}

func (p plansSortByCost) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func (p plansSortByCost) Less(i, j int) bool {
	return *p[i].Body.Cost < *p[j].Body.Cost
}

type productsSortByName []*cModels.Product

func (p productsSortByName) Len() int {
	return len(p)
}

func (p productsSortByName) Swap(i, j int) {
	p[i], p[j] = p[j], p[i]
}

func (p productsSortByName) Less(i, j int) bool {
	return strings.Compare(
		strings.ToLower(fmt.Sprintf("%s", p[i].Body.Name)),
		strings.ToLower(fmt.Sprintf("%s", p[j].Body.Name)),
	) < 0
}

// SelectProduct prompts the user to select a product from the given list.
func SelectProduct(products []*cModels.Product, label string) (int, string, error) {
	line := func(p *cModels.Product) string {
		return fmt.Sprintf("%s (%s)", p.Body.Name, p.Body.Label)
	}

	var idx int
	if label != "" {
		found := false
		for i, p := range products {
			if string(p.Body.Label) == label {
				idx = i
				found = true
				break
			}
		}

		if !found {
			fmt.Println(promptui.FailedValue("Product", label))
			return 0, "", errs.ErrProductNotFound
		}

		fmt.Println(promptui.SuccessfulValue("Product", line(products[idx])))
		return idx, label, nil
	}

	sort.Sort(productsSortByName(products))
	labels := make([]string, len(products))
	for i, p := range products {
		labels[i] = line(p)
	}

	prompt := promptui.Select{
		Label: "Select Product",
		Items: labels,
	}

	return prompt.Run()
}

// SelectPlan prompts the user to select a plan from the given list.
func SelectPlan(plans []*cModels.Plan, label string, filterLabelTop bool) (int, string, error) {
	line := func(p *cModels.Plan) string {
		return fmt.Sprintf("%s (%s) - %s", p.Body.Name, p.Body.Label, getPlanCost(p))
	}

	var idx int
	var fp *cModels.Plan
	if label != "" {
		found := false
		for i, p := range plans {
			if string(p.Body.Label) == label {
				idx = i
				found = true
				break
			}
		}

		fp = plans[idx]
		if !found {
			fmt.Println(promptui.FailedValue("Plan", label))
			return 0, "", errs.ErrPlanNotFound
		}

		if !filterLabelTop {
			fmt.Println(promptui.SuccessfulValue("Plan", line(fp)))
			return idx, label, nil
		}
	}

	sort.Sort(plansSortByCost(plans))
	labels := make([]string, len(plans))

	var selectedIdx int
	for i, p := range plans {
		labels[i] = line(p)
		if p == fp {
			selectedIdx = i
		}
	}

	if filterLabelTop {
		labels[0], labels[selectedIdx] = labels[selectedIdx], labels[0]
	}

	prompt := promptui.Select{
		Label: "Select Plan",
		Items: labels,
	}

	return prompt.Run()
}

// SelectResource promps the user to select a provisioned resource from the given list
func SelectResource(resources []*mModels.Resource, label string) (int, string, error) {
	line := func(r *mModels.Resource) string {
		return fmt.Sprintf("%s (%s) %s", r.Body.Name, r.Body.Label, r.Body.AppName)
	}

	var idx int
	if label != "" {
		found := false
		for i, p := range resources {
			if string(p.Body.Label) == label {
				idx = i
				found = true
				break
			}
		}

		r := resources[idx]
		if !found {
			fmt.Println(promptui.FailedValue("Resource", label))
			return 0, "", errs.ErrResourceNotFound
		}

		fmt.Println(promptui.SuccessfulValue("Resource", line(r)))
		return idx, label, nil
	}

	labels := make([]string, len(resources))
	for i, r := range resources {
		labels[i] = line(r)
	}

	prompt := promptui.Select{
		Label: "Select Resource",
		Items: labels,
	}

	return prompt.Run()
}

// SelectRegion prompts the user to select a region from the given list.
func SelectRegion(regions []*cModels.Region) (int, string, error) {
	line := func(r *cModels.Region) string {
		return fmt.Sprintf("%s (%s::%s)", r.Body.Name, *r.Body.Platform, *r.Body.Location)
	}

	labels := make([]string, len(regions))
	for i, r := range regions {
		labels[i] = line(r)
	}

	// TODO: Build "auto" resolve into promptui in case of only one item
	if len(regions) == 1 {
		fmt.Println(promptui.SuccessfulValue("Region", line(regions[0])))
		return 0, string(regions[0].Body.Name), nil
	}

	prompt := promptui.Select{
		Label: "Select Region",
		Items: labels,
	}

	return prompt.Run()
}

// SelectTeam prompts the user to select a team from the given list
func SelectTeam(teams []*iModels.Team, label string) (int, string, error) {
	line := func(t *iModels.Team) string {
		return fmt.Sprintf("%s (%s)", t.Body.Name, t.Body.Label)
	}

	var idx int
	if label != "" {
		found := false
		for i, t := range teams {
			if string(t.Body.Label) == label {
				idx = i
				found = true
				break
			}
		}

		t := teams[idx]

		if !found {
			fmt.Println(promptui.FailedValue("Team", label))
			return 0, "", errs.ErrTeamNotFound
		}

		fmt.Println(promptui.SuccessfulValue("Team", line(t)))
		return idx, label, nil
	}

	labels := make([]string, len(teams))
	for i, t := range teams {
		labels[i] = line(t)
	}

	prompt := promptui.Select{
		Label: "Select Team",
		Items: labels,
	}

	return prompt.Run()
}

// SelectCreateAppName prompts the user to select or create an application
// name, a -1 idx will be returned if the app name requires creation.
func SelectCreateAppName(names []string, label string, filterToTop bool) (int, string, error) {
	labels := make([]string, len(names))

	var idx int
	for i, n := range names {
		labels[i] = n

		if label != "" && labels[i] == label {
			if !filterToTop {
				fmt.Println(promptui.SuccessfulValue("App Name", label))
				return i, label, nil
			}

			idx = i
		}
	}

	if label != "" && !filterToTop {
		prompt := promptui.Prompt{
			Label:   "App Name",
			Default: label,
		}

		value, err := prompt.Run()
		return -1, value, err
	}

	if filterToTop {
		labels[0], labels[idx] = labels[idx], labels[0]
	}

	prompt := promptui.SelectWithAdd{
		Label:    "App Name",
		Items:    labels,
		AddLabel: "Create a New App",
		Validate: func(input string) error {
			n := manifold.Name(input)
			if err := n.Validate(nil); err != nil {
				return promptui.NewValidationError("Please provide a valid App Name")
			}

			return nil
		},
	}

	return prompt.Run()
}

// ResourceName prompts the user to provide a resource name or to accept empty
// to let the system generate one.
func ResourceName(defaultValue string, autoSelect bool) (string, error) {
	validate := func(input string) error {
		if len(input) == 0 {
			return nil
		}

		n := manifold.Name(input)
		if err := n.Validate(nil); err != nil {
			return promptui.NewValidationError("Please provide a valid App Name")
		}

		return nil
	}

	label := "Resource Name (one will be generated if left blank)"

	if autoSelect {
		err := validate(defaultValue)
		if err != nil {
			fmt.Println(promptui.FailedValue(label, defaultValue))
		} else {
			fmt.Println(promptui.SuccessfulValue(label, defaultValue))
		}
		return defaultValue, err
	}

	p := promptui.Prompt{
		Label:    label,
		Default:  defaultValue,
		Validate: validate,
	}

	return p.Run()
}

// ResourceLabel prompts the user to provide a label name
func ResourceLabel(defaultValue string, autoSelect bool) (string, error) {
	validate := func(input string) error {
		if len(input) == 0 {
			return promptui.NewValidationError("Please provide a resource label")
		}

		l := manifold.Label(input)
		if err := l.Validate(nil); err != nil {
			return promptui.NewValidationError("Please provide a valid resource label")
		}

		return nil
	}

	label := "Resource Label"

	if autoSelect {
		err := validate(defaultValue)
		if err != nil {
			fmt.Println(promptui.FailedValue(label, defaultValue))
		} else {
			fmt.Println(promptui.SuccessfulValue(label, defaultValue))
		}

		return defaultValue, err
	}

	p := promptui.Prompt{
		Label:    label,
		Default:  defaultValue,
		Validate: validate,
	}

	return p.Run()
}

// TeamName prompts the user to enter a new Team name
func TeamName(defaultValue string, autoSelect bool) (string, error) {
	validate := func(input string) error {
		if len(input) == 0 {
			return promptui.NewValidationError("Please provide a valid team name")
		}

		l := manifold.Name(input)
		if err := l.Validate(nil); err != nil {
			return promptui.NewValidationError("Please provide a valid team name")
		}

		return nil
	}

	label := "Team Name"

	if autoSelect {
		err := validate(defaultValue)
		if err != nil {
			fmt.Println(promptui.FailedValue(label, defaultValue))
		} else {
			fmt.Println(promptui.SuccessfulValue(label, defaultValue))
		}
		return defaultValue, err
	}

	p := promptui.Prompt{
		Label:    label,
		Default:  defaultValue,
		Validate: validate,
	}

	return p.Run()
}

// Email prompts the user to provide an email *or* accepted the default
// email value
func Email(defaultValue string) (string, error) {
	p := promptui.Prompt{
		Label: "Email",
		Validate: func(input string) error {
			valid := govalidator.IsEmail(input)
			if valid {
				return nil
			}

			return promptui.NewValidationError("Please enter a valid email address")
		},
	}

	if defaultValue != "" {
		p.Default = defaultValue
	}

	return p.Run()
}

// FullName prompts the user to input a person's name
func FullName(defaultValue string) (string, error) {
	p := promptui.Prompt{
		Label: "Name",
		Validate: func(input string) error {
			if govalidator.StringMatches(input, namePattern) {
				return nil
			}
			return promptui.NewValidationError("Please enter a valid name")
		},
	}
	if defaultValue != "" {
		p.Default = defaultValue
	}

	return p.Run()
}

// PasswordMask is the character used to mask password inputs
const PasswordMask = '●'

// Password prompts the user to input a password value
func Password() (string, error) {
	prompt := promptui.Prompt{
		Label: "Password",
		Mask:  PasswordMask,
		Validate: func(input string) error {
			if len(input) < 8 {
				return promptui.NewValidationError(
					"Passwords must be greater than 8 characters")
			}

			return nil
		},
	}

	return prompt.Run()
}

// Confirm is a confirmation prompt
func Confirm(msg string) (string, error) {
	p := promptui.Prompt{
		Label:     msg,
		IsConfirm: true,
	}

	return p.Run()
}

// HandleSelectError returns a cli error if the error is not an EOF or
// Interrupt
func HandleSelectError(err error, generic string) error {
	if err == promptui.ErrEOF || err == promptui.ErrInterrupt {
		return err
	}

	return errs.NewErrorExitError(generic, err)
}

func getPlanCost(p *cModels.Plan) string {
	if p.Body.Cost == nil {
		return "Free!"
	}

	c := *p.Body.Cost
	if c == 0 {
		return "Free!"
	}

	return money.New(c, "USD").Display()
}

func isCard(raw string) error {
	if govalidator.StringLength(raw, "16", "16") && govalidator.IsNumeric(raw) {
		return nil
	}

	return errBad
}

func isExpiry(raw string) error {
	if govalidator.StringLength(raw, "5", "5") {
		return nil
	}

	return errBad
}

func isCVV(raw string) error {
	if govalidator.StringLength(raw, "3", "3") && govalidator.IsNumeric(raw) {
		return nil
	}

	return errBad
}

// CreditCard handles receiving and tokenizing payment information
func CreditCard() (*stripe.Token, error) {
	rCrd, err := (&promptui.Prompt{
		Label:    "💳  Card Number",
		Validate: isCard,
	}).Run()
	if err != nil {
		return nil, err
	}

	rExp, err := (&promptui.Prompt{
		Label:    "📅  Expiry (MM/YY)",
		Validate: isExpiry,
	}).Run()
	if err != nil {
		return nil, err
	}

	rCVV, err := (&promptui.Prompt{
		Label:    "🔒  CVV",
		Mask:     NumberMask,
		Validate: isCVV,
	}).Run()
	if err != nil {
		return nil, err
	}

	parts := strings.Split(rExp, "/")
	year, month := "20"+parts[1], parts[0]
	tkn, err := token.New(&stripe.TokenParams{Card: &stripe.CardParams{
		Number: rCrd,
		Month:  month,
		Year:   year,
		CVC:    rCVV,
	}})
	if err != nil {
		return nil, errs.NewStripeError(err)
	}

	return tkn, nil
}
<|endoftext|>


<commit_before>package ndpcmd

import (
	"context"
	"fmt"
	"log"
	"net"
	"os"
	"time"

	"github.com/mdlayher/ndp"
)

func sendRS(ctx context.Context, c *ndp.Conn, addr net.HardwareAddr) error {
	ll := log.New(os.Stderr, "ndp rs> ", 0)

	ll.Printf("router solicitation:\n    - source link-layer address: %s", addr.String())

	m := &ndp.RouterSolicitation{
		Options: []ndp.Option{
			&ndp.LinkLayerAddress{
				Direction: ndp.Source,
				Addr:      addr,
			},
		},
	}

	for i := 0; ; i++ {
		if err := c.WriteTo(m, nil, net.IPv6linklocalallrouters); err != nil {
			return fmt.Errorf("failed to write router solicitation: %v", err)
		}

		ra, from, err := receiveRA(c)
		if err == nil {
			fmt.Println()
			printRA(ll, ra, from)
			return nil
		}

		// Was the context canceled already?
		select {
		case <-ctx.Done():
			fmt.Println()
			ll.Printf("sent %d router solicitation(s)", i+1)
			return ctx.Err()
		default:
		}

		// Was the error caused by a read timeout, and should the loop continue?
		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
			fmt.Print(".")
			continue
		}

		return fmt.Errorf("failed to read router advertisement: %v", err)
	}
}

func receiveRA(c *ndp.Conn) (*ndp.RouterAdvertisement, net.IP, error) {
	if err := c.SetReadDeadline(time.Now().Add(1 * time.Second)); err != nil {
		return nil, nil, err
	}

	for {
		msg, _, from, err := c.ReadFrom()
		if err != nil {
			return nil, nil, err
		}

		ra, ok := msg.(*ndp.RouterAdvertisement)
		if !ok {
			continue
		}

		return ra, from, nil
	}
}

func printRA(ll *log.Logger, ra *ndp.RouterAdvertisement, from net.IP) {
	var opts string
	for _, o := range ra.Options {
		opts += fmt.Sprintf("        - %s\n", optStr(o))
	}

	var flags string
	if ra.ManagedConfiguration {
		flags += "M"
	}
	if ra.OtherConfiguration {
		flags += "O"
	}
	if ra.MobileIPv6HomeAgent {
		flags += "H"
	}
	if ra.NeighborDiscoveryProxy {
		flags += "P"
	}

	ll.Printf(
		raFormat,
		from.String(),
		ra.CurrentHopLimit,
		flags,
		ra.RouterSelectionPreference,
		ra.RouterLifetime,
		ra.ReachableTime,
		ra.RetransmitTimer,
		opts,
	)
}

const raFormat = `router advertisement from: %s:
    - hop limit:        %d
    - flags:            [%s]
    - preference:       %d
    - router lifetime:  %s
    - reachable time:   %s
    - retransmit timer: %s
    - options:
%s`

func optStr(o ndp.Option) string {
	switch o := o.(type) {
	case *ndp.LinkLayerAddress:
		dir := "source"
		if o.Direction == ndp.Target {
			dir = "target"
		}

		return fmt.Sprintf("%s link-layer address: %s", dir, o.Addr.String())
	case *ndp.MTU:
		return fmt.Sprintf("MTU: %d", *o)
	case *ndp.PrefixInformation:
		var flags string
		if o.OnLink {
			flags += "O"
		}
		if o.AutonomousAddressConfiguration {
			flags += "A"
		}

		return fmt.Sprintf("prefix information: %s/%d, flags: [%s], valid: %s, preferred: %s",
			o.Prefix.String(),
			o.PrefixLength,
			flags,
			o.ValidLifetime,
			o.PreferredLifetime,
		)
	case *ndp.RawOption:
		return fmt.Sprintf("type: %03d, value: %v", o.Type, o.Value)
	default:
		panic(fmt.Sprintf("unrecognized option: %v", o))
	}
}

func printNA(ll *log.Logger, na *ndp.NeighborAdvertisement, from net.IP) {
	var opts string
	for _, o := range na.Options {
		opts += fmt.Sprintf("        - %s\n", optStr(o))
	}

	ll.Printf(
		naFormat,
		from.String(),
		na.Router,
		na.Solicited,
		na.Override,
		na.TargetAddress.String(),
		opts,
	)
}

const naFormat = `neighbor advertisement from %s:
    - router:         %t
    - solicited:      %t
    - override:       %t
    - target address: %s
    - options:
%s`
<commit_msg>internal/ndpcmd: implement RDNSS option printing<commit_after>package ndpcmd

import (
	"context"
	"fmt"
	"log"
	"net"
	"os"
	"strings"
	"time"

	"github.com/mdlayher/ndp"
)

func sendRS(ctx context.Context, c *ndp.Conn, addr net.HardwareAddr) error {
	ll := log.New(os.Stderr, "ndp rs> ", 0)

	ll.Printf("router solicitation:\n    - source link-layer address: %s", addr.String())

	m := &ndp.RouterSolicitation{
		Options: []ndp.Option{
			&ndp.LinkLayerAddress{
				Direction: ndp.Source,
				Addr:      addr,
			},
		},
	}

	for i := 0; ; i++ {
		if err := c.WriteTo(m, nil, net.IPv6linklocalallrouters); err != nil {
			return fmt.Errorf("failed to write router solicitation: %v", err)
		}

		ra, from, err := receiveRA(c)
		if err == nil {
			fmt.Println()
			printRA(ll, ra, from)
			return nil
		}

		// Was the context canceled already?
		select {
		case <-ctx.Done():
			fmt.Println()
			ll.Printf("sent %d router solicitation(s)", i+1)
			return ctx.Err()
		default:
		}

		// Was the error caused by a read timeout, and should the loop continue?
		if nerr, ok := err.(net.Error); ok && nerr.Timeout() {
			fmt.Print(".")
			continue
		}

		return fmt.Errorf("failed to read router advertisement: %v", err)
	}
}

func receiveRA(c *ndp.Conn) (*ndp.RouterAdvertisement, net.IP, error) {
	if err := c.SetReadDeadline(time.Now().Add(1 * time.Second)); err != nil {
		return nil, nil, err
	}

	for {
		msg, _, from, err := c.ReadFrom()
		if err != nil {
			return nil, nil, err
		}

		ra, ok := msg.(*ndp.RouterAdvertisement)
		if !ok {
			continue
		}

		return ra, from, nil
	}
}

func printRA(ll *log.Logger, ra *ndp.RouterAdvertisement, from net.IP) {
	var opts string
	for _, o := range ra.Options {
		opts += fmt.Sprintf("        - %s\n", optStr(o))
	}

	var flags string
	if ra.ManagedConfiguration {
		flags += "M"
	}
	if ra.OtherConfiguration {
		flags += "O"
	}
	if ra.MobileIPv6HomeAgent {
		flags += "H"
	}
	if ra.NeighborDiscoveryProxy {
		flags += "P"
	}

	ll.Printf(
		raFormat,
		from.String(),
		ra.CurrentHopLimit,
		flags,
		ra.RouterSelectionPreference,
		ra.RouterLifetime,
		ra.ReachableTime,
		ra.RetransmitTimer,
		opts,
	)
}

const raFormat = `router advertisement from: %s:
    - hop limit:        %d
    - flags:            [%s]
    - preference:       %d
    - router lifetime:  %s
    - reachable time:   %s
    - retransmit timer: %s
    - options:
%s`

func optStr(o ndp.Option) string {
	switch o := o.(type) {
	case *ndp.LinkLayerAddress:
		dir := "source"
		if o.Direction == ndp.Target {
			dir = "target"
		}

		return fmt.Sprintf("%s link-layer address: %s", dir, o.Addr.String())
	case *ndp.MTU:
		return fmt.Sprintf("MTU: %d", *o)
	case *ndp.PrefixInformation:
		var flags string
		if o.OnLink {
			flags += "O"
		}
		if o.AutonomousAddressConfiguration {
			flags += "A"
		}

		return fmt.Sprintf("prefix information: %s/%d, flags: [%s], valid: %s, preferred: %s",
			o.Prefix.String(),
			o.PrefixLength,
			flags,
			o.ValidLifetime,
			o.PreferredLifetime,
		)
	case *ndp.RawOption:
		return fmt.Sprintf("type: %03d, value: %v", o.Type, o.Value)
	case *ndp.RecursiveDNSServer:
		var ss []string
		for _, s := range o.Servers {
			ss = append(ss, s.String())
		}
		servers := strings.Join(ss, ", ")

		return fmt.Sprintf("recursive DNS servers: lifetime: %s, servers: %s", o.Lifetime, servers)
	default:
		panic(fmt.Sprintf("unrecognized option: %v", o))
	}
}

func printNA(ll *log.Logger, na *ndp.NeighborAdvertisement, from net.IP) {
	var opts string
	for _, o := range na.Options {
		opts += fmt.Sprintf("        - %s\n", optStr(o))
	}

	ll.Printf(
		naFormat,
		from.String(),
		na.Router,
		na.Solicited,
		na.Override,
		na.TargetAddress.String(),
		opts,
	)
}

const naFormat = `neighbor advertisement from %s:
    - router:         %t
    - solicited:      %t
    - override:       %t
    - target address: %s
    - options:
%s`
<|endoftext|>


<commit_before>// Package adhier provides an algorithm for adaptive hierarchical interpolation
// with local refinements.
package adhier

import (
	"math"
)

// Grid is the interface that an sparse grid should satisfy in order to be used
// in the algorithm.
type Grid interface {
	Dimensions() uint16
	ComputeNodes(levels []uint8, orders []uint32) []float64
	ComputeChildren(levels []uint8, orders []uint32) ([]uint8, []uint32)
}

// Basis is the interface that a functional basis should satisfy in order to be
// used in the algorithm.
type Basis interface {
	Evaluate(levels []uint8, orders []uint32, point []float64) float64
}

// Self represents a particular instantiation of the algorithm.
type Self struct {
	grid   Grid
	basis  Basis
	config Config

	ic uint16
	oc uint16
}

// New creates an instance of the algorithm for the given configuration.
func New(grid Grid, basis Basis, config Config, outputs uint16) *Self {
	return &Self{
		grid:   grid,
		basis:  basis,
		config: config,

		ic: grid.Dimensions(),
		oc: outputs,
	}
}

// Compute takes a function and yields a surrogate for it, which can be further
// fed to Evaluate for the actual interpolation.
func (self *Self) Compute(target func([]float64) []float64) *Surrogate {
	ic := uint32(self.ic)
	oc := uint32(self.oc)

	surrogate := new(Surrogate)
	surrogate.initialize(uint16(ic), uint16(oc))

	// Assume level 0 has only one node, and its order is 0.
	level := uint8(0)
	newc := uint32(1)
	levels := make([]uint8, newc*ic)
	orders := make([]uint32, newc*ic)

	oldc := uint32(0)
	nc := uint32(0)

	var i, j, k, l uint32

	value := make([]float64, oc)

	minValue := make([]float64, oc)
	maxValue := make([]float64, oc)

	minValue[0] = math.Inf(1)
	maxValue[0] = math.Inf(-1)
	for i = 1; i < oc; i++ {
		minValue[i] = minValue[i-1]
		maxValue[i] = maxValue[i-1]
	}

	for {
		surrogate.resize(oldc + newc)

		copy(surrogate.levels[oldc*ic:], levels)
		copy(surrogate.orders[oldc*ic:], orders)

		nodes := self.grid.ComputeNodes(levels, orders)
		values := target(nodes)

		// Compute the surpluses corresponding to the active nodes.
		for i, k = 0, oldc*oc; i < newc; i++ {
			evaluate(self.basis, surrogate, ic, oc, oldc,
				nodes[i*ic:(i+1)*ic], value)

			for j = 0; j < oc; j++ {
				surrogate.surpluses[k] = values[i*oc+j] - value[j]
				k++
			}
		}

		nc += newc

		if level >= self.config.MaxLevel {
			break
		}

		// Keep track of the maximal and minimal values of the function.
		for i, k = 0, 0; i < newc; i++ {
			for j = 0; j < oc; j++ {
				if values[k] < minValue[j] {
					minValue[j] = values[k]
				}
				if values[k] > maxValue[j] {
					maxValue[j] = values[k]
				}
				k++
			}
		}

		if level >= self.config.MinLevel {
			k, l = 0, 0

			for i = 0; i < newc; i++ {
				refine := false

				for j = 0; j < oc; j++ {
					absError := math.Abs(surrogate.surpluses[(oldc+i)*oc+j])

					if absError > self.config.AbsError {
						refine = true
						break
					}

					relError := absError / (maxValue[j] - minValue[j])

					if relError > self.config.RelError {
						refine = true
						break
					}
				}

				if !refine {
					l += ic
					continue
				}

				if k != l {
					// Shift everything, assuming a lot of refinements.
					copy(levels[k:], levels[l:])
					copy(orders[k:], orders[l:])
					l = k
				}

				k += ic
				l += ic
			}

			levels = levels[0:k]
			orders = orders[0:k]
		}

		levels, orders = self.grid.ComputeChildren(levels, orders)

		oldc += newc
		newc = uint32(len(levels)) / ic

		if newc == 0 {
			break
		}

		level++
	}

	surrogate.finalize(level, nc)
	return surrogate
}

// Evaluate takes a surrogate produced by Compute and evaluates it at the
// given points.
func (self *Self) Evaluate(surrogate *Surrogate, points []float64) []float64 {
	ic := uint32(surrogate.ic)
	oc := uint32(surrogate.oc)

	pointCount := uint32(len(points)) / ic

	values := make([]float64, pointCount*oc)

	for i := uint32(0); i < pointCount; i++ {
		evaluate(self.basis, surrogate, ic, oc, surrogate.nc,
			points[i*ic:], values[i*oc:])
	}

	return values
}

func evaluate(basis Basis, surrogate *Surrogate, ic, oc, nc uint32,
	point []float64, value []float64) {

	var i, j uint32 = 1, 0

	// Rewrite value in case it is dirty (not zeroed).
	weight := basis.Evaluate(surrogate.levels, surrogate.orders, point)
	for ; j < oc; j++ {
		value[j] = surrogate.surpluses[j] * weight
	}

	for ; i < nc; i++ {
		weight = basis.Evaluate(surrogate.levels[i*ic:], surrogate.orders[i*ic:], point)
		for j = 0; j < oc; j++ {
			value[j] += surrogate.surpluses[i*oc+j] * weight
		}
	}
}
<commit_msg>Minor renamings<commit_after>// Package adhier provides an algorithm for adaptive hierarchical interpolation
// with local refinements.
package adhier

import (
	"math"
)

// Grid is the interface that an sparse grid should satisfy in order to be used
// in the algorithm.
type Grid interface {
	Dimensions() uint16
	ComputeNodes(levels []uint8, orders []uint32) []float64
	ComputeChildren(levels []uint8, orders []uint32) ([]uint8, []uint32)
}

// Basis is the interface that a functional basis should satisfy in order to be
// used in the algorithm.
type Basis interface {
	Evaluate(levels []uint8, orders []uint32, point []float64) float64
}

// Self represents a particular instantiation of the algorithm.
type Self struct {
	grid   Grid
	basis  Basis
	config Config

	ic uint16
	oc uint16
}

// New creates an instance of the algorithm for the given configuration.
func New(grid Grid, basis Basis, config Config, outputs uint16) *Self {
	return &Self{
		grid:   grid,
		basis:  basis,
		config: config,

		ic: grid.Dimensions(),
		oc: outputs,
	}
}

// Compute takes a function and yields a surrogate for it, which can be further
// fed to Evaluate for the actual interpolation.
func (self *Self) Compute(target func([]float64) []float64) *Surrogate {
	ic := uint32(self.ic)
	oc := uint32(self.oc)

	surrogate := new(Surrogate)
	surrogate.initialize(uint16(ic), uint16(oc))

	// Assume level 0 has only one node, and its order is 0.
	level := uint8(0)
	newc := uint32(1)
	levels := make([]uint8, newc*ic)
	orders := make([]uint32, newc*ic)

	oldc := uint32(0)
	nc := uint32(0)

	var i, j, k, l uint32

	value := make([]float64, oc)

	minValue := make([]float64, oc)
	maxValue := make([]float64, oc)

	minValue[0] = math.Inf(1)
	maxValue[0] = math.Inf(-1)
	for i = 1; i < oc; i++ {
		minValue[i] = minValue[i-1]
		maxValue[i] = maxValue[i-1]
	}

	for {
		surrogate.resize(oldc + newc)

		copy(surrogate.levels[oldc*ic:], levels)
		copy(surrogate.orders[oldc*ic:], orders)

		nodes := self.grid.ComputeNodes(levels, orders)
		values := target(nodes)

		// Compute the surpluses corresponding to the active nodes.
		for i, k = 0, oldc*oc; i < newc; i++ {
			evaluate(self.basis, surrogate, ic, oc, oldc,
				nodes[i*ic:(i+1)*ic], value)

			for j = 0; j < oc; j++ {
				surrogate.surpluses[k] = values[i*oc+j] - value[j]
				k++
			}
		}

		nc += newc

		if level >= self.config.MaxLevel {
			break
		}

		// Keep track of the maximal and minimal values of the function.
		for i, k = 0, 0; i < newc; i++ {
			for j = 0; j < oc; j++ {
				if values[k] < minValue[j] {
					minValue[j] = values[k]
				}
				if values[k] > maxValue[j] {
					maxValue[j] = values[k]
				}
				k++
			}
		}

		if level >= self.config.MinLevel {
			k, l = 0, 0

			for i = 0; i < newc; i++ {
				refine := false

				for j = 0; j < oc; j++ {
					absError := math.Abs(surrogate.surpluses[(oldc+i)*oc+j])

					if absError > self.config.AbsError {
						refine = true
						break
					}

					relError := absError / (maxValue[j] - minValue[j])

					if relError > self.config.RelError {
						refine = true
						break
					}
				}

				if !refine {
					l += ic
					continue
				}

				if k != l {
					// Shift everything, assuming a lot of refinements.
					copy(levels[k:], levels[l:])
					copy(orders[k:], orders[l:])
					l = k
				}

				k += ic
				l += ic
			}

			levels = levels[0:k]
			orders = orders[0:k]
		}

		levels, orders = self.grid.ComputeChildren(levels, orders)

		oldc += newc
		newc = uint32(len(levels)) / ic

		if newc == 0 {
			break
		}

		level++
	}

	surrogate.finalize(level, nc)
	return surrogate
}

// Evaluate takes a surrogate produced by Compute and evaluates it at the
// given points.
func (self *Self) Evaluate(s *Surrogate, points []float64) []float64 {
	ic := uint32(s.ic)
	oc := uint32(s.oc)
	pc := uint32(len(points)) / ic

	values := make([]float64, pc*oc)
	for i := uint32(0); i < pc; i++ {
		evaluate(self.basis, s, ic, oc, s.nc, points[i*ic:], values[i*oc:])
	}

	return values
}

func evaluate(b Basis, s *Surrogate, ic, oc, nc uint32, point []float64, value []float64) {
	var i, j uint32 = 1, 0

	// Rewrite value in case it is dirty (not zeroed).
	weight := b.Evaluate(s.levels, s.orders, point)
	for ; j < oc; j++ {
		value[j] = s.surpluses[j] * weight
	}

	for ; i < nc; i++ {
		weight = b.Evaluate(s.levels[i*ic:], s.orders[i*ic:], point)
		for j = 0; j < oc; j++ {
			value[j] += s.surpluses[i*oc+j] * weight
		}
	}
}
<|endoftext|>


<commit_before>package wsutil

import (
	"io"
	"io/ioutil"
	"strconv"

	"github.com/gobwas/pool/pbytes"
	"github.com/gobwas/ws"
)

type FrameHandler func(h ws.Header, r io.Reader) error

func PingHandler(w io.Writer, state ws.State) FrameHandler {
	return func(h ws.Header, rd io.Reader) (err error) {
		var p []byte
		if h.Length != 0 {
			// int(h.Length) is safe here because control frame could be < 125
			// bytes length by RFC.
			p = pbytes.GetBufLen(int(h.Length))
			defer pbytes.PutBuf(p)

			_, err = io.ReadFull(rd, p)
			if err != nil {
				return
			}
		}

		f := ws.NewPongFrame(p)
		if state.Is(ws.StateClientSide) {
			f = ws.MaskFrameInplace(f)
		}

		return ws.WriteFrame(w, f)
	}
}

func PongHandler(w io.Writer, state ws.State) FrameHandler {
	return func(h ws.Header, rd io.Reader) (err error) {
		if h.Length == 0 {
			return nil
		}

		// int(h.Length) is safe here because control frame could be < 125
		// bytes length by RFC.
		buf := pbytes.GetBufLen(int(h.Length))
		defer pbytes.PutBuf(buf)

		// Discard pong message according to the RFC6455:
		// A Pong frame MAY be sent unsolicited. This serves as a
		// unidirectional heartbeat. A response to an unsolicited Pong frame
		// is not expected.
		_, err = io.CopyBuffer(ioutil.Discard, rd, buf)

		return
	}
}

func CloseHandler(w io.Writer, state ws.State) FrameHandler {
	return func(h ws.Header, rd io.Reader) (err error) {
		var (
			f      ws.Frame
			code   ws.StatusCode
			reason string
		)
		if h.Length == 0 {
			f = ws.CloseFrame
			code = ws.StatusNoStatusRcvd
		} else {
			// int(h.Length) is safe here because control frame could be < 125
			// bytes length by RFC.
			p := pbytes.GetBufLen(int(h.Length))
			defer pbytes.PutBuf(p)

			_, err = io.ReadFull(rd, p)
			if err != nil {
				return
			}

			code, reason = ws.ParseCloseFrameData(p)

			if e := ws.CheckCloseFrameData(code, reason); e != nil {
				f = ws.NewCloseFrame(ws.StatusProtocolError, e.Error())
			} else {
				// RFC6455#5.5.1:
				// If an endpoint receives a Close frame and did not previously
				// send a Close frame, the endpoint MUST send a Close frame in
				// response. (When sending a Close frame in response, the endpoint
				// typically echos the status code it received.)
				f = ws.NewCloseFrame(code, "")
			}
		}

		if state.Is(ws.StateClientSide) {
			f = ws.MaskFrameInplace(f)
		}

		if err = ws.WriteFrame(w, f); err == nil {
			err = ClosedError{code, reason}
		}

		return
	}
}

func ControlHandler(w io.Writer, state ws.State) FrameHandler {
	pingHandler := PingHandler(w, state)
	pongHandler := PongHandler(w, state)
	closeHandler := CloseHandler(w, state)

	return func(h ws.Header, rd io.Reader) (err error) {
		switch h.OpCode {
		case ws.OpPing:
			return pingHandler(h, rd)
		case ws.OpPong:
			return pongHandler(h, rd)
		case ws.OpClose:
			return closeHandler(h, rd)
		}
		return
	}
}

func NextReader(r io.Reader, s ws.State) (h ws.Header, rd *Reader, err error) {
	rd = NewReader(r, s)
	h, err = rd.Next()
	return
}

type handler struct {
	continuation FrameHandler
	intermediate FrameHandler
}

type Reader struct {
	src     io.Reader
	state   ws.State
	payload io.Reader
	handler handler
}

type ClosedError struct {
	code   ws.StatusCode
	reason string
}

func (err ClosedError) Error() string {
	return "ws closed: " + strconv.FormatUint(uint64(err.code), 10) + " " + err.reason
}

func (err ClosedError) Reason() string {
	return err.reason
}

func (err ClosedError) Code() ws.StatusCode {
	return err.code
}

func NewReader(r io.Reader, s ws.State) *Reader {
	return &Reader{
		src:   r,
		state: s,
	}
}

func (r *Reader) HandleContinuation(h FrameHandler) { r.handler.continuation = h }
func (r *Reader) HandleIntermediate(h FrameHandler) { r.handler.intermediate = h }

func (r *Reader) Read(p []byte) (n int, err error) {
	if r.payload == nil {
		_, err = r.Next()
		if err != nil || r.payload == nil {
			return
		}
	}

	n, err = r.payload.Read(p)

	if err == io.EOF {
		r.payload = nil
		if r.state.Is(ws.StateFragmented) {
			err = nil
		}
	}

	return
}

func (r *Reader) Next() (h ws.Header, err error) {
	h, err = ws.ReadHeader(r.src)
	if err != nil {
		return
	}
	if err = ws.CheckHeader(h, r.state); err != nil {
		return
	}

	src := io.LimitReader(r.src, h.Length)
	rd := src
	if h.Masked {
		rd = NewCipherReader(rd, h.Mask)
	}

	if r.state.Is(ws.StateFragmented) && h.OpCode.IsControl() {
		if hi := r.handler.intermediate; hi != nil {
			err = hi(h, rd)
		}
		if err == nil {
			_, err = io.Copy(ioutil.Discard, src)
		}
		return
	}

	if h.OpCode == ws.OpContinuation {
		if hc := r.handler.continuation; hc != nil {
			err = hc(h, rd)
		}
	}

	r.state = r.state.SetOrClearIf(!h.Fin, ws.StateFragmented)
	r.payload = rd

	return
}
<commit_msg>add comment<commit_after>package wsutil

import (
	"io"
	"io/ioutil"
	"strconv"

	"github.com/gobwas/pool/pbytes"
	"github.com/gobwas/ws"
)

type FrameHandler func(h ws.Header, r io.Reader) error

func PingHandler(w io.Writer, state ws.State) FrameHandler {
	return func(h ws.Header, rd io.Reader) (err error) {
		var p []byte
		if h.Length != 0 {
			// int(h.Length) is safe here because control frame could be < 125
			// bytes length by RFC.
			p = pbytes.GetBufLen(int(h.Length))
			defer pbytes.PutBuf(p)

			_, err = io.ReadFull(rd, p)
			if err != nil {
				return
			}
		}

		f := ws.NewPongFrame(p)
		if state.Is(ws.StateClientSide) {
			f = ws.MaskFrameInplace(f)
		}

		return ws.WriteFrame(w, f)
	}
}

func PongHandler(w io.Writer, state ws.State) FrameHandler {
	return func(h ws.Header, rd io.Reader) (err error) {
		if h.Length == 0 {
			return nil
		}

		// int(h.Length) is safe here because control frame could be < 125
		// bytes length by RFC.
		buf := pbytes.GetBufLen(int(h.Length))
		defer pbytes.PutBuf(buf)

		// Discard pong message according to the RFC6455:
		// A Pong frame MAY be sent unsolicited. This serves as a
		// unidirectional heartbeat. A response to an unsolicited Pong frame
		// is not expected.
		_, err = io.CopyBuffer(ioutil.Discard, rd, buf)

		return
	}
}

func CloseHandler(w io.Writer, state ws.State) FrameHandler {
	return func(h ws.Header, rd io.Reader) (err error) {
		var (
			f      ws.Frame
			code   ws.StatusCode
			reason string
		)
		if h.Length == 0 {
			f = ws.CloseFrame
			code = ws.StatusNoStatusRcvd
		} else {
			// int(h.Length) is safe here because control frame could be < 125
			// bytes length by RFC.
			p := pbytes.GetBufLen(int(h.Length))
			defer pbytes.PutBuf(p)

			_, err = io.ReadFull(rd, p)
			if err != nil {
				return
			}

			code, reason = ws.ParseCloseFrameData(p)

			if e := ws.CheckCloseFrameData(code, reason); e != nil {
				f = ws.NewCloseFrame(ws.StatusProtocolError, e.Error())
			} else {
				// RFC6455#5.5.1:
				// If an endpoint receives a Close frame and did not previously
				// send a Close frame, the endpoint MUST send a Close frame in
				// response. (When sending a Close frame in response, the endpoint
				// typically echos the status code it received.)
				f = ws.NewCloseFrame(code, "")
			}
		}

		if state.Is(ws.StateClientSide) {
			f = ws.MaskFrameInplace(f)
		}

		if err = ws.WriteFrame(w, f); err == nil {
			err = ClosedError{code, reason}
		}

		return
	}
}

func ControlHandler(w io.Writer, state ws.State) FrameHandler {
	pingHandler := PingHandler(w, state)
	pongHandler := PongHandler(w, state)
	closeHandler := CloseHandler(w, state)

	return func(h ws.Header, rd io.Reader) (err error) {
		switch h.OpCode {
		case ws.OpPing:
			return pingHandler(h, rd)
		case ws.OpPong:
			return pongHandler(h, rd)
		case ws.OpClose:
			return closeHandler(h, rd)
		}
		return
	}
}

func NextReader(r io.Reader, s ws.State) (h ws.Header, rd *Reader, err error) {
	rd = NewReader(r, s)
	h, err = rd.Next()
	return
}

type handler struct {
	continuation FrameHandler
	intermediate FrameHandler
}

type Reader struct {
	src     io.Reader
	state   ws.State
	payload io.Reader
	handler handler
}

type ClosedError struct {
	code   ws.StatusCode
	reason string
}

func (err ClosedError) Error() string {
	return "ws closed: " + strconv.FormatUint(uint64(err.code), 10) + " " + err.reason
}

func (err ClosedError) Reason() string {
	return err.reason
}

func (err ClosedError) Code() ws.StatusCode {
	return err.code
}

func NewReader(r io.Reader, s ws.State) *Reader {
	return &Reader{
		src:   r,
		state: s,
	}
}

func (r *Reader) HandleContinuation(h FrameHandler) { r.handler.continuation = h }
func (r *Reader) HandleIntermediate(h FrameHandler) { r.handler.intermediate = h }

func (r *Reader) Read(p []byte) (n int, err error) {
	if r.payload == nil {
		_, err = r.Next()
		if err != nil || r.payload == nil {
			return
		}
	}

	n, err = r.payload.Read(p)

	if err == io.EOF {
		r.payload = nil
		if r.state.Is(ws.StateFragmented) {
			err = nil
		}
	}

	return
}

func (r *Reader) Next() (h ws.Header, err error) {
	h, err = ws.ReadHeader(r.src)
	if err != nil {
		return
	}
	if err = ws.CheckHeader(h, r.state); err != nil {
		return
	}

	src := io.LimitReader(r.src, h.Length)
	rd := src
	if h.Masked {
		rd = NewCipherReader(rd, h.Mask)
	}

	if r.state.Is(ws.StateFragmented) && h.OpCode.IsControl() {
		if hi := r.handler.intermediate; hi != nil {
			err = hi(h, rd)
		}
		if err == nil {
			// Ensure that src is empty.
			_, err = io.Copy(ioutil.Discard, src)
		}
		return
	}

	if h.OpCode == ws.OpContinuation {
		if hc := r.handler.continuation; hc != nil {
			err = hc(h, rd)
		}
	}

	r.state = r.state.SetOrClearIf(!h.Fin, ws.StateFragmented)
	r.payload = rd

	return
}
<|endoftext|>


<commit_before>package util

import (
	"bytes"
	"errors"
	"fmt"
	"html/template"
	"net/http"
	"os"
	pathutil "path/filepath"
	"strings"

	"github.com/7sDream/rikka/common/logger"
)

var (
	l = logger.NewLogger("[Util]")
)

// ErrHandle is a simple error handl function.
// If err is an error, write 500 InernalServerError to header and write error message to response and return true.
// Else (err is nil), don't do anything and return false.
func ErrHandle(w http.ResponseWriter, err error) bool {
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return true
	}
	return false
}

// GetTaskIDByRequest gets last part of url path as a taskID and return it.
func GetTaskIDByRequest(r *http.Request) string {
	splitedPath := strings.Split(r.URL.Path, "/")
	filename := splitedPath[len(splitedPath)-1]
	return filename
}

// CheckExist chekc if a file or dir is Exist.
func CheckExist(filepath string) bool {
	if _, err := os.Stat(filepath); os.IsNotExist(err) {
		return false
	}
	return true
}

// IsDir chekc if the path is a file, false when not exist or is a dir.
func IsDir(path string) bool {
	if CheckExist(path) {
		stat, _ := os.Stat(path)
		return stat.IsDir()
	}
	return false
}

// IsFile chekc if a path a file, false when not exist or is a file.
func IsFile(path string) bool {
	if CheckExist(path) {
		stat, _ := os.Stat(path)
		return !stat.IsDir()
	}
	return false
}

// CheckMethod check if request method is as excepted.
// If not, write the stauts "MethodNotAllow" to header, "Method Not Allowed." to response and return false.
// Else don't do anything and return true.
func CheckMethod(w http.ResponseWriter, r *http.Request, excepted string) bool {
	if r.Method != excepted {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return false
	}
	return true
}

// RenderTemplate is a shortcut function to render template to response.
func RenderTemplate(templatePath string, w http.ResponseWriter, data interface{}) error {
	t, err := template.ParseFiles(templatePath)
	if ErrHandle(w, err) {
		l.Error("Error happened when parse template file", templatePath, ":", err)
		return err
	}

	// a buff that ues in execute, if error happened,
	// error message will not be write to truly response
	buff := bytes.NewBuffer([]byte{})
	err = t.Execute(buff, data)

	// error happened, write a generic error message to response
	if err != nil {
		l.Error("Error happened when execute template", t, "with data", fmt.Sprintf("%+v", data), ":", err)
		ErrHandle(w, errors.New("error when render template"))
		return err
	}

	// no error happened, write to response
	content := make([]byte, buff.Len())
	buff.Read(content)
	_, err = w.Write(content)

	return err
}

// RenderJSON is a shortcut function to write JSON data to response, and set the header Content-Type.
func RenderJSON(w http.ResponseWriter, data []byte, code int) (err error) {
	w.WriteHeader(code)
	w.Header().Set("Content-Type", "application/json")
	_, err = w.Write(data)
	return err
}

// MustBeOr404 check if URL path is as excepted.
// If not equal, write 404 to header, "404 not fount" to response, and return false.
// Else don't do anything and return true.
func MustBeOr404(w http.ResponseWriter, r *http.Request, path string) bool {
	if r.URL.Path != path {
		http.NotFound(w, r)
		return false
	}
	return true
}

// MustExistOr404 check if a file is exist.
// If not, write 404 to header, "404 not fount" to response, and return false.
// Else don't do anything and return true.
func MustExistOr404(w http.ResponseWriter, r *http.Request, filepath string) bool {
	if !CheckExist(filepath) {
		http.NotFound(w, r)
		return false
	}
	return true
}

// DisableListDir accept a handle func and return a handle that not allow list dir.
func DisableListDir(log *logger.Logger, h http.HandlerFunc) http.HandlerFunc {
	if log == nil {
		l.Warn("Get a nil logger in function DisableListDirFunc")
		log = l
	}
	return func(w http.ResponseWriter, r *http.Request) {
		if strings.HasSuffix(r.URL.Path, "/") {
			log.Warn("Someone try to list dir", r.URL.Path)
			http.NotFound(w, r)
		} else {
			h(w, r)
		}
	}
}

// ContextCreator accept a request and return a context, used in TemplateRenderHandler.
type ContextCreator func(r *http.Request) interface{}

// TemplateRenderHandler is a shortcut function that generate a http.HandlerFunc.
// The generated func use contextCreator to create context and render the templatePath template file.
// If contextCreator is nil, nil will be used as context.
func TemplateRenderHandler(templatePath string, contextCreator ContextCreator, log *logger.Logger) http.HandlerFunc {
	if log == nil {
		l.Warn("Get a nil logger in function TemplateRenderHandler")
		log = l
	}
	return func(w http.ResponseWriter, r *http.Request) {

		templateName := pathutil.Base(templatePath)

		log.Info("Recieve a template render request of", templateName, "from ip", r.RemoteAddr)

		var err error

		if contextCreator != nil {
			err = RenderTemplate(templatePath, w, contextCreator(r))
		} else {
			err = RenderTemplate(templatePath, w, nil)
		}

		if err != nil {
			log.Warn("Render template", templateName, "with data", nil, "error: ", err)
		}

		log.Info("Render template", templateName, "successfully")
	}
}

// RequestFilter accept a http.HandlerFunc and return a new one
// which only accept path is pathMustBe and method is methodMustBe.
// Error message in new hander will be print with logger log, if log is nil, will use default logger.
// If pathMustBe or methodMustBe is empty string, no check will be performed.
func RequestFilter(pathMustBe string, methodMustBe string, log *logger.Logger, handlerFunc http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		if log == nil {
			l.Warn("Get a nil logger in function RequestFilter")
			log = l
		}

		if pathMustBe != "" {
			if !MustBeOr404(w, r, pathMustBe) {
				log.Warn("Someone visit a non-exist page", r.URL.Path, ", excepted is /")
				return
			}
		}

		if methodMustBe != "" {
			if !CheckMethod(w, r, methodMustBe) {
				log.Warn("Someone visit page", r.URL.Path, "with method", r.Method, ", only", methodMustBe, "is allowed.")
				return
			}
		}

		handlerFunc(w, r)
	}
}
<commit_msg>remove extra . in log message<commit_after>package util

import (
	"bytes"
	"errors"
	"fmt"
	"html/template"
	"net/http"
	"os"
	pathutil "path/filepath"
	"strings"

	"github.com/7sDream/rikka/common/logger"
)

var (
	l = logger.NewLogger("[Util]")
)

// ErrHandle is a simple error handl function.
// If err is an error, write 500 InernalServerError to header and write error message to response and return true.
// Else (err is nil), don't do anything and return false.
func ErrHandle(w http.ResponseWriter, err error) bool {
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return true
	}
	return false
}

// GetTaskIDByRequest gets last part of url path as a taskID and return it.
func GetTaskIDByRequest(r *http.Request) string {
	splitedPath := strings.Split(r.URL.Path, "/")
	filename := splitedPath[len(splitedPath)-1]
	return filename
}

// CheckExist chekc if a file or dir is Exist.
func CheckExist(filepath string) bool {
	if _, err := os.Stat(filepath); os.IsNotExist(err) {
		return false
	}
	return true
}

// IsDir chekc if the path is a file, false when not exist or is a dir.
func IsDir(path string) bool {
	if CheckExist(path) {
		stat, _ := os.Stat(path)
		return stat.IsDir()
	}
	return false
}

// IsFile chekc if a path a file, false when not exist or is a file.
func IsFile(path string) bool {
	if CheckExist(path) {
		stat, _ := os.Stat(path)
		return !stat.IsDir()
	}
	return false
}

// CheckMethod check if request method is as excepted.
// If not, write the stauts "MethodNotAllow" to header, "Method Not Allowed." to response and return false.
// Else don't do anything and return true.
func CheckMethod(w http.ResponseWriter, r *http.Request, excepted string) bool {
	if r.Method != excepted {
		http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
		return false
	}
	return true
}

// RenderTemplate is a shortcut function to render template to response.
func RenderTemplate(templatePath string, w http.ResponseWriter, data interface{}) error {
	t, err := template.ParseFiles(templatePath)
	if ErrHandle(w, err) {
		l.Error("Error happened when parse template file", templatePath, ":", err)
		return err
	}

	// a buff that ues in execute, if error happened,
	// error message will not be write to truly response
	buff := bytes.NewBuffer([]byte{})
	err = t.Execute(buff, data)

	// error happened, write a generic error message to response
	if err != nil {
		l.Error("Error happened when execute template", t, "with data", fmt.Sprintf("%+v", data), ":", err)
		ErrHandle(w, errors.New("error when render template"))
		return err
	}

	// no error happened, write to response
	content := make([]byte, buff.Len())
	buff.Read(content)
	_, err = w.Write(content)

	return err
}

// RenderJSON is a shortcut function to write JSON data to response, and set the header Content-Type.
func RenderJSON(w http.ResponseWriter, data []byte, code int) (err error) {
	w.WriteHeader(code)
	w.Header().Set("Content-Type", "application/json")
	_, err = w.Write(data)
	return err
}

// MustBeOr404 check if URL path is as excepted.
// If not equal, write 404 to header, "404 not fount" to response, and return false.
// Else don't do anything and return true.
func MustBeOr404(w http.ResponseWriter, r *http.Request, path string) bool {
	if r.URL.Path != path {
		http.NotFound(w, r)
		return false
	}
	return true
}

// MustExistOr404 check if a file is exist.
// If not, write 404 to header, "404 not fount" to response, and return false.
// Else don't do anything and return true.
func MustExistOr404(w http.ResponseWriter, r *http.Request, filepath string) bool {
	if !CheckExist(filepath) {
		http.NotFound(w, r)
		return false
	}
	return true
}

// DisableListDir accept a handle func and return a handle that not allow list dir.
func DisableListDir(log *logger.Logger, h http.HandlerFunc) http.HandlerFunc {
	if log == nil {
		l.Warn("Get a nil logger in function DisableListDirFunc")
		log = l
	}
	return func(w http.ResponseWriter, r *http.Request) {
		if strings.HasSuffix(r.URL.Path, "/") {
			log.Warn("Someone try to list dir", r.URL.Path)
			http.NotFound(w, r)
		} else {
			h(w, r)
		}
	}
}

// ContextCreator accept a request and return a context, used in TemplateRenderHandler.
type ContextCreator func(r *http.Request) interface{}

// TemplateRenderHandler is a shortcut function that generate a http.HandlerFunc.
// The generated func use contextCreator to create context and render the templatePath template file.
// If contextCreator is nil, nil will be used as context.
func TemplateRenderHandler(templatePath string, contextCreator ContextCreator, log *logger.Logger) http.HandlerFunc {
	if log == nil {
		l.Warn("Get a nil logger in function TemplateRenderHandler")
		log = l
	}
	return func(w http.ResponseWriter, r *http.Request) {

		templateName := pathutil.Base(templatePath)

		log.Info("Recieve a template render request of", templateName, "from ip", r.RemoteAddr)

		var err error

		if contextCreator != nil {
			err = RenderTemplate(templatePath, w, contextCreator(r))
		} else {
			err = RenderTemplate(templatePath, w, nil)
		}

		if err != nil {
			log.Warn("Render template", templateName, "with data", nil, "error: ", err)
		}

		log.Info("Render template", templateName, "successfully")
	}
}

// RequestFilter accept a http.HandlerFunc and return a new one
// which only accept path is pathMustBe and method is methodMustBe.
// Error message in new hander will be print with logger log, if log is nil, will use default logger.
// If pathMustBe or methodMustBe is empty string, no check will be performed.
func RequestFilter(pathMustBe string, methodMustBe string, log *logger.Logger, handlerFunc http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {

		if log == nil {
			l.Warn("Get a nil logger in function RequestFilter")
			log = l
		}

		if pathMustBe != "" {
			if !MustBeOr404(w, r, pathMustBe) {
				log.Warn("Someone visit a non-exist page", r.URL.Path, ", excepted is /")
				return
			}
		}

		if methodMustBe != "" {
			if !CheckMethod(w, r, methodMustBe) {
				log.Warn("Someone visit page", r.URL.Path, "with method", r.Method, ", only", methodMustBe, "is allowed")
				return
			}
		}

		handlerFunc(w, r)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"github.com/abiosoft/errs"
	"github.com/fatih/color"
)

var (
	commands = map[string]func([]string){
		"install":       installPlugins,
		"uninstall":     uninstallPlugins,
		"list":          listPlugins,
		"install-caddy": installCaddy,
		"help":          func([]string) { usage() },
	}

	successMark = color.GreenString("✓")
	errorMark   = color.RedString("✗")
)

func installPlugins(pluginNames []string) {
	var outputs []string
	var errored bool
	for _, pluginName := range pluginNames {
		plugin, ok := plugins[pluginName]
		if !ok {
			outputs = append(outputs, " "+errorMark+" "+pluginName+" - plugin not found")
			errored = true
			continue
		}
		fmt.Println("installing", plugin.Name+"...")
		if err := plugin.Build(); err != nil {
			fmt.Println(err)
			errored = true
			outputs = append(outputs, " "+errorMark+" "+plugin.Name)
		} else {
			outputs = append(outputs, " "+successMark+" "+plugin.Name)
		}
	}
	if len(outputs) > 0 {
		for _, p := range outputs {
			fmt.Println(p)
		}
	}
	if errored {
		os.Exit(1)
	}
}

func uninstallPlugins(pluginNames []string) {
	var success []string
	var failures []string
	for _, pluginName := range pluginNames {
		plugin, ok := plugins[pluginName]
		if !ok {
			fmt.Println("plugin not found", pluginName)
			return
		}
		if err := plugin.Remove(); err != nil {
			fmt.Println(err)
			success = append(success, plugin.Name)
		} else {
			failures = append(failures, plugin.Name)
		}
	}
	if len(success) > 0 {
		fmt.Println("Uninstalled:")
		fmt.Println(" ", strings.Join(success, ", "))
	}
	if len(failures) > 0 {
		fmt.Println("Failed to uninstall:")
		fmt.Println(" ", strings.Join(failures, ", "))
	}
}

func listPlugins([]string) {
	for pluginType, fetcher := range fetchers {
		plugins, err := fetcher.FetchPlugins()
		if err != nil {
			fmt.Println(err)
			return
		}
		sort.Slice(plugins, func(i, j int) bool {
			if plugins[i].Installed() != plugins[j].Installed() {
				return plugins[i].Installed()
			}
			return plugins[i].Name < plugins[j].Name
		})
		if len(plugins) > 1 {
			fmt.Println(pluginType + ":")
		}
		for _, plugin := range plugins {
			check := "  "
			if plugin.Installed() {
				check = " ✓"
			}
			fmt.Println(check, plugin.Name)
		}
	}
}

const (
	pluginLoaderFile = "caddy/caddymain/pluginloader.go"
	pluginLoaderSrc  = `package caddymain
	import _ "github.com/abiosoft/caddyplug"`
)

func installCaddy([]string) {
	fmt.Println("installing Caddy...")
	outputFile := "/usr/local/bin/caddy"
	// check if GOBIN is in PATH and use it instead
	for _, binPath := range strings.Split(os.Getenv("PATH"), string([]byte{filepath.ListSeparator})) {
		if filepath.Clean(binPath) == filepath.Join(goPath(), "bin") {
			outputFile = filepath.Join(goPath(), "bin", "caddy")
			break
		}
	}

	var e errs.Group
	pluginFile := filepath.Join(goPath(), "src", caddyPackage, pluginLoaderFile)
	e.Add(func() error {
		return ioutil.WriteFile(pluginFile, []byte(pluginLoaderSrc), 0644)
	})
	e.Add(func() error {
		return shellCmd{}.
			run("go", "build", "-o", outputFile, caddyPackage+"/caddy")
	})
	e.Add(func() error {
		fmt.Println(" ", successMark, "installed Caddy in", outputFile)
		return nil
	})
	e.Add(func() error {
		return os.Remove(pluginFile)
	})

	if err := e.Exec(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}
<commit_msg>install to /usr/bin/caddy if writable<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"strings"

	"golang.org/x/sys/unix"

	"github.com/abiosoft/errs"
	"github.com/fatih/color"
)

var (
	commands = map[string]func([]string){
		"install":       installPlugins,
		"uninstall":     uninstallPlugins,
		"list":          listPlugins,
		"install-caddy": installCaddy,
		"help":          func([]string) { usage() },
	}

	successMark = color.GreenString("✓")
	errorMark   = color.RedString("✗")
)

func installPlugins(pluginNames []string) {
	var outputs []string
	var errored bool
	for _, pluginName := range pluginNames {
		plugin, ok := plugins[pluginName]
		if !ok {
			outputs = append(outputs, " "+errorMark+" "+pluginName+" - plugin not found")
			errored = true
			continue
		}
		fmt.Println("installing", plugin.Name+"...")
		if err := plugin.Build(); err != nil {
			fmt.Println(err)
			errored = true
			outputs = append(outputs, " "+errorMark+" "+plugin.Name)
		} else {
			outputs = append(outputs, " "+successMark+" "+plugin.Name)
		}
	}
	if len(outputs) > 0 {
		for _, p := range outputs {
			fmt.Println(p)
		}
	}
	if errored {
		os.Exit(1)
	}
}

func uninstallPlugins(pluginNames []string) {
	var success []string
	var failures []string
	for _, pluginName := range pluginNames {
		plugin, ok := plugins[pluginName]
		if !ok {
			fmt.Println("plugin not found", pluginName)
			return
		}
		if err := plugin.Remove(); err != nil {
			fmt.Println(err)
			success = append(success, plugin.Name)
		} else {
			failures = append(failures, plugin.Name)
		}
	}
	if len(success) > 0 {
		fmt.Println("Uninstalled:")
		fmt.Println(" ", strings.Join(success, ", "))
	}
	if len(failures) > 0 {
		fmt.Println("Failed to uninstall:")
		fmt.Println(" ", strings.Join(failures, ", "))
	}
}

func listPlugins([]string) {
	for pluginType, fetcher := range fetchers {
		plugins, err := fetcher.FetchPlugins()
		if err != nil {
			fmt.Println(err)
			return
		}
		sort.Slice(plugins, func(i, j int) bool {
			if plugins[i].Installed() != plugins[j].Installed() {
				return plugins[i].Installed()
			}
			return plugins[i].Name < plugins[j].Name
		})
		if len(plugins) > 1 {
			fmt.Println(pluginType + ":")
		}
		for _, plugin := range plugins {
			check := "  "
			if plugin.Installed() {
				check = " ✓"
			}
			fmt.Println(check, plugin.Name)
		}
	}
}

const (
	pluginLoaderFile = "caddy/caddymain/pluginloader.go"
	pluginLoaderSrc  = `package caddymain
	import _ "github.com/abiosoft/caddyplug"`
)

func installCaddy([]string) {
	fmt.Println("installing Caddy...")
	outputFile := "/usr/bin/caddy"

	// if not writable, fall back to local paths
	if !writable("/usr/bin") {
		outputFile = "/usr/local/bin/caddy"
		// check if GOBIN is in PATH and use it instead
		for _, binPath := range strings.Split(os.Getenv("PATH"),
			string([]byte{filepath.ListSeparator})) {
			if filepath.Clean(binPath) == filepath.Join(goPath(), "bin") {
				outputFile = filepath.Join(goPath(), "bin", "caddy")
				break
			}
		}
	}

	var e errs.Group
	pluginFile := filepath.Join(goPath(), "src", caddyPackage, pluginLoaderFile)
	e.Add(func() error {
		return ioutil.WriteFile(pluginFile, []byte(pluginLoaderSrc), 0644)
	})
	e.Add(func() error {
		return shellCmd{}.
			run("go", "build", "-o", outputFile, caddyPackage+"/caddy")
	})
	e.Add(func() error {
		fmt.Println(" ", successMark, "installed Caddy in", outputFile)
		return nil
	})
	e.Add(func() error {
		return os.Remove(pluginFile)
	})

	if err := e.Exec(); err != nil {
		fmt.Println(err)
		os.Exit(1)
	}
}

func writable(path string) bool {
	return unix.Access(path, unix.W_OK) == nil
}
<|endoftext|>


<commit_before>/*
 * Logging-related functions.
 *
 * (c) 2011-2012 Bernd Fix   >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package logger

///////////////////////////////////////////////////////////////////////
// Import external declarations

import (
	"fmt"
	"log"
	"os"
)

///////////////////////////////////////////////////////////////////////
// Logging constants

const (
	CRITICAL = iota // critical errors
	SEVERE          // severe errors
	ERROR           // errors
	WARN            // warnings
	INFO            // info
	DBG_HIGH        // debug (high prio)
	DBG             // debug (normal)
	DBG_ALL         // debug (all)
)

///////////////////////////////////////////////////////////////////////
// Public variables

var logLevel = DBG // current logging verbosity

///////////////////////////////////////////////////////////////////////
// Public logging functions.

/*
 * Punch logging data for given level.
 * @param level int - associated logging level
 * @param line string - information to be logged
 */
func Println(level int, line string) {
	if level <= logLevel {
		log.Println(getTag(level) + line)
	}
}

//---------------------------------------------------------------------
/*
 * Punch formatted logging data for givel level
 * @param level int - associated logging level
 * @param format string - format definition
 * @param v ...interface{} - list of variables to be formatted
 */
func Printf(level int, format string, v ...interface{}) {
	if level <= logLevel {
		log.Print(getTag(level) + fmt.Sprintf(format, v...))
	}
}

//=====================================================================
// Logfile functions
//=====================================================================

/*
 * Start logging to file.
 * @param filename string - name of logfile
 */
func LogToFile(filename string) bool {
	Println(INFO, "[log] file-based logging to '"+filename+"'")
	if f, err := os.Create(filename); err == nil {
		log.SetOutput(f)
		return true
	}
	Println(ERROR, "[log] can't enable file-based logging!")
	return false
}

//=====================================================================
// Human-readable log tags
//=====================================================================

/*
 * Return numeric log level.
 * @return int - current log level
 */
func GetLogLevel() int {
	return logLevel
}

//---------------------------------------------------------------------
/*
 * Get current loglevel in human-readable form.
 * @return string - symbolic name of loglevel
 */
func GetLogLevelName() string {
	switch logLevel {
	case CRITICAL:
		return "CRITICAL"
	case SEVERE:
		return "SEVERE"
	case ERROR:
		return "ERROR"
	case WARN:
		return "WARN"
	case INFO:
		return "INFO"
	case DBG_HIGH:
		return "DBG_HIGH"
	case DBG:
		return "DBG"
	case DBG_ALL:
		return "DBG_ALL"
	}
	return "UNKNOWN_LOGLEVEL"
}

//---------------------------------------------------------------------
/*
 * Set logging level from value
 * @param lvl int - new log level
 */
func SetLogLevel(lvl int) {
	if lvl < CRITICAL || lvl > DBG_ALL {
		Printf(WARN, "[logger] Unknown loglevel '%d' requested -- ignored.\n", lvl)
	}
	logLevel = lvl
}

//---------------------------------------------------------------------
/*
 * Set logging level from symbolic name.
 * @param name string - name of log level
 */
func SetLogLevelFromName(name string) {
	switch name {
	case "ERROR":
		logLevel = ERROR
	case "WARN":
		logLevel = WARN
	case "INFO":
		logLevel = INFO
	case "DBG_HIGH":
		logLevel = DBG_HIGH
	case "DBG":
		logLevel = DBG
	case "DBG_ALL":
		logLevel = DBG_ALL
	default:
		Println(WARN, "[logger] Unknown loglevel '"+name+"' requested.")
	}
}

//---------------------------------------------------------------------
/*
 * Get loglevel tag as prefix for message
 * @param level int - log level
 * @return string - log tag
 */
func getTag(level int) string {
	switch level {
	case CRITICAL:
		return "{C}"
	case SEVERE:
		return "{S}"
	case ERROR:
		return "{E}"
	case WARN:
		return "{W}"
	case INFO:
		return "{I}"
	case DBG_HIGH:
		return "{D2}"
	case DBG:
		return "{D1}"
	case DBG_ALL:
		return "{D0}"
	}
	return "{?}"
}
<commit_msg>Internal message processing reworked. Added log rotation.<commit_after>/*
 * Logging-related functions.
 *
 * (c) 2011-2012 Bernd Fix   >Y<
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package logger

///////////////////////////////////////////////////////////////////////
// Import external declarations

import (
	"fmt"
	"os"
	"time"
)

///////////////////////////////////////////////////////////////////////
// Logging constants

const (
	CRITICAL = iota // critical errors
	SEVERE          // severe errors
	ERROR           // errors
	WARN            // warnings
	INFO            // info
	DBG_HIGH        // debug (high prio)
	DBG             // debug (normal)
	DBG_ALL         // debug (all)

	cmd_ROTATE = iota // rotate log file
)

///////////////////////////////////////////////////////////////////////
// Local types

type logger struct {
	msgChan chan string // message to be logged
	cmdChan chan int    // commands to be executed
	logfile *os.File    // current log file (can be stdout/stderr)
	started time.Time   // start time of current log file
	level   int         // current log level
}

///////////////////////////////////////////////////////////////////////
// Local variables

var (
	logInst *logger = nil // singleton logger instance
)

///////////////////////////////////////////////////////////////////////
// Logger-internal methods / functions
/*
 * Instantiate new logger (to stdout)
 */
func init() {
	logInst = new(logger)
	logInst.msgChan = make(chan string)
	logInst.cmdChan = make(chan int)
	logInst.logfile = os.Stdout
	logInst.started = time.Now()
	logInst.level = DBG

	go logInst.run()
}

//---------------------------------------------------------------------
/*
 * Handler loop for logger instance.
 */
func (l *logger) run() {
	for {
		select {
		case msg := <-l.msgChan:
			ts := time.Now().Format(time.Stamp)
			l.logfile.WriteString(ts + msg)
		case cmd := <-l.cmdChan:
			switch cmd {
			case cmd_ROTATE:
				if l.logfile != os.Stdout {
					fname := logInst.logfile.Name()
					logInst.logfile.Close()
					ts := logInst.started.Format(time.RFC3339)
					os.Rename(fname, fname+"."+ts)
					LogToFile(fname)
				} else {
					Println(WARN, "[log] log rotation for 'stdout' not applicable.")
				}
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////
// Public logging functions.

/*
 * Punch logging data for given level.
 * @param level int - associated logging level
 * @param line string - information to be logged
 */
func Println(level int, line string) {
	if level <= logInst.level {
		logInst.msgChan <- getTag(level) + line + "\n"
	}
}

//---------------------------------------------------------------------
/*
 * Punch formatted logging data for givel level
 * @param level int - associated logging level
 * @param format string - format definition
 * @param v ...interface{} - list of variables to be formatted
 */
func Printf(level int, format string, v ...interface{}) {
	if level <= logInst.level {
		logInst.msgChan <- getTag(level) + fmt.Sprintf(format, v...)
	}
}

//=====================================================================
// Logfile functions
//=====================================================================

/*
 * Start logging to file.
 * @param filename string - name of logfile
 */
func LogToFile(filename string) bool {
	Println(INFO, "[log] file-based logging to '"+filename+"'")
	if f, err := os.Create(filename); err == nil {
		logInst.logfile = f
		logInst.started = time.Now()
		return true
	}
	Println(ERROR, "[log] can't enable file-based logging!")
	return false
}

//---------------------------------------------------------------------
/*
 * Rotate log file.
 */
func Rotate() {
	logInst.cmdChan <- cmd_ROTATE
}

//=====================================================================
// Human-readable log tags
//=====================================================================

/*
 * Return numeric log level.
 * @return int - current log level
 */
func GetLogLevel() int {
	return logInst.level
}

//---------------------------------------------------------------------
/*
 * Get current loglevel in human-readable form.
 * @return string - symbolic name of loglevel
 */
func GetLogLevelName() string {
	switch logInst.level {
	case CRITICAL:
		return "CRITICAL"
	case SEVERE:
		return "SEVERE"
	case ERROR:
		return "ERROR"
	case WARN:
		return "WARN"
	case INFO:
		return "INFO"
	case DBG_HIGH:
		return "DBG_HIGH"
	case DBG:
		return "DBG"
	case DBG_ALL:
		return "DBG_ALL"
	}
	return "UNKNOWN_LOGLEVEL"
}

//---------------------------------------------------------------------
/*
 * Set logging level from value
 * @param lvl int - new log level
 */
func SetLogLevel(lvl int) {
	if lvl < CRITICAL || lvl > DBG_ALL {
		Printf(WARN, "[logger] Unknown loglevel '%d' requested -- ignored.\n", lvl)
	}
	logInst.level = lvl
}

//---------------------------------------------------------------------
/*
 * Set logging level from symbolic name.
 * @param name string - name of log level
 */
func SetLogLevelFromName(name string) {
	switch name {
	case "ERROR":
		logInst.level = ERROR
	case "WARN":
		logInst.level = WARN
	case "INFO":
		logInst.level = INFO
	case "DBG_HIGH":
		logInst.level = DBG_HIGH
	case "DBG":
		logInst.level = DBG
	case "DBG_ALL":
		logInst.level = DBG_ALL
	default:
		Println(WARN, "[logger] Unknown loglevel '"+name+"' requested.")
	}
}

//---------------------------------------------------------------------
/*
 * Get loglevel tag as prefix for message
 * @param level int - log level
 * @return string - log tag
 */
func getTag(level int) string {
	switch level {
	case CRITICAL:
		return "{C}"
	case SEVERE:
		return "{S}"
	case ERROR:
		return "{E}"
	case WARN:
		return "{W}"
	case INFO:
		return "{I}"
	case DBG_HIGH:
		return "{D2}"
	case DBG:
		return "{D1}"
	case DBG_ALL:
		return "{D0}"
	}
	return "{?}"
}
<|endoftext|>


<commit_before>package redis

import (
	"errors"
	"fmt"
	"strings"
)

type RedisKeyer interface {
	SetKey(ids ...string) *RedisKey
	Get() (result []byte, err error)
	Set(data []byte) (err error)
	Delete() (err error)
	String() string
}

type RedisKey struct {
	base       string
	fieldcount int
	hash       bool
	expire     bool
	lock       bool
	key        string
	hashid     string
	keyset     bool
}

var _ = RedisKeyer(&RedisKey{})

var (
	RedisKeyIndex = make(map[string]RedisKey)
	RedisKeys     = []RedisKey{
		{base: "index", fieldcount: 1, hash: true, expire: false, lock: true},
		{base: "thread", fieldcount: 2, hash: true, expire: false},
		{base: "tag", fieldcount: 2, hash: true, expire: true},
		{base: "image", fieldcount: 1, hash: true, expire: false},
		{base: "post", fieldcount: 2, hash: true, expire: false},
		{base: "tags", fieldcount: 1, hash: true, expire: false},
		{base: "directory", fieldcount: 1, hash: true, expire: false},
		{base: "new", fieldcount: 1, hash: false, expire: true},
		{base: "popular", fieldcount: 1, hash: false, expire: true},
		{base: "favorited", fieldcount: 1, hash: false, expire: true},
		{base: "tagtypes", fieldcount: 0, hash: false, expire: false},
		{base: "imageboards", fieldcount: 0, hash: false, expire: true},
	}
)

func init() {
	// key index map
	for _, key := range RedisKeys {
		RedisKeyIndex[key.base] = key
	}
}

// return a string version of the key
func (r *RedisKey) String() string {
	return r.key
}

// NewKey returns a key from the index or nil if it doesnt exist
func NewKey(name string) *RedisKey {
	key, ok := RedisKeyIndex[name]
	if !ok {
		return nil
	}
	return &key
}

// populates the fields in a key and sets the hash id
func (r *RedisKey) SetKey(ids ...string) *RedisKey {

	// set the key to the base if theres no fields
	if r.fieldcount == 0 {
		r.key = r.base
		return r
	}

	// create our key
	r.key = strings.Join([]string{r.base, strings.Join(ids[:r.fieldcount], ":")}, ":")

	// get our hash id
	if r.hash {
		r.hashid = strings.Join(ids[r.fieldcount:], "")
	}

	r.keyset = true

	return r
}

// gets a key, automatically handles hash types
func (r *RedisKey) Get() (result []byte, err error) {

	if !r.keyset {
		err = errors.New("Key is not set")
		return
	}

	if r.hash {
		return RedisCache.HGet(r.key, r.hashid)
	} else {
		return RedisCache.Get(r.key)
	}

	return
}

// sets a key, handles hash types and expiry
func (r *RedisKey) Set(data []byte) (err error) {

	if !r.keyset {
		return errors.New("Key is not set")
	}

	if r.hash {
		err = RedisCache.HMSet(r.key, r.hashid, data)
	} else {
		err = RedisCache.Set(r.key, data)
	}
	if err != nil {
		return
	}

	// unlock this key
	if r.lock {
		RedisCache.Unlock(fmt.Sprintf("%s:mutex", r.key))
	}

	// expire the key if set
	if r.expire {
		return RedisCache.Expire(r.key, 600)
	}

	return
}

// deletes a key
func (r *RedisKey) Delete() (err error) {

	if !r.keyset {
		return errors.New("Key is not set")
	}

	err = RedisCache.Delete(r.key)
	if err != nil {
		return
	}

	// lock this key
	if r.lock {
		RedisCache.Lock(fmt.Sprintf("%s:mutex", r.key))
	}

	return
}
<commit_msg>fix keyset with 0 fields<commit_after>package redis

import (
	"errors"
	"fmt"
	"strings"
)

type RedisKeyer interface {
	SetKey(ids ...string) *RedisKey
	Get() (result []byte, err error)
	Set(data []byte) (err error)
	Delete() (err error)
	String() string
}

type RedisKey struct {
	base       string
	fieldcount int
	hash       bool
	expire     bool
	lock       bool
	key        string
	hashid     string
	keyset     bool
}

var _ = RedisKeyer(&RedisKey{})

var (
	RedisKeyIndex = make(map[string]RedisKey)
	RedisKeys     = []RedisKey{
		{base: "index", fieldcount: 1, hash: true, expire: false, lock: true},
		{base: "thread", fieldcount: 2, hash: true, expire: false},
		{base: "tag", fieldcount: 2, hash: true, expire: true},
		{base: "image", fieldcount: 1, hash: true, expire: false},
		{base: "post", fieldcount: 2, hash: true, expire: false},
		{base: "tags", fieldcount: 1, hash: true, expire: false},
		{base: "directory", fieldcount: 1, hash: true, expire: false},
		{base: "new", fieldcount: 1, hash: false, expire: true},
		{base: "popular", fieldcount: 1, hash: false, expire: true},
		{base: "favorited", fieldcount: 1, hash: false, expire: true},
		{base: "tagtypes", fieldcount: 0, hash: false, expire: false},
		{base: "imageboards", fieldcount: 0, hash: false, expire: true},
	}
)

func init() {
	// key index map
	for _, key := range RedisKeys {
		RedisKeyIndex[key.base] = key
	}
}

// return a string version of the key
func (r *RedisKey) String() string {
	return r.key
}

// NewKey returns a key from the index or nil if it doesnt exist
func NewKey(name string) *RedisKey {
	key, ok := RedisKeyIndex[name]
	if !ok {
		return nil
	}
	return &key
}

// populates the fields in a key and sets the hash id
func (r *RedisKey) SetKey(ids ...string) *RedisKey {

	// set the key to the base if theres no fields
	if r.fieldcount == 0 {
		r.key = r.base
		r.keyset = true
		return r
	}

	// create our key
	r.key = strings.Join([]string{r.base, strings.Join(ids[:r.fieldcount], ":")}, ":")

	// get our hash id
	if r.hash {
		r.hashid = strings.Join(ids[r.fieldcount:], "")
	}

	r.keyset = true

	return r
}

// gets a key, automatically handles hash types
func (r *RedisKey) Get() (result []byte, err error) {

	if !r.keyset {
		err = errors.New("Key is not set")
		return
	}

	if r.hash {
		return RedisCache.HGet(r.key, r.hashid)
	} else {
		return RedisCache.Get(r.key)
	}

	return
}

// sets a key, handles hash types and expiry
func (r *RedisKey) Set(data []byte) (err error) {

	if !r.keyset {
		return errors.New("Key is not set")
	}

	if r.hash {
		err = RedisCache.HMSet(r.key, r.hashid, data)
	} else {
		err = RedisCache.Set(r.key, data)
	}
	if err != nil {
		return
	}

	// unlock this key
	if r.lock {
		RedisCache.Unlock(fmt.Sprintf("%s:mutex", r.key))
	}

	// expire the key if set
	if r.expire {
		return RedisCache.Expire(r.key, 600)
	}

	return
}

// deletes a key
func (r *RedisKey) Delete() (err error) {

	if !r.keyset {
		return errors.New("Key is not set")
	}

	err = RedisCache.Delete(r.key)
	if err != nil {
		return
	}

	// lock this key
	if r.lock {
		RedisCache.Lock(fmt.Sprintf("%s:mutex", r.key))
	}

	return
}
<|endoftext|>