<commit_before>package mpaccesslog

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Songmu/axslogparser"
	"github.com/Songmu/postailer"
	mp "github.com/mackerelio/go-mackerel-plugin"
	"github.com/mackerelio/golib/pluginutil"
	"github.com/montanaflynn/stats"
)

// AccesslogPlugin mackerel plugin
type AccesslogPlugin struct {
	prefix    string
	file      string
	posFile   string
	noPosFile bool
}

// MetricKeyPrefix interface for PluginWithPrefix
func (p *AccesslogPlugin) MetricKeyPrefix() string {
	if p.prefix == "" {
		p.prefix = "accesslog"
	}
	return p.prefix
}

// GraphDefinition interface for mackerelplugin
func (p *AccesslogPlugin) GraphDefinition() map[string]mp.Graphs {
	labelPrefix := strings.Title(p.prefix)
	return map[string]mp.Graphs{
		"access_num": {
			Label: labelPrefix + " Access Num",
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "total_count", Label: "Total Count"},
				{Name: "5xx_count", Label: "HTTP 5xx Count", Stacked: true},
				{Name: "4xx_count", Label: "HTTP 4xx Count", Stacked: true},
				{Name: "3xx_count", Label: "HTTP 3xx Count", Stacked: true},
				{Name: "2xx_count", Label: "HTTP 2xx Count", Stacked: true},
			},
		},
		"access_rate": {
			Label: labelPrefix + " Access Rate",
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "5xx_percentage", Label: "HTTP 5xx Percentage", Stacked: true},
				{Name: "4xx_percentage", Label: "HTTP 4xx Percentage", Stacked: true},
				{Name: "3xx_percentage", Label: "HTTP 3xx Percentage", Stacked: true},
				{Name: "2xx_percentage", Label: "HTTP 2xx Percentage", Stacked: true},
			},
		},
		"latency": {
			Label: labelPrefix + " Latency",
			Unit:  "float",
			Metrics: []mp.Metrics{
				{Name: "99_percentile", Label: "99 Percentile"},
				{Name: "95_percentile", Label: "95 Percentile"},
				{Name: "90_percentile", Label: "90 Percentile"},
				{Name: "average", Label: "Average"},
			},
		},
	}
}

func (p *AccesslogPlugin) getPosPath() string {
	base := p.file + ".pos.json"
	if p.posFile != "" {
		if filepath.IsAbs(p.posFile) {
			return p.posFile
		}
		base = p.posFile
	}
	return filepath.Join(pluginutil.PluginWorkDir(), "mackerel-plugin-accesslog.d", base)
}

func (p *AccesslogPlugin) getReadCloser() (io.ReadCloser, bool, error) {
	if p.noPosFile {
		rc, err := os.Open(p.file)
		return rc, true, err
	}
	posfile := p.getPosPath()
	fi, err := os.Stat(posfile)
	// don't output count metrics when the pos file doesn't exist or is too old
	takeCount := err == nil && fi.ModTime().After(time.Now().Add(-2*time.Minute))
	rc, err := postailer.Open(p.file, posfile)
	return rc, takeCount, err
}

// FetchMetrics interface for mackerelplugin
func (p *AccesslogPlugin) FetchMetrics() (map[string]float64, error) {
	rc, takeCount, err := p.getReadCloser()
	if err != nil {
		return nil, err
	}
	defer rc.Close()

	countMetrics := []string{"total_count", "2xx_count", "3xx_count", "4xx_count", "5xx_count"}
	ret := make(map[string]float64)
	for _, k := range countMetrics {
		ret[k] = 0
	}
	var reqtimes []float64
	var psr axslogparser.Parser
	s := bufio.NewScanner(rc)
	for s.Scan() {
		var (
			l   *axslogparser.Log
			err error
		)
		line := s.Text()
		if psr == nil {
			psr, l, err = axslogparser.GuessParser(line)
		} else {
			l, err = psr.Parse(line)
		}
		if err != nil {
			log.Println(err)
			continue
		}
		ret[string(fmt.Sprintf("%d", l.Status)[0])+"xx_count"]++
		ret["total_count"]++

		if l.ReqTime != nil {
			reqtimes = append(reqtimes, *l.ReqTime)
		} else if l.TakenSec != nil {
			reqtimes = append(reqtimes, *l.TakenSec)
		}
	}
	if s.Err() != nil {
		log.Println(s.Err())
	}
	if ret["total_count"] > 0 {
		for _, v := range []string{"2xx", "3xx", "4xx", "5xx"} {
			ret[v+"_percentage"] = ret[v+"_count"] * 100 / ret["total_count"]
		}
	}
	if len(reqtimes) > 0 {
		ret["average"], _ = stats.Mean(reqtimes)
		for _, v := range []int{90, 95, 99} {
			ret[fmt.Sprintf("%d", v)+"_percentile"], _ = stats.Percentile(reqtimes, float64(v))
		}
	}
	if !takeCount {
		for _, k := range countMetrics {
			delete(ret, k)
		}
	}
	return ret, nil
}

// Do the plugin
func Do() {
	var (
		optPrefix    = flag.String("metric-key-prefix", "", "Metric key prefix")
		optPosFile   = flag.String("posfile", "", "(not necessary to specify it in the usual use case) posfile")
		optNoPosFile = flag.Bool("no-posfile", false, "no position file")
	)
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [OPTION] /path/to/access.log\n", os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()
	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(1)
	}
	mp.NewMackerelPlugin(&AccesslogPlugin{
		prefix:    *optPrefix,
		file:      flag.Args()[0],
		posFile:   *optPosFile,
		noPosFile: *optNoPosFile,
	}).Run()
}
<commit_msg>[accesslog] care windows drive letter in `getPosPath`<commit_after>package mpaccesslog

import (
	"bufio"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/Songmu/axslogparser"
	"github.com/Songmu/postailer"
	mp "github.com/mackerelio/go-mackerel-plugin"
	"github.com/mackerelio/golib/pluginutil"
	"github.com/montanaflynn/stats"
)

// AccesslogPlugin mackerel plugin
type AccesslogPlugin struct {
	prefix    string
	file      string
	posFile   string
	noPosFile bool
}

// MetricKeyPrefix interface for PluginWithPrefix
func (p *AccesslogPlugin) MetricKeyPrefix() string {
	if p.prefix == "" {
		p.prefix = "accesslog"
	}
	return p.prefix
}

// GraphDefinition interface for mackerelplugin
func (p *AccesslogPlugin) GraphDefinition() map[string]mp.Graphs {
	labelPrefix := strings.Title(p.prefix)
	return map[string]mp.Graphs{
		"access_num": {
			Label: labelPrefix + " Access Num",
			Unit:  "integer",
			Metrics: []mp.Metrics{
				{Name: "total_count", Label: "Total Count"},
				{Name: "5xx_count", Label: "HTTP 5xx Count", Stacked: true},
				{Name: "4xx_count", Label: "HTTP 4xx Count", Stacked: true},
				{Name: "3xx_count", Label: "HTTP 3xx Count", Stacked: true},
				{Name: "2xx_count", Label: "HTTP 2xx Count", Stacked: true},
			},
		},
		"access_rate": {
			Label: labelPrefix + " Access Rate",
			Unit:  "percentage",
			Metrics: []mp.Metrics{
				{Name: "5xx_percentage", Label: "HTTP 5xx Percentage", Stacked: true},
				{Name: "4xx_percentage", Label: "HTTP 4xx Percentage", Stacked: true},
				{Name: "3xx_percentage", Label: "HTTP 3xx Percentage", Stacked: true},
				{Name: "2xx_percentage", Label: "HTTP 2xx Percentage", Stacked: true},
			},
		},
		"latency": {
			Label: labelPrefix + " Latency",
			Unit:  "float",
			Metrics: []mp.Metrics{
				{Name: "99_percentile", Label: "99 Percentile"},
				{Name: "95_percentile", Label: "95 Percentile"},
				{Name: "90_percentile", Label: "90 Percentile"},
				{Name: "average", Label: "Average"},
			},
		},
	}
}

var posRe = regexp.MustCompile(`^([a-zA-Z]):[/\\]`)

func (p *AccesslogPlugin) getPosPath() string {
	base := p.file + ".pos.json"
	if p.posFile != "" {
		if filepath.IsAbs(p.posFile) {
			return p.posFile
		}
		base = p.posFile
	}
	return filepath.Join(
		pluginutil.PluginWorkDir(),
		"mackerel-plugin-accesslog.d",
		posRe.ReplaceAllString(base, `$1`+string(filepath.Separator)),
	)
}

func (p *AccesslogPlugin) getReadCloser() (io.ReadCloser, bool, error) {
	if p.noPosFile {
		rc, err := os.Open(p.file)
		return rc, true, err
	}
	posfile := p.getPosPath()
	fi, err := os.Stat(posfile)
	// don't output count metrics when the pos file doesn't exist or is too old
	takeCount := err == nil && fi.ModTime().After(time.Now().Add(-2*time.Minute))
	rc, err := postailer.Open(p.file, posfile)
	return rc, takeCount, err
}

// FetchMetrics interface for mackerelplugin
func (p *AccesslogPlugin) FetchMetrics() (map[string]float64, error) {
	rc, takeCount, err := p.getReadCloser()
	if err != nil {
		return nil, err
	}
	defer rc.Close()

	countMetrics := []string{"total_count", "2xx_count", "3xx_count", "4xx_count", "5xx_count"}
	ret := make(map[string]float64)
	for _, k := range countMetrics {
		ret[k] = 0
	}
	var reqtimes []float64
	var psr axslogparser.Parser
	s := bufio.NewScanner(rc)
	for s.Scan() {
		var (
			l   *axslogparser.Log
			err error
		)
		line := s.Text()
		if psr == nil {
			psr, l, err = axslogparser.GuessParser(line)
		} else {
			l, err = psr.Parse(line)
		}
		if err != nil {
			log.Println(err)
			continue
		}
		ret[string(fmt.Sprintf("%d", l.Status)[0])+"xx_count"]++
		ret["total_count"]++

		if l.ReqTime != nil {
			reqtimes = append(reqtimes, *l.ReqTime)
		} else if l.TakenSec != nil {
			reqtimes = append(reqtimes, *l.TakenSec)
		}
	}
	if s.Err() != nil {
		log.Println(s.Err())
	}
	if ret["total_count"] > 0 {
		for _, v := range []string{"2xx", "3xx", "4xx", "5xx"} {
			ret[v+"_percentage"] = ret[v+"_count"] * 100 / ret["total_count"]
		}
	}
	if len(reqtimes) > 0 {
		ret["average"], _ = stats.Mean(reqtimes)
		for _, v := range []int{90, 95, 99} {
			ret[fmt.Sprintf("%d", v)+"_percentile"], _ = stats.Percentile(reqtimes, float64(v))
		}
	}
	if !takeCount {
		for _, k := range countMetrics {
			delete(ret, k)
		}
	}
	return ret, nil
}

// Do the plugin
func Do() {
	var (
		optPrefix    = flag.String("metric-key-prefix", "", "Metric key prefix")
		optPosFile   = flag.String("posfile", "", "(not necessary to specify it in the usual use case) posfile")
		optNoPosFile = flag.Bool("no-posfile", false, "no position file")
	)
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, "Usage: %s [OPTION] /path/to/access.log\n", os.Args[0])
		flag.PrintDefaults()
	}
	flag.Parse()
	if flag.NArg() < 1 {
		flag.Usage()
		os.Exit(1)
	}
	mp.NewMackerelPlugin(&AccesslogPlugin{
		prefix:    *optPrefix,
		file:      flag.Args()[0],
		posFile:   *optPosFile,
		noPosFile: *optNoPosFile,
	}).Run()
}
<|endoftext|>


<commit_before>package unsplash

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/ayoisaiah/stellar-photos-server/config"
	"github.com/ayoisaiah/stellar-photos-server/utils"
)

// UnsplashAPILocation represents the base URL for requests to Unsplash's API
const UnsplashAPILocation = "https://api.unsplash.com"

// download represents the result from triggering a download on a photo
type download struct {
	URL string `json:"url,omitempty"`
}

// searchResult represents the result for a search for photos.
type searchResult struct {
	Total      int           `json:"total,omitempty"`
	TotalPages int           `json:"total_pages,omitempty"`
	Results    []interface{} `json:"results,omitempty"`
}

// collection respresents a single Unsplash collection ID
type collection struct {
	ID string `json:"id,omitempty"`
}

// randomPhoto represents the result from fetching a random photo from Unsplash
type randomPhoto struct {
	ID             string `json:"id"`
	CreatedAt      string `json:"created_at"`
	UpdatedAt      string `json:"updated_at"`
	PromotedAt     string `json:"promoted_at"`
	Width          int    `json:"width"`
	Height         int    `json:"height"`
	Color          string `json:"color"`
	BlurHash       string `json:"blur_hash"`
	Description    string `json:"description"`
	AltDescription string `json:"alt_description"`
	Urls           struct {
		Raw     string `json:"raw"`
		Full    string `json:"full"`
		Regular string `json:"regular"`
		Small   string `json:"small"`
		Thumb   string `json:"thumb"`
		Custom  string `json:"custom"`
	} `json:"urls"`
	Links struct {
		Self             string `json:"self"`
		HTML             string `json:"html"`
		Download         string `json:"download"`
		DownloadLocation string `json:"download_location"`
	} `json:"links"`
	Categories             []interface{} `json:"categories"`
	Likes                  int           `json:"likes"`
	LikedByUser            bool          `json:"liked_by_user"`
	CurrentUserCollections []interface{} `json:"current_user_collections"`
	User                   struct {
		ID              string      `json:"id"`
		UpdatedAt       string      `json:"updated_at"`
		Username        string      `json:"username"`
		Name            string      `json:"name"`
		FirstName       string      `json:"first_name"`
		LastName        string      `json:"last_name"`
		TwitterUsername interface{} `json:"twitter_username"`
		PortfolioURL    string      `json:"portfolio_url"`
		Bio             string      `json:"bio"`
		Location        interface{} `json:"location"`
		Links           struct {
			Self      string `json:"self"`
			HTML      string `json:"html"`
			Photos    string `json:"photos"`
			Likes     string `json:"likes"`
			Portfolio string `json:"portfolio"`
			Following string `json:"following"`
			Followers string `json:"followers"`
		} `json:"links"`
		ProfileImage struct {
			Small  string `json:"small"`
			Medium string `json:"medium"`
			Large  string `json:"large"`
		} `json:"profile_image"`
		InstagramUsername string `json:"instagram_username"`
		TotalCollections  int    `json:"total_collections"`
		TotalLikes        int    `json:"total_likes"`
		TotalPhotos       int    `json:"total_photos"`
		AcceptedTos       bool   `json:"accepted_tos"`
	} `json:"user"`
	Exif struct {
		Make         string `json:"make"`
		Model        string `json:"model"`
		ExposureTime string `json:"exposure_time"`
		Aperture     string `json:"aperture"`
		FocalLength  string `json:"focal_length"`
		Iso          int    `json:"iso"`
	} `json:"exif"`
	Location struct {
		Title    string `json:"title"`
		Name     string `json:"name"`
		City     string `json:"city"`
		Country  string `json:"country"`
		Position struct {
			Latitude  float64 `json:"latitude"`
			Longitude float64 `json:"longitude"`
		} `json:"position"`
	} `json:"location"`
	Views     int `json:"views"`
	Downloads int `json:"downloads"`
}

// randomPhotoWithBase64 respresents the base64 encoding of randomPhoto
type randomPhotoBase64 struct {
	*randomPhoto
	Base64 string `json:"base64,omitempty"`
}

// DownloadPhoto is triggered each time a download is attempted
func DownloadPhoto(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	id := values.Get("id")
	if id == "" {
		return utils.NewHTTPError(
			nil,
			http.StatusBadRequest,
			"Photo ID must not be empty",
		)
	}

	_, err = TrackPhotoDownload(id)
	if err != nil {
		return err
	}

	w.WriteHeader(http.StatusOK)
	return nil
}

// TrackPhotoDownload is used to increment the number of downloads
// for the specified photo
func TrackPhotoDownload(id string) ([]byte, error) {
	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/photos/%s/download?client_id=%s",
		UnsplashAPILocation,
		id,
		unsplashAccessKey,
	)

	return utils.SendGETRequest(url, &download{})
}

// SearchUnsplash triggers a photo search and sends a single page of photo
// results for a query.
func SearchUnsplash(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	key := values.Get("key")
	page := values.Get("page")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/search/photos?page=%s&query=%s&per_page=%s&client_id=%s",
		UnsplashAPILocation,
		page,
		key,
		"28",
		unsplashAccessKey,
	)

	s := &searchResult{}

	bs, err := utils.SendGETRequest(url, s)
	if err != nil {
		return err
	}

	return utils.JSONResponse(w, bs)
}

// GetRandomPhoto retrives a single random photo using the provided collection
// IDs to narrow the pool of photos from which a random one will be chosen.
// If no collection IDs are present, it defaults to 998309 which is the ID of
// the official Stellar Photos collection
func GetRandomPhoto(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	collections := values.Get("collections")
	if collections == "" {
		collections = "998309"
	}

	resolution := values.Get("resolution")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/photos/random?collections=%s&client_id=%s",
		UnsplashAPILocation,
		collections,
		unsplashAccessKey,
	)

	res := &randomPhoto{}

	_, err = utils.SendGETRequest(url, res)
	if err != nil {
		return err
	}

	var imageWidth = "2000"
	switch resolution {
	case "high":
		imageWidth = "4000"
	case "max":
		imageWidth = strconv.Itoa(res.Width)
	}

	imageURL := res.Urls.Raw + "&w=" + imageWidth

	base64, err := utils.ImageURLToBase64(imageURL)
	if err != nil {
		return err
	}

	data := randomPhotoBase64{
		res,
		base64,
	}

	bytes, err := json.Marshal(data)
	if err != nil {
		return err
	}

	return utils.JSONResponse(w, bytes)
}

// ValidateCollections ensures that all the custom collection IDs that are added
// to the extension are valid
func ValidateCollections(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	collections := strings.Split(values.Get("collections"), ",")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey

	for _, value := range collections {
		valueToNum, err := strconv.Atoi(value)
		if err != nil {
			return utils.NewHTTPError(
				err,
				http.StatusBadRequest,
				"Collection ID must be a number",
			)
		}

		url := fmt.Sprintf(
			"%s/collections/%d/?client_id=%s",
			UnsplashAPILocation,
			valueToNum,
			unsplashAccessKey,
		)
		_, err = utils.SendGETRequest(url, &collection{})
		if err != nil {
			return err
		}
	}

	w.WriteHeader(http.StatusOK)
	return nil
}
<commit_msg>Ensure highest available resolution is used<commit_after>package unsplash

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"
	"strings"

	"github.com/ayoisaiah/stellar-photos-server/config"
	"github.com/ayoisaiah/stellar-photos-server/utils"
)

// UnsplashAPILocation represents the base URL for requests to Unsplash's API
const UnsplashAPILocation = "https://api.unsplash.com"

// download represents the result from triggering a download on a photo
type download struct {
	URL string `json:"url,omitempty"`
}

// searchResult represents the result for a search for photos.
type searchResult struct {
	Total      int           `json:"total,omitempty"`
	TotalPages int           `json:"total_pages,omitempty"`
	Results    []interface{} `json:"results,omitempty"`
}

// collection respresents a single Unsplash collection ID
type collection struct {
	ID string `json:"id,omitempty"`
}

// randomPhoto represents the result from fetching a random photo from Unsplash
type randomPhoto struct {
	ID             string `json:"id"`
	CreatedAt      string `json:"created_at"`
	UpdatedAt      string `json:"updated_at"`
	PromotedAt     string `json:"promoted_at"`
	Width          int    `json:"width"`
	Height         int    `json:"height"`
	Color          string `json:"color"`
	BlurHash       string `json:"blur_hash"`
	Description    string `json:"description"`
	AltDescription string `json:"alt_description"`
	Urls           struct {
		Raw     string `json:"raw"`
		Full    string `json:"full"`
		Regular string `json:"regular"`
		Small   string `json:"small"`
		Thumb   string `json:"thumb"`
		Custom  string `json:"custom"`
	} `json:"urls"`
	Links struct {
		Self             string `json:"self"`
		HTML             string `json:"html"`
		Download         string `json:"download"`
		DownloadLocation string `json:"download_location"`
	} `json:"links"`
	Categories             []interface{} `json:"categories"`
	Likes                  int           `json:"likes"`
	LikedByUser            bool          `json:"liked_by_user"`
	CurrentUserCollections []interface{} `json:"current_user_collections"`
	User                   struct {
		ID              string      `json:"id"`
		UpdatedAt       string      `json:"updated_at"`
		Username        string      `json:"username"`
		Name            string      `json:"name"`
		FirstName       string      `json:"first_name"`
		LastName        string      `json:"last_name"`
		TwitterUsername interface{} `json:"twitter_username"`
		PortfolioURL    string      `json:"portfolio_url"`
		Bio             string      `json:"bio"`
		Location        interface{} `json:"location"`
		Links           struct {
			Self      string `json:"self"`
			HTML      string `json:"html"`
			Photos    string `json:"photos"`
			Likes     string `json:"likes"`
			Portfolio string `json:"portfolio"`
			Following string `json:"following"`
			Followers string `json:"followers"`
		} `json:"links"`
		ProfileImage struct {
			Small  string `json:"small"`
			Medium string `json:"medium"`
			Large  string `json:"large"`
		} `json:"profile_image"`
		InstagramUsername string `json:"instagram_username"`
		TotalCollections  int    `json:"total_collections"`
		TotalLikes        int    `json:"total_likes"`
		TotalPhotos       int    `json:"total_photos"`
		AcceptedTos       bool   `json:"accepted_tos"`
	} `json:"user"`
	Exif struct {
		Make         string `json:"make"`
		Model        string `json:"model"`
		ExposureTime string `json:"exposure_time"`
		Aperture     string `json:"aperture"`
		FocalLength  string `json:"focal_length"`
		Iso          int    `json:"iso"`
	} `json:"exif"`
	Location struct {
		Title    string `json:"title"`
		Name     string `json:"name"`
		City     string `json:"city"`
		Country  string `json:"country"`
		Position struct {
			Latitude  float64 `json:"latitude"`
			Longitude float64 `json:"longitude"`
		} `json:"position"`
	} `json:"location"`
	Views     int `json:"views"`
	Downloads int `json:"downloads"`
}

// randomPhotoWithBase64 respresents the base64 encoding of randomPhoto
type randomPhotoBase64 struct {
	*randomPhoto
	Base64 string `json:"base64,omitempty"`
}

// DownloadPhoto is triggered each time a download is attempted
func DownloadPhoto(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	id := values.Get("id")
	if id == "" {
		return utils.NewHTTPError(
			nil,
			http.StatusBadRequest,
			"Photo ID must not be empty",
		)
	}

	_, err = TrackPhotoDownload(id)
	if err != nil {
		return err
	}

	w.WriteHeader(http.StatusOK)
	return nil
}

// TrackPhotoDownload is used to increment the number of downloads
// for the specified photo
func TrackPhotoDownload(id string) ([]byte, error) {
	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/photos/%s/download?client_id=%s",
		UnsplashAPILocation,
		id,
		unsplashAccessKey,
	)

	return utils.SendGETRequest(url, &download{})
}

// SearchUnsplash triggers a photo search and sends a single page of photo
// results for a query.
func SearchUnsplash(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	key := values.Get("key")
	page := values.Get("page")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/search/photos?page=%s&query=%s&per_page=%s&client_id=%s",
		UnsplashAPILocation,
		page,
		key,
		"28",
		unsplashAccessKey,
	)

	s := &searchResult{}

	bs, err := utils.SendGETRequest(url, s)
	if err != nil {
		return err
	}

	return utils.JSONResponse(w, bs)
}

// GetRandomPhoto retrives a single random photo using the provided collection
// IDs to narrow the pool of photos from which a random one will be chosen.
// If no collection IDs are present, it defaults to 998309 which is the ID of
// the official Stellar Photos collection
func GetRandomPhoto(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	collections := values.Get("collections")
	if collections == "" {
		collections = "998309"
	}

	resolution := values.Get("resolution")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey
	url := fmt.Sprintf(
		"%s/photos/random?collections=%s&client_id=%s",
		UnsplashAPILocation,
		collections,
		unsplashAccessKey,
	)

	res := &randomPhoto{}

	_, err = utils.SendGETRequest(url, res)
	if err != nil {
		return err
	}

	var imageWidth = "2000"
	switch resolution {
	case "high":
		highRes := 4000
		if res.Width >= highRes {
			imageWidth = "4000"
		} else {
			imageWidth = strconv.Itoa(res.Width)
		}
	case "max":
		imageWidth = strconv.Itoa(res.Width)
	}

	imageURL := res.Urls.Raw + "&w=" + imageWidth

	base64, err := utils.ImageURLToBase64(imageURL)
	if err != nil {
		return err
	}

	data := randomPhotoBase64{
		res,
		base64,
	}

	bytes, err := json.Marshal(data)
	if err != nil {
		return err
	}

	return utils.JSONResponse(w, bytes)
}

// ValidateCollections ensures that all the custom collection IDs that are added
// to the extension are valid
func ValidateCollections(w http.ResponseWriter, r *http.Request) error {
	values, err := utils.GetURLQueryParams(r.URL.String())
	if err != nil {
		return err
	}

	collections := strings.Split(values.Get("collections"), ",")

	unsplashAccessKey := config.Conf.Unsplash.AccessKey

	for _, value := range collections {
		valueToNum, err := strconv.Atoi(value)
		if err != nil {
			return utils.NewHTTPError(
				err,
				http.StatusBadRequest,
				"Collection ID must be a number",
			)
		}

		url := fmt.Sprintf(
			"%s/collections/%d/?client_id=%s",
			UnsplashAPILocation,
			valueToNum,
			unsplashAccessKey,
		)
		_, err = utils.SendGETRequest(url, &collection{})
		if err != nil {
			return err
		}
	}

	w.WriteHeader(http.StatusOK)
	return nil
}
<|endoftext|>


<commit_before>/*
 * Mini Copy, (C) 2014, 2015 Minio, Inc.
 *
 * 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 (
	"fmt"
	"time"

	"github.com/minio-io/cli"
	"github.com/minio-io/mc/pkg/client"
	"github.com/minio-io/mc/pkg/console"
	"github.com/minio-io/minio/pkg/iodine"
	"github.com/minio-io/minio/pkg/utils/log"
)

func runAccessCmd(ctx *cli.Context) {
	if !ctx.Args().Present() || ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "access", 1) // last argument is exit code
	}

	config, err := getMcConfig()
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalf("loading config file failed with following reason: [%s]\n", iodine.ToError(err))
	}

	targetURLConfigMap := make(map[string]*hostConfig)
	targetURLs, err := getURLs(ctx.Args(), config.Aliases)
	if err != nil {
		switch e := iodine.ToError(err).(type) {
		case errUnsupportedScheme:
			log.Debug.Println(iodine.New(err, nil))
			console.Fatalf("reading URL [%s] failed with following reason: [%s]\n", e.url, e)
		default:
			log.Debug.Println(iodine.New(err, nil))
			console.Fatalf("reading URLs failed with following reason: [%s]\n", e)
		}
	}
	acl := bucketACL(ctx.Args().First())
	if !acl.isValidBucketACL() {
		log.Debug.Println(iodine.New(errInvalidACL{acl: acl.String()}, nil))
		console.Fatalf("Access type [%s] is not supported. Valid types are [private, public, readonly].\n", acl)
	}
	targetURLs = targetURLs[1:] // 1 or more target URLs
	for _, targetURL := range targetURLs {
		targetConfig, err := getHostConfig(targetURL)
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Fatalf("Unable to read configuration for host [%s]. Reason: [%s].\n", targetURL, iodine.ToError(err))
		}
		targetURLConfigMap[targetURL] = targetConfig
	}
	for targetURL, targetConfig := range targetURLConfigMap {
		errorMsg, err := doUpdateAccessCmd(mcClientMethods{}, targetURL, acl.String(), targetConfig, globalDebugFlag)
		err = iodine.New(err, nil)
		if err != nil {
			if errorMsg == "" {
				errorMsg = "Empty error message.  Please rerun this command with --debug and file a bug report."
			}
			log.Debug.Println(err)
			console.Errorf("%s", errorMsg)
		}
	}
}

func doUpdateAccessCmd(methods clientMethods, targetURL, targetACL string, targetConfig *hostConfig, debug bool) (string, error) {
	var err error
	var clnt client.Client
	clnt, err = methods.getNewClient(targetURL, targetConfig, debug)
	if err != nil {
		err := iodine.New(err, nil)
		msg := fmt.Sprintf("Unable to initialize client for [%s]. Reason: [%s].\n",
			targetURL, iodine.ToError(err))
		return msg, err
	}
	return doUpdateAccess(clnt, targetURL, targetACL)
}

func doUpdateAccess(clnt client.Client, targetURL, targetACL string) (string, error) {
	err := clnt.PutBucket(targetACL)
	if err != nil && isValidRetry(err) {
		console.Infof("Retrying ...")
	}
	for i := 0; i < globalMaxRetryFlag && err != nil && isValidRetry(err); i++ {
		err = clnt.PutBucket(targetACL)
		console.Errorf(" %d", i)
		// Progressively longer delays
		time.Sleep(time.Duration(i*i) * time.Second)
	}
	if err != nil {
		err := iodine.New(err, nil)
		msg := fmt.Sprintf("Failed to add bucket access policy for URL [%s]. Reason: [%s].\n", targetURL, iodine.ToError(err))
		return msg, err
	}
	return "", nil
}
<commit_msg>Fix cmd-access retry code<commit_after>/*
 * Mini Copy, (C) 2014, 2015 Minio, Inc.
 *
 * 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 (
	"fmt"
	"time"

	"github.com/minio-io/cli"
	"github.com/minio-io/mc/pkg/client"
	"github.com/minio-io/mc/pkg/console"
	"github.com/minio-io/minio/pkg/iodine"
	"github.com/minio-io/minio/pkg/utils/log"
)

func runAccessCmd(ctx *cli.Context) {
	if !ctx.Args().Present() || ctx.Args().First() == "help" {
		cli.ShowCommandHelpAndExit(ctx, "access", 1) // last argument is exit code
	}

	config, err := getMcConfig()
	if err != nil {
		log.Debug.Println(iodine.New(err, nil))
		console.Fatalf("loading config file failed with following reason: [%s]\n", iodine.ToError(err))
	}

	targetURLConfigMap := make(map[string]*hostConfig)
	targetURLs, err := getURLs(ctx.Args(), config.Aliases)
	if err != nil {
		switch e := iodine.ToError(err).(type) {
		case errUnsupportedScheme:
			log.Debug.Println(iodine.New(err, nil))
			console.Fatalf("reading URL [%s] failed with following reason: [%s]\n", e.url, e)
		default:
			log.Debug.Println(iodine.New(err, nil))
			console.Fatalf("reading URLs failed with following reason: [%s]\n", e)
		}
	}
	acl := bucketACL(ctx.Args().First())
	if !acl.isValidBucketACL() {
		log.Debug.Println(iodine.New(errInvalidACL{acl: acl.String()}, nil))
		console.Fatalf("Access type [%s] is not supported. Valid types are [private, public, readonly].\n", acl)
	}
	targetURLs = targetURLs[1:] // 1 or more target URLs
	for _, targetURL := range targetURLs {
		targetConfig, err := getHostConfig(targetURL)
		if err != nil {
			log.Debug.Println(iodine.New(err, nil))
			console.Fatalf("Unable to read configuration for host [%s]. Reason: [%s].\n", targetURL, iodine.ToError(err))
		}
		targetURLConfigMap[targetURL] = targetConfig
	}
	for targetURL, targetConfig := range targetURLConfigMap {
		errorMsg, err := doUpdateAccessCmd(mcClientMethods{}, targetURL, acl.String(), targetConfig, globalDebugFlag)
		err = iodine.New(err, nil)
		if err != nil {
			if errorMsg == "" {
				errorMsg = "Empty error message.  Please rerun this command with --debug and file a bug report."
			}
			log.Debug.Println(err)
			console.Errorf("%s", errorMsg)
		}
	}
}

func doUpdateAccessCmd(methods clientMethods, targetURL, targetACL string, targetConfig *hostConfig, debug bool) (string, error) {
	var err error
	var clnt client.Client
	clnt, err = methods.getNewClient(targetURL, targetConfig, debug)
	if err != nil {
		err := iodine.New(err, nil)
		msg := fmt.Sprintf("Unable to initialize client for [%s]. Reason: [%s].\n",
			targetURL, iodine.ToError(err))
		return msg, err
	}
	return doUpdateAccess(clnt, targetURL, targetACL)
}

func doUpdateAccess(clnt client.Client, targetURL, targetACL string) (string, error) {
	err := clnt.PutBucket(targetACL)
	for i := 0; i < globalMaxRetryFlag && err != nil && isValidRetry(err); i++ {
		fmt.Println(console.Retry("Retrying ... %d", i))
		// Progressively longer delays
		time.Sleep(time.Duration(i*i) * time.Second)
		err = clnt.PutBucket(targetACL)
	}
	if err != nil {
		err := iodine.New(err, nil)
		msg := fmt.Sprintf("Failed to add bucket access policy for URL [%s]. Reason: [%s].\n", targetURL, iodine.ToError(err))
		return msg, err
	}
	return "", nil
}
<|endoftext|>


<commit_before>// Copyright © 2016 NAME HERE <EMAIL ADDRESS>
//
// 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 cmd

import (
	"fmt"

	"github.com/spf13/cobra"
)

// brokerCmd represents the broker command
var brokerCmd = &cobra.Command{
	Use:   "broker",
	Short: "Manage your brokers",
	Long: `A broker holds a number of partitions from differents topics.
You can manager them with this command`,
	Run: func(cmd *cobra.Command, args []string) {
		if apiURL == "" {
			fmt.Println("Cannot find kafka scheduler url. Using default: 0.0.0.0:7070")
		}
	},
}

func init() {
	RootCmd.AddCommand(brokerCmd)
}
<commit_msg>cmd: Brokers & client package integrated to broker cmd<commit_after>// Copyright © 2016 NAME HERE <EMAIL ADDRESS>
//
// 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 cmd

import (
	"fmt"
	"os"

	"github.com/eddyzags/kafkactl/api/client"
	"github.com/eddyzags/kafkactl/brokers"

	"github.com/spf13/cobra"
)

var (
	brokerListAll bool
)

// brokerCmd represents the broker command
var brokerCmd = &cobra.Command{
	Use:   "broker",
	Short: "Manage your brokers",
	Long: `A broker holds a number of partitions from differents topics.
You can manager them with this command`,
	Run: func(cmd *cobra.Command, args []string) {
		if apiURL == "" {
			fmt.Println("Cannot find kafka scheduler url. Using default: 0.0.0.0:7070")
		}

		c := client.NewClient(apiURL)

		if err := brokers.List(c, brokerListAll); err != nil {
			fmt.Fprintf(os.Stderr, "kafkactl: Unexpected error occured \"%v\"\n", err)
			os.Exit(1)
		}
	},
}

func init() {
	RootCmd.AddCommand(brokerCmd)

	brokerCmd.Flags().BoolVarP(&brokerListAll, "all", "a", false, "Do not ignore unactive brokers")
}
<|endoftext|>


<commit_before>package cmd

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path"
	"strings"

	"github.com/rancher/go-rancher/v2"

	"github.com/Sirupsen/logrus"
	"github.com/urfave/cli"
)

type Config struct {
	AccessKey   string `json:"accessKey"`
	SecretKey   string `json:"secretKey"`
	URL         string `json:"url"`
	Environment string `json:"environment"`
	Path        string `json:"path,omitempty"`
}

func baseURL(fullURL string) (string, error) {
	idx := strings.LastIndex(fullURL, "/v2-beta")
	if idx == -1 {
		u, err := url.Parse(fullURL)
		if err != nil {
			return "", err
		}
		newURL := url.URL{
			Scheme: u.Scheme,
			Host:   u.Host,
		}
		return newURL.String(), nil
	}
	return fullURL[:idx], nil
}

func (c Config) EnvironmentURL() (string, error) {
	projectID := c.Environment
	if projectID == "" || !strings.HasPrefix(projectID, "1a") {
		rancherClient, err := client.NewRancherClient(&client.ClientOpts{
			Url:       c.URL,
			AccessKey: c.AccessKey,
			SecretKey: c.SecretKey,
		})
		if err != nil {
			return "", err
		}
		project, err := GetEnvironment(c.Environment, rancherClient)
		if err != nil {
			return "", err
		}
		projectID = project.Id
	}

	url, err := baseURL(c.URL)
	if err != nil {
		return "", err
	}
	url = url + "/v2-beta/projects/" + projectID + "/schemas"
	return url, nil
}

func (c Config) Write() error {
	err := os.MkdirAll(path.Dir(c.Path), 0700)
	if err != nil {
		return err
	}

	logrus.Infof("Saving config to %s", c.Path)
	p := c.Path
	c.Path = ""
	output, err := os.Create(p)
	if err != nil {
		return err
	}
	defer output.Close()

	return json.NewEncoder(output).Encode(c)
}

func LoadConfig(path string) (Config, error) {
	config := Config{
		Path: path,
	}

	content, err := ioutil.ReadFile(path)
	if os.IsNotExist(err) {
		return config, nil
	} else if err != nil {
		return config, err
	}

	err = json.Unmarshal(content, &config)
	config.Path = path

	return config, err
}

func ConfigCommand() cli.Command {
	return cli.Command{
		Name:      "config",
		Usage:     "Setup client configuration",
		Action:    configSetup,
		ArgsUsage: "None",
		Flags: []cli.Flag{
			cli.BoolFlag{
				Name:  "print",
				Usage: "Print the current configuration",
			},
		},
	}
}

func getConfig(reader *bufio.Reader, text, def string) (string, error) {
	for {
		fmt.Printf("%s [%s]: ", text, def)
		input, err := reader.ReadString('\n')
		if err != nil {
			return "", err
		}
		input = strings.TrimSpace(input)

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

		if input == "" && def != "" {
			return def, nil
		}
	}
}

func configSetup(ctx *cli.Context) error {
	config, err := lookupConfig(ctx)
	if err != nil && err != errNoURL {
		return err
	}

	if ctx.Bool("print") {
		return json.NewEncoder(os.Stdout).Encode(config)
	}

	reader := bufio.NewReader(os.Stdin)

	config.URL, err = getConfig(reader, "URL", config.URL)
	if err != nil {
		return err
	}

	config.AccessKey, err = getConfig(reader, "Access Key", config.AccessKey)
	if err != nil {
		return err
	}

	config.SecretKey, err = getConfig(reader, "Secret Key", config.SecretKey)
	if err != nil {
		return err
	}

	c, err := client.NewRancherClient(&client.ClientOpts{
		Url:       config.URL,
		AccessKey: config.AccessKey,
		SecretKey: config.SecretKey,
	})
	if err != nil {
		return err
	}

	if schema, ok := c.GetSchemas().CheckSchema("schema"); ok {
		// Normalize URL
		config.URL = schema.Links["collection"]
	} else {
		return fmt.Errorf("Failed to find schema URL")
	}

	c, err = client.NewRancherClient(&client.ClientOpts{
		Url:       config.URL,
		AccessKey: config.AccessKey,
		SecretKey: config.SecretKey,
	})
	if err != nil {
		return err
	}

	project, err := GetEnvironment("", c)
	if err != errNoEnv {
		if err != nil {
			return err
		}
		config.Environment = project.Id
	}

	return config.Write()
}
<commit_msg>Add -p for config --print<commit_after>package cmd

import (
	"bufio"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net/url"
	"os"
	"path"
	"strings"

	"github.com/rancher/go-rancher/v2"

	"github.com/Sirupsen/logrus"
	"github.com/urfave/cli"
)

type Config struct {
	AccessKey   string `json:"accessKey"`
	SecretKey   string `json:"secretKey"`
	URL         string `json:"url"`
	Environment string `json:"environment"`
	Path        string `json:"path,omitempty"`
}

func baseURL(fullURL string) (string, error) {
	idx := strings.LastIndex(fullURL, "/v2-beta")
	if idx == -1 {
		u, err := url.Parse(fullURL)
		if err != nil {
			return "", err
		}
		newURL := url.URL{
			Scheme: u.Scheme,
			Host:   u.Host,
		}
		return newURL.String(), nil
	}
	return fullURL[:idx], nil
}

func (c Config) EnvironmentURL() (string, error) {
	projectID := c.Environment
	if projectID == "" || !strings.HasPrefix(projectID, "1a") {
		rancherClient, err := client.NewRancherClient(&client.ClientOpts{
			Url:       c.URL,
			AccessKey: c.AccessKey,
			SecretKey: c.SecretKey,
		})
		if err != nil {
			return "", err
		}
		project, err := GetEnvironment(c.Environment, rancherClient)
		if err != nil {
			return "", err
		}
		projectID = project.Id
	}

	url, err := baseURL(c.URL)
	if err != nil {
		return "", err
	}
	url = url + "/v2-beta/projects/" + projectID + "/schemas"
	return url, nil
}

func (c Config) Write() error {
	err := os.MkdirAll(path.Dir(c.Path), 0700)
	if err != nil {
		return err
	}

	logrus.Infof("Saving config to %s", c.Path)
	p := c.Path
	c.Path = ""
	output, err := os.Create(p)
	if err != nil {
		return err
	}
	defer output.Close()

	return json.NewEncoder(output).Encode(c)
}

func LoadConfig(path string) (Config, error) {
	config := Config{
		Path: path,
	}

	content, err := ioutil.ReadFile(path)
	if os.IsNotExist(err) {
		return config, nil
	} else if err != nil {
		return config, err
	}

	err = json.Unmarshal(content, &config)
	config.Path = path

	return config, err
}

func ConfigCommand() cli.Command {
	return cli.Command{
		Name:      "config",
		Usage:     "Setup client configuration",
		Action:    configSetup,
		ArgsUsage: "None",
		Flags: []cli.Flag{
			cli.BoolFlag{
				Name:  "print,p",
				Usage: "Print the current configuration",
			},
		},
	}
}

func getConfig(reader *bufio.Reader, text, def string) (string, error) {
	for {
		fmt.Printf("%s [%s]: ", text, def)
		input, err := reader.ReadString('\n')
		if err != nil {
			return "", err
		}
		input = strings.TrimSpace(input)

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

		if input == "" && def != "" {
			return def, nil
		}
	}
}

func configSetup(ctx *cli.Context) error {
	config, err := lookupConfig(ctx)
	if err != nil && err != errNoURL {
		return err
	}

	if ctx.Bool("print") {
		return json.NewEncoder(os.Stdout).Encode(config)
	}

	reader := bufio.NewReader(os.Stdin)

	config.URL, err = getConfig(reader, "URL", config.URL)
	if err != nil {
		return err
	}

	config.AccessKey, err = getConfig(reader, "Access Key", config.AccessKey)
	if err != nil {
		return err
	}

	config.SecretKey, err = getConfig(reader, "Secret Key", config.SecretKey)
	if err != nil {
		return err
	}

	c, err := client.NewRancherClient(&client.ClientOpts{
		Url:       config.URL,
		AccessKey: config.AccessKey,
		SecretKey: config.SecretKey,
	})
	if err != nil {
		return err
	}

	if schema, ok := c.GetSchemas().CheckSchema("schema"); ok {
		// Normalize URL
		config.URL = schema.Links["collection"]
	} else {
		return fmt.Errorf("Failed to find schema URL")
	}

	c, err = client.NewRancherClient(&client.ClientOpts{
		Url:       config.URL,
		AccessKey: config.AccessKey,
		SecretKey: config.SecretKey,
	})
	if err != nil {
		return err
	}

	project, err := GetEnvironment("", c)
	if err != errNoEnv {
		if err != nil {
			return err
		}
		config.Environment = project.Id
	}

	return config.Write()
}
<|endoftext|>


<commit_before>/*
Copyright 2021 Google LLC

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

    https://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 cmd defines command line utilities for ghpc
package cmd

import (
	"hpc-toolkit/pkg/config"
	"hpc-toolkit/pkg/reswriter"
	"log"

	"github.com/spf13/cobra"
)

func init() {
	createCmd.Flags().StringVarP(&yamlFilename, "config", "c", "",
		"Configuration file for the new blueprints")
	if err := createCmd.MarkFlagRequired("config"); err != nil {
		log.Fatalf("error while marking 'config' flag as required: %e", err)
	}
	rootCmd.AddCommand(createCmd)
}

var (
	yamlFilename string
	createCmd    = &cobra.Command{
		Use:   "create",
		Short: "Create a new blueprint.",
		Long:  "Create a new blueprint based on a provided YAML config.",
		Run:   runCreateCmd,
	}
)

func runCreateCmd(cmd *cobra.Command, args []string) {
	blueprintConfig := config.NewBlueprintConfig(yamlFilename)
	blueprintConfig.ExpandConfig()
	reswriter.WriteBlueprint(&blueprintConfig.Config)
}
<commit_msg>Remove the -c flag for create<commit_after>/*
Copyright 2021 Google LLC

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

    https://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 cmd defines command line utilities for ghpc
package cmd

import (
	"fmt"
	"hpc-toolkit/pkg/config"
	"hpc-toolkit/pkg/reswriter"

	"github.com/spf13/cobra"
)

func init() {
	createCmd.Flags().StringVarP(&yamlFilename, "config", "c", "",
		"Configuration file for the new blueprints")
	cobra.CheckErr(createCmd.Flags().MarkDeprecated("config",
		"please see the command usage for more details."))
	rootCmd.AddCommand(createCmd)
}

var (
	yamlFilename string
	createCmd    = &cobra.Command{
		Use:   "create FILENAME",
		Short: "Create a new blueprint.",
		Long:  "Create a new blueprint based on a provided YAML config.",
		Run:   runCreateCmd,
	}
)

func runCreateCmd(cmd *cobra.Command, args []string) {
	if yamlFilename == "" {
		if len(args) == 0 {
			fmt.Println(cmd.UsageString())
			return
		}

		yamlFilename = args[0]
	}

	blueprintConfig := config.NewBlueprintConfig(yamlFilename)
	blueprintConfig.ExpandConfig()
	reswriter.WriteBlueprint(&blueprintConfig.Config)
}
<|endoftext|>


<commit_before>// Copyright 2022 Google LLC
//
// 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
//
//     https://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 cmd

import (
	"errors"
)

var (
	errSigInt = &exitError{
		Err:  errors.New("SIGINT signal received"),
		Code: 130,
	}

	errSigTerm = &exitError{
		Err:  errors.New("SIGTERM signal received"),
		Code: 137,
	}
)

func newBadCommandError(msg string) error {
	return &exitError{
		Err:  errors.New(msg),
		Code: 1,
	}
}

// exitError is an error with an exit code, that's returned when the cmd exits.
// When possible, try to match these conventions: https://tldp.org/LDP/abs/html/exitcodes.html
type exitError struct {
	Code int
	Err  error
}

func (e *exitError) Error() string {
	if e.Err == nil {
		return "<missing error>"
	}
	return e.Err.Error()
}
<commit_msg>fix: return correct exit code on SIGTERM (#1530)<commit_after>// Copyright 2022 Google LLC
//
// 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
//
//     https://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 cmd

import (
	"errors"
)

var (
	errSigInt = &exitError{
		Err:  errors.New("SIGINT signal received"),
		Code: 130,
	}

	errSigTerm = &exitError{
		Err:  errors.New("SIGTERM signal received"),
		Code: 143,
	}
)

func newBadCommandError(msg string) error {
	return &exitError{
		Err:  errors.New(msg),
		Code: 1,
	}
}

// exitError is an error with an exit code, that's returned when the cmd exits.
// When possible, try to match these conventions: https://tldp.org/LDP/abs/html/exitcodes.html
type exitError struct {
	Code int
	Err  error
}

func (e *exitError) Error() string {
	if e.Err == nil {
		return "<missing error>"
	}
	return e.Err.Error()
}
<|endoftext|>


<commit_before>// Copyright 2015 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
	"fmt"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strings"

	"golang.org/x/crypto/ssh/terminal"
)

const (
	pattern  = "\033[%d;%d;%dm%s\033[0m"
	bgFactor = 10
)

var ignoredPatterns = []*regexp.Regexp{
	regexp.MustCompile("\033\\[\\d+;\\d+;\\d+m"),
	regexp.MustCompile("\033\\[0m"),
}

var fontColors = map[string]int{
	"black":   30,
	"red":     31,
	"green":   32,
	"yellow":  33,
	"blue":    34,
	"magenta": 35,
	"cyan":    36,
	"white":   37,
}

var fontEffects = map[string]int{
	"reset":   0,
	"bold":    1,
	"inverse": 7,
}

type Table struct {
	Headers       Row
	LineSeparator bool
	rows          rowSlice
}

type Row []string

func NewTable() *Table {
	return &Table{}
}

// Sort sorts the rows in the table using the first column as key.
func (t *Table) Sort() {
	sort.Sort(t.rows)
}

func (t *Table) Reverse() {
	sort.Sort(sort.Reverse(t.rows))
}

func (t *Table) SortByColumn(column int) {
	sort.Sort(rowSliceByColumn{rowSlice: t.rows, column: column})
}

func (t *Table) addRows(rows rowSlice, sizes []int, result string) string {
	for _, row := range rows {
		extraRows := rowSlice{}
		for column, field := range row {
			parts := strings.Split(field, "\n")
			field = parts[0]
			for i := range parts[1:] {
				var newRow Row
				if len(extraRows) > i {
					newRow = extraRows[i]
				} else {
					newRow = Row(make([]string, len(row)))
					extraRows.add(newRow)
				}
				newRow[column] = parts[i+1]
			}
			result += "| " + field
			result += strings.Repeat(" ", sizes[column]+1-runeLen(field))
		}
		result += "|\n"
		result = t.addRows(extraRows, sizes, result)
		ptr1 := reflect.ValueOf(rows).Pointer()
		ptr2 := reflect.ValueOf(t.rows).Pointer()
		if ptr1 == ptr2 && t.LineSeparator {
			result += t.separator()
		}
	}
	return result
}

func splitJoinEvery(str string, n int) string {
	breakOnAny := os.Getenv("TSURU_BREAK_ANY") != ""
	breakChars := []rune{' ', '.', ':', '='}
	n -= 1
	str = strings.TrimSpace(str)
	lines := strings.Split(str, "\n")
	var parts [][]rune
	for _, line := range lines {
		line = strings.TrimSpace(line)
		lineRunes := []rune(line)
		strLen := len(lineRunes)
		var start, end int
		for ; start < strLen; start = end {
			end = start + n
			for _, p := range ignoredPatterns {
				pos := p.FindStringIndex(string(lineRunes[start:]))
				if pos != nil && pos[0] < (end-start+1) {
					end += pos[1] - pos[0]
				}
			}
			if end > strLen {
				end = strLen
			}
			oldEnd := end
			skipSpace := false
			breakableChar := false
			if !breakOnAny && end < strLen {
				for ; end > start; end-- {
					for _, chr := range breakChars {
						if chr == lineRunes[end] {
							breakableChar = true
							if chr == ' ' {
								skipSpace = true
							} else if end < oldEnd {
								end++
							}
							break
						}
					}
					if breakableChar {
						break
					}
				}
				if !breakableChar {
					end = oldEnd
				}
			}
			part := make([]rune, end-start)
			copy(part, lineRunes[start:end])
			if breakableChar {
				padding := n - (end - start)
				if padding > 0 {
					part = append(part, []rune(strings.Repeat(" ", padding))...)
				}
				if skipSpace {
					end++
				}
			}
			if end < strLen {
				part = append(part, rune('↵'))
			}
			parts = append(parts, part)
		}
	}
	return redistributeColors(parts)
}

func redistributeColors(parts [][]rune) string {
	var result string
	var lastStartColor, nextResetStr string
	for _, part := range parts {
		nextStartColor := lastStartColor
		partStr := string(part)
		startPos := ignoredPatterns[0].FindStringIndex(partStr)
		resetPos := ignoredPatterns[1].FindStringIndex(partStr)
		if startPos != nil {
			if resetPos == nil || resetPos[0] < startPos[0] {
				nextStartColor = partStr[startPos[0]:startPos[1]]
				nextResetStr = "\033[0m"
			} else {
				nextStartColor = ""
			}
		}
		if resetPos != nil {
			if startPos == nil || resetPos[0] > startPos[0] {
				nextResetStr = ""
				nextStartColor = ""
			}
		}
		partStr = lastStartColor + partStr + nextResetStr
		lastStartColor = nextStartColor
		result += partStr + "\n"
	}
	return strings.TrimRight(result, "\n")
}

func (t *Table) resizeLastColumn(ttyWidth int) []int {
	sizes := t.columnsSize()
	if ttyWidth == 0 {
		return sizes
	}
	fullSize := 0
	toLastSize := 0
	for i, sz := range sizes {
		fullSize += sz
		if i != len(sizes)-1 {
			toLastSize += sz
		}
	}
	fullSize += len(sizes)*3 + 1
	toLastSize += (len(sizes)-1)*3 + 4
	available := ttyWidth - toLastSize
	if fullSize > ttyWidth && available > 1 {
		for _, row := range t.rows {
			row[len(sizes)-1] = splitJoinEvery(row[len(sizes)-1], available)
		}
	}
	return t.columnsSize()
}

func (t *Table) String() string {
	if t.Headers == nil && len(t.rows) < 1 {
		return ""
	}
	var ttyWidth int
	terminalFd := int(os.Stdout.Fd())
	if os.Getenv("TSURU_FORCE_WRAP") != "" {
		terminalFd = int(os.Stdin.Fd())
	}
	if terminal.IsTerminal(terminalFd) {
		ttyWidth, _, _ = terminal.GetSize(terminalFd)
	}
	sizes := t.resizeLastColumn(ttyWidth)
	result := t.separator()
	if t.Headers != nil {
		for column, header := range t.Headers {
			result += "| " + header
			result += strings.Repeat(" ", sizes[column]+1-len(header))
		}
		result += "|\n"
		result += t.separator()
	}
	result = t.addRows(t.rows, sizes, result)
	if !t.LineSeparator {
		result += t.separator()
	}
	return result
}

func (t *Table) Bytes() []byte {
	return []byte(t.String())
}

func (t *Table) AddRow(row Row) {
	t.rows.add(row)
}

func (t *Table) Rows() int {
	return t.rows.Len()
}

func runeLen(s string) int {
	for _, p := range ignoredPatterns {
		s = p.ReplaceAllString(s, "")
	}
	return len([]rune(s))
}

func (t *Table) columnsSize() []int {
	var columns int
	if t.Headers != nil {
		columns = len(t.Headers)
	} else {
		columns = len(t.rows[0])
	}
	sizes := make([]int, columns)
	for _, row := range t.rows {
		for i := 0; i < columns; i++ {
			rowParts := strings.Split(row[i], "\n")
			for _, part := range rowParts {
				partLen := runeLen(part)
				if partLen > sizes[i] {
					sizes[i] = partLen
				}
			}
		}
	}
	if t.Headers != nil {
		for i, header := range t.Headers {
			headerLen := runeLen(header)
			if headerLen > sizes[i] {
				sizes[i] = headerLen
			}
		}
	}
	return sizes
}

func (t *Table) separator() string {
	result := ""
	sizes := t.columnsSize()
	for i := 0; i < len(sizes); i++ {
		result = result + "+" + strings.Repeat("-", sizes[i]+2)
	}
	result = result + "+" + "\n"
	return result
}

type rowSlice []Row

type rowSliceByColumn struct {
	rowSlice
	column int
}

func (l rowSliceByColumn) Len() int {
	return len(l.rowSlice)
}

func (l rowSliceByColumn) Less(i, j int) bool {
	return strings.ToLower(l.rowSlice[i][l.column]) < strings.ToLower(l.rowSlice[j][l.column])
}

func (l rowSliceByColumn) Swap(i, j int) {
	l.rowSlice[i], l.rowSlice[j] = l.rowSlice[j], l.rowSlice[i]
}

func (l *rowSlice) add(r Row) {
	*l = append(*l, r)
}

func (l rowSlice) Len() int {
	return len(l)
}

func (l rowSlice) Less(i, j int) bool {
	return strings.ToLower(l[i][0]) < strings.ToLower(l[j][0])
}

func (l rowSlice) Swap(i, j int) {
	l[i], l[j] = l[j], l[i]
}

func Colorfy(msg string, fontcolor string, background string, effect string) string {
	return fmt.Sprintf(pattern, fontEffects[effect], fontColors[fontcolor], fontColors[background]+bgFactor, msg)
}
<commit_msg>cmd: trim only spaces on right when breaking table<commit_after>// Copyright 2016 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
	"fmt"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strings"
	"unicode"

	"golang.org/x/crypto/ssh/terminal"
)

const (
	pattern  = "\033[%d;%d;%dm%s\033[0m"
	bgFactor = 10
)

var ignoredPatterns = []*regexp.Regexp{
	regexp.MustCompile("\033\\[\\d+;\\d+;\\d+m"),
	regexp.MustCompile("\033\\[0m"),
}

var fontColors = map[string]int{
	"black":   30,
	"red":     31,
	"green":   32,
	"yellow":  33,
	"blue":    34,
	"magenta": 35,
	"cyan":    36,
	"white":   37,
}

var fontEffects = map[string]int{
	"reset":   0,
	"bold":    1,
	"inverse": 7,
}

type Table struct {
	Headers       Row
	LineSeparator bool
	rows          rowSlice
}

type Row []string

func NewTable() *Table {
	return &Table{}
}

// Sort sorts the rows in the table using the first column as key.
func (t *Table) Sort() {
	sort.Sort(t.rows)
}

func (t *Table) Reverse() {
	sort.Sort(sort.Reverse(t.rows))
}

func (t *Table) SortByColumn(column int) {
	sort.Sort(rowSliceByColumn{rowSlice: t.rows, column: column})
}

func (t *Table) addRows(rows rowSlice, sizes []int, result string) string {
	for _, row := range rows {
		extraRows := rowSlice{}
		for column, field := range row {
			parts := strings.Split(field, "\n")
			field = parts[0]
			for i := range parts[1:] {
				var newRow Row
				if len(extraRows) > i {
					newRow = extraRows[i]
				} else {
					newRow = Row(make([]string, len(row)))
					extraRows.add(newRow)
				}
				newRow[column] = parts[i+1]
			}
			result += "| " + field
			result += strings.Repeat(" ", sizes[column]+1-runeLen(field))
		}
		result += "|\n"
		result = t.addRows(extraRows, sizes, result)
		ptr1 := reflect.ValueOf(rows).Pointer()
		ptr2 := reflect.ValueOf(t.rows).Pointer()
		if ptr1 == ptr2 && t.LineSeparator {
			result += t.separator()
		}
	}
	return result
}

func splitJoinEvery(str string, n int) string {
	breakOnAny := os.Getenv("TSURU_BREAK_ANY") != ""
	breakChars := []rune{' ', '.', ':', '='}
	n -= 1
	str = strings.TrimRightFunc(str, unicode.IsSpace)
	lines := strings.Split(str, "\n")
	var parts [][]rune
	for _, line := range lines {
		line = strings.TrimRightFunc(line, unicode.IsSpace)
		lineRunes := []rune(line)
		strLen := len(lineRunes)
		var start, end int
		for ; start < strLen; start = end {
			end = start + n
			for _, p := range ignoredPatterns {
				pos := p.FindStringIndex(string(lineRunes[start:]))
				if pos != nil && pos[0] < (end-start+1) {
					end += pos[1] - pos[0]
				}
			}
			if end > strLen {
				end = strLen
			}
			oldEnd := end
			skipSpace := false
			breakableChar := false
			if !breakOnAny && end < strLen {
				for ; end > start; end-- {
					for _, chr := range breakChars {
						if chr == lineRunes[end] {
							breakableChar = true
							if chr == ' ' {
								skipSpace = true
							} else if end < oldEnd {
								end++
							}
							break
						}
					}
					if breakableChar {
						break
					}
				}
				if !breakableChar {
					end = oldEnd
				}
			}
			part := make([]rune, end-start)
			copy(part, lineRunes[start:end])
			if breakableChar {
				padding := n - (end - start)
				if padding > 0 {
					part = append(part, []rune(strings.Repeat(" ", padding))...)
				}
				if skipSpace {
					end++
				}
			}
			if end < strLen {
				part = append(part, rune('↵'))
			}
			parts = append(parts, part)
		}
	}
	return redistributeColors(parts)
}

func redistributeColors(parts [][]rune) string {
	var result string
	var lastStartColor, nextResetStr string
	for _, part := range parts {
		nextStartColor := lastStartColor
		partStr := string(part)
		startPos := ignoredPatterns[0].FindStringIndex(partStr)
		resetPos := ignoredPatterns[1].FindStringIndex(partStr)
		if startPos != nil {
			if resetPos == nil || resetPos[0] < startPos[0] {
				nextStartColor = partStr[startPos[0]:startPos[1]]
				nextResetStr = "\033[0m"
			} else {
				nextStartColor = ""
			}
		}
		if resetPos != nil {
			if startPos == nil || resetPos[0] > startPos[0] {
				nextResetStr = ""
				nextStartColor = ""
			}
		}
		partStr = lastStartColor + partStr + nextResetStr
		lastStartColor = nextStartColor
		result += partStr + "\n"
	}
	return strings.TrimRight(result, "\n")
}

func (t *Table) resizeLastColumn(ttyWidth int) []int {
	sizes := t.columnsSize()
	if ttyWidth == 0 {
		return sizes
	}
	fullSize := 0
	toLastSize := 0
	for i, sz := range sizes {
		fullSize += sz
		if i != len(sizes)-1 {
			toLastSize += sz
		}
	}
	fullSize += len(sizes)*3 + 1
	toLastSize += (len(sizes)-1)*3 + 4
	available := ttyWidth - toLastSize
	if fullSize > ttyWidth && available > 1 {
		for _, row := range t.rows {
			row[len(sizes)-1] = splitJoinEvery(row[len(sizes)-1], available)
		}
	}
	return t.columnsSize()
}

func (t *Table) String() string {
	if t.Headers == nil && len(t.rows) < 1 {
		return ""
	}
	var ttyWidth int
	terminalFd := int(os.Stdout.Fd())
	if os.Getenv("TSURU_FORCE_WRAP") != "" {
		terminalFd = int(os.Stdin.Fd())
	}
	if terminal.IsTerminal(terminalFd) {
		ttyWidth, _, _ = terminal.GetSize(terminalFd)
	}
	sizes := t.resizeLastColumn(ttyWidth)
	result := t.separator()
	if t.Headers != nil {
		for column, header := range t.Headers {
			result += "| " + header
			result += strings.Repeat(" ", sizes[column]+1-len(header))
		}
		result += "|\n"
		result += t.separator()
	}
	result = t.addRows(t.rows, sizes, result)
	if !t.LineSeparator {
		result += t.separator()
	}
	return result
}

func (t *Table) Bytes() []byte {
	return []byte(t.String())
}

func (t *Table) AddRow(row Row) {
	t.rows.add(row)
}

func (t *Table) Rows() int {
	return t.rows.Len()
}

func runeLen(s string) int {
	for _, p := range ignoredPatterns {
		s = p.ReplaceAllString(s, "")
	}
	return len([]rune(s))
}

func (t *Table) columnsSize() []int {
	var columns int
	if t.Headers != nil {
		columns = len(t.Headers)
	} else {
		columns = len(t.rows[0])
	}
	sizes := make([]int, columns)
	for _, row := range t.rows {
		for i := 0; i < columns; i++ {
			rowParts := strings.Split(row[i], "\n")
			for _, part := range rowParts {
				partLen := runeLen(part)
				if partLen > sizes[i] {
					sizes[i] = partLen
				}
			}
		}
	}
	if t.Headers != nil {
		for i, header := range t.Headers {
			headerLen := runeLen(header)
			if headerLen > sizes[i] {
				sizes[i] = headerLen
			}
		}
	}
	return sizes
}

func (t *Table) separator() string {
	result := ""
	sizes := t.columnsSize()
	for i := 0; i < len(sizes); i++ {
		result = result + "+" + strings.Repeat("-", sizes[i]+2)
	}
	result = result + "+" + "\n"
	return result
}

type rowSlice []Row

type rowSliceByColumn struct {
	rowSlice
	column int
}

func (l rowSliceByColumn) Len() int {
	return len(l.rowSlice)
}

func (l rowSliceByColumn) Less(i, j int) bool {
	return strings.ToLower(l.rowSlice[i][l.column]) < strings.ToLower(l.rowSlice[j][l.column])
}

func (l rowSliceByColumn) Swap(i, j int) {
	l.rowSlice[i], l.rowSlice[j] = l.rowSlice[j], l.rowSlice[i]
}

func (l *rowSlice) add(r Row) {
	*l = append(*l, r)
}

func (l rowSlice) Len() int {
	return len(l)
}

func (l rowSlice) Less(i, j int) bool {
	return strings.ToLower(l[i][0]) < strings.ToLower(l[j][0])
}

func (l rowSlice) Swap(i, j int) {
	l[i], l[j] = l[j], l[i]
}

func Colorfy(msg string, fontcolor string, background string, effect string) string {
	return fmt.Sprintf(pattern, fontEffects[effect], fontColors[fontcolor], fontColors[background]+bgFactor, msg)
}
<|endoftext|>


<commit_before>// Copyright 2012 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
	"fmt"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strings"
	"unicode"

	"golang.org/x/crypto/ssh/terminal"
)

const (
	pattern  = "\033[%d;%d;%dm%s\033[0m"
	bgFactor = 10
)

var ignoredPatterns = []*regexp.Regexp{
	regexp.MustCompile("\033\\[\\d+;\\d+;\\d+m"),
	regexp.MustCompile("\033\\[0m"),
}

var fontColors = map[string]int{
	"black":   30,
	"red":     31,
	"green":   32,
	"yellow":  33,
	"blue":    34,
	"magenta": 35,
	"cyan":    36,
	"white":   37,
}

var fontEffects = map[string]int{
	"reset":   0,
	"bold":    1,
	"inverse": 7,
}

type Table struct {
	Headers       Row
	LineSeparator bool
	rows          rowSlice
}

type Row []string

func NewTable() *Table {
	return &Table{}
}

// Sort sorts the rows in the table using the first column as key.
func (t *Table) Sort() {
	sort.Sort(t.rows)
}

func (t *Table) Reverse() {
	sort.Sort(sort.Reverse(t.rows))
}

func (t *Table) SortByColumn(column int) {
	sort.Sort(rowSliceByColumn{rowSlice: t.rows, column: column})
}

func (t *Table) addRows(rows rowSlice, sizes []int, result string) string {
	for _, row := range rows {
		extraRows := rowSlice{}
		for column, field := range row {
			parts := strings.Split(field, "\n")
			field = parts[0]
			for i := range parts[1:] {
				var newRow Row
				if len(extraRows) > i {
					newRow = extraRows[i]
				} else {
					newRow = Row(make([]string, len(row)))
					extraRows.add(newRow)
				}
				newRow[column] = parts[i+1]
			}
			result += "| " + field
			result += strings.Repeat(" ", sizes[column]+1-runeLen(field))
		}
		result += "|\n"
		result = t.addRows(extraRows, sizes, result)
		ptr1 := reflect.ValueOf(rows).Pointer()
		ptr2 := reflect.ValueOf(t.rows).Pointer()
		if ptr1 == ptr2 && t.LineSeparator {
			result += t.separator()
		}
	}
	return result
}

func splitJoinEvery(str string, n int) string {
	breakOnAny := os.Getenv("TSURU_BREAK_ANY") != ""
	breakChars := []rune{' ', '.', ':', '='}
	n -= 1
	str = strings.TrimRightFunc(str, unicode.IsSpace)
	lines := strings.Split(str, "\n")
	var parts [][]rune
	for _, line := range lines {
		line = strings.TrimRightFunc(line, unicode.IsSpace)
		lineRunes := []rune(line)
		strLen := len(lineRunes)
		var start, end int
		for ; start < strLen; start = end {
			end = start + n
			for _, p := range ignoredPatterns {
				pos := p.FindStringIndex(string(lineRunes[start:]))
				if pos != nil && pos[0] < (end-start+1) {
					end += pos[1] - pos[0]
				}
			}
			if end > strLen {
				end = strLen
			}
			oldEnd := end
			skipSpace := false
			breakableChar := false
			if !breakOnAny && end < strLen {
				for ; end > start; end-- {
					for _, chr := range breakChars {
						if chr == lineRunes[end] {
							breakableChar = true
							if chr == ' ' {
								skipSpace = true
							} else if end < oldEnd {
								end++
							}
							break
						}
					}
					if breakableChar {
						break
					}
				}
				if !breakableChar {
					end = oldEnd
				}
			}
			part := make([]rune, end-start)
			copy(part, lineRunes[start:end])
			if breakableChar {
				padding := n - (end - start)
				if padding > 0 {
					part = append(part, []rune(strings.Repeat(" ", padding))...)
				}
				if skipSpace {
					end++
				}
			}
			if end < strLen {
				part = append(part, rune('↵'))
			}
			parts = append(parts, part)
		}
	}
	return redistributeColors(parts)
}

func redistributeColors(parts [][]rune) string {
	var result string
	var lastStartColor, nextResetStr string
	for _, part := range parts {
		nextStartColor := lastStartColor
		partStr := string(part)
		startPos := ignoredPatterns[0].FindStringIndex(partStr)
		resetPos := ignoredPatterns[1].FindStringIndex(partStr)
		if startPos != nil {
			if resetPos == nil || resetPos[0] < startPos[0] {
				nextStartColor = partStr[startPos[0]:startPos[1]]
				nextResetStr = "\033[0m"
			} else {
				nextStartColor = ""
			}
		}
		if resetPos != nil {
			if startPos == nil || resetPos[0] > startPos[0] {
				nextResetStr = ""
				nextStartColor = ""
			}
		}
		partStr = lastStartColor + partStr + nextResetStr
		lastStartColor = nextStartColor
		result += partStr + "\n"
	}
	return strings.TrimRight(result, "\n")
}

func (t *Table) resizeLastColumn(ttyWidth int) []int {
	sizes := t.columnsSize()
	if ttyWidth == 0 {
		return sizes
	}
	fullSize := 0
	toLastSize := 0
	for i, sz := range sizes {
		fullSize += sz
		if i != len(sizes)-1 {
			toLastSize += sz
		}
	}
	fullSize += len(sizes)*3 + 1
	toLastSize += (len(sizes)-1)*3 + 4
	available := ttyWidth - toLastSize
	if fullSize > ttyWidth && available > 1 {
		for _, row := range t.rows {
			row[len(sizes)-1] = splitJoinEvery(row[len(sizes)-1], available)
		}
	}
	return t.columnsSize()
}

func (t *Table) String() string {
	if t.Headers == nil && len(t.rows) < 1 {
		return ""
	}
	var ttyWidth int
	terminalFd := int(os.Stdout.Fd())
	if os.Getenv("TSURU_FORCE_WRAP") != "" {
		terminalFd = int(os.Stdin.Fd())
	}
	if terminal.IsTerminal(terminalFd) {
		ttyWidth, _, _ = terminal.GetSize(terminalFd)
	}
	sizes := t.resizeLastColumn(ttyWidth)
	result := t.separator()
	if t.Headers != nil {
		for column, header := range t.Headers {
			result += "| " + header
			result += strings.Repeat(" ", sizes[column]+1-len(header))
		}
		result += "|\n"
		result += t.separator()
	}
	result = t.addRows(t.rows, sizes, result)
	if !t.LineSeparator {
		result += t.separator()
	}
	return result
}

func (t *Table) Bytes() []byte {
	return []byte(t.String())
}

func (t *Table) AddRow(row Row) {
	t.rows.add(row)
}

func (t *Table) Rows() int {
	return t.rows.Len()
}

func runeLen(s string) int {
	for _, p := range ignoredPatterns {
		s = p.ReplaceAllString(s, "")
	}
	return len([]rune(s))
}

func (t *Table) columnsSize() []int {
	var columns int
	if t.Headers != nil {
		columns = len(t.Headers)
	} else {
		columns = len(t.rows[0])
	}
	sizes := make([]int, columns)
	for _, row := range t.rows {
		for i := 0; i < columns; i++ {
			rowParts := strings.Split(row[i], "\n")
			for _, part := range rowParts {
				partLen := runeLen(part)
				if partLen > sizes[i] {
					sizes[i] = partLen
				}
			}
		}
	}
	if t.Headers != nil {
		for i, header := range t.Headers {
			headerLen := runeLen(header)
			if headerLen > sizes[i] {
				sizes[i] = headerLen
			}
		}
	}
	return sizes
}

func (t *Table) separator() string {
	result := ""
	sizes := t.columnsSize()
	for i := 0; i < len(sizes); i++ {
		result = result + "+" + strings.Repeat("-", sizes[i]+2)
	}
	result = result + "+" + "\n"
	return result
}

type rowSlice []Row

type rowSliceByColumn struct {
	rowSlice
	column int
}

func (l rowSliceByColumn) Len() int {
	return len(l.rowSlice)
}

func (l rowSliceByColumn) Less(i, j int) bool {
	return strings.ToLower(l.rowSlice[i][l.column]) < strings.ToLower(l.rowSlice[j][l.column])
}

func (l rowSliceByColumn) Swap(i, j int) {
	l.rowSlice[i], l.rowSlice[j] = l.rowSlice[j], l.rowSlice[i]
}

func (l *rowSlice) add(r Row) {
	*l = append(*l, r)
}

func (l rowSlice) Len() int {
	return len(l)
}

func (l rowSlice) Less(i, j int) bool {
	return strings.ToLower(l[i][0]) < strings.ToLower(l[j][0])
}

func (l rowSlice) Swap(i, j int) {
	l[i], l[j] = l[j], l[i]
}

func Colorfy(msg string, fontcolor string, background string, effect string) string {
	if os.Getenv("TSURU_DISABLE_COLORS") != "" {
		return msg
	}
	return fmt.Sprintf(pattern, fontEffects[effect], fontColors[fontcolor], fontColors[background]+bgFactor, msg)
}
<commit_msg>cmd: allow sort by multiple columns<commit_after>// Copyright 2012 tsuru authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package cmd

import (
	"fmt"
	"os"
	"reflect"
	"regexp"
	"sort"
	"strings"
	"unicode"

	"golang.org/x/crypto/ssh/terminal"
)

const (
	pattern  = "\033[%d;%d;%dm%s\033[0m"
	bgFactor = 10
)

var ignoredPatterns = []*regexp.Regexp{
	regexp.MustCompile("\033\\[\\d+;\\d+;\\d+m"),
	regexp.MustCompile("\033\\[0m"),
}

var fontColors = map[string]int{
	"black":   30,
	"red":     31,
	"green":   32,
	"yellow":  33,
	"blue":    34,
	"magenta": 35,
	"cyan":    36,
	"white":   37,
}

var fontEffects = map[string]int{
	"reset":   0,
	"bold":    1,
	"inverse": 7,
}

type Table struct {
	Headers       Row
	LineSeparator bool
	rows          rowSlice
}

type Row []string

func NewTable() *Table {
	return &Table{}
}

// Sort sorts the rows in the table using the first column as key.
func (t *Table) Sort() {
	sort.Sort(t.rows)
}

func (t *Table) Reverse() {
	sort.Sort(sort.Reverse(t.rows))
}

func (t *Table) SortByColumn(columns ...int) {
	sort.Sort(rowSliceByColumn{rowSlice: t.rows, columns: columns})
}

func (t *Table) addRows(rows rowSlice, sizes []int, result string) string {
	for _, row := range rows {
		extraRows := rowSlice{}
		for column, field := range row {
			parts := strings.Split(field, "\n")
			field = parts[0]
			for i := range parts[1:] {
				var newRow Row
				if len(extraRows) > i {
					newRow = extraRows[i]
				} else {
					newRow = Row(make([]string, len(row)))
					extraRows.add(newRow)
				}
				newRow[column] = parts[i+1]
			}
			result += "| " + field
			result += strings.Repeat(" ", sizes[column]+1-runeLen(field))
		}
		result += "|\n"
		result = t.addRows(extraRows, sizes, result)
		ptr1 := reflect.ValueOf(rows).Pointer()
		ptr2 := reflect.ValueOf(t.rows).Pointer()
		if ptr1 == ptr2 && t.LineSeparator {
			result += t.separator()
		}
	}
	return result
}

func splitJoinEvery(str string, n int) string {
	breakOnAny := os.Getenv("TSURU_BREAK_ANY") != ""
	breakChars := []rune{' ', '.', ':', '='}
	n -= 1
	str = strings.TrimRightFunc(str, unicode.IsSpace)
	lines := strings.Split(str, "\n")
	var parts [][]rune
	for _, line := range lines {
		line = strings.TrimRightFunc(line, unicode.IsSpace)
		lineRunes := []rune(line)
		strLen := len(lineRunes)
		var start, end int
		for ; start < strLen; start = end {
			end = start + n
			for _, p := range ignoredPatterns {
				pos := p.FindStringIndex(string(lineRunes[start:]))
				if pos != nil && pos[0] < (end-start+1) {
					end += pos[1] - pos[0]
				}
			}
			if end > strLen {
				end = strLen
			}
			oldEnd := end
			skipSpace := false
			breakableChar := false
			if !breakOnAny && end < strLen {
				for ; end > start; end-- {
					for _, chr := range breakChars {
						if chr == lineRunes[end] {
							breakableChar = true
							if chr == ' ' {
								skipSpace = true
							} else if end < oldEnd {
								end++
							}
							break
						}
					}
					if breakableChar {
						break
					}
				}
				if !breakableChar {
					end = oldEnd
				}
			}
			part := make([]rune, end-start)
			copy(part, lineRunes[start:end])
			if breakableChar {
				padding := n - (end - start)
				if padding > 0 {
					part = append(part, []rune(strings.Repeat(" ", padding))...)
				}
				if skipSpace {
					end++
				}
			}
			if end < strLen {
				part = append(part, rune('↵'))
			}
			parts = append(parts, part)
		}
	}
	return redistributeColors(parts)
}

func redistributeColors(parts [][]rune) string {
	var result string
	var lastStartColor, nextResetStr string
	for _, part := range parts {
		nextStartColor := lastStartColor
		partStr := string(part)
		startPos := ignoredPatterns[0].FindStringIndex(partStr)
		resetPos := ignoredPatterns[1].FindStringIndex(partStr)
		if startPos != nil {
			if resetPos == nil || resetPos[0] < startPos[0] {
				nextStartColor = partStr[startPos[0]:startPos[1]]
				nextResetStr = "\033[0m"
			} else {
				nextStartColor = ""
			}
		}
		if resetPos != nil {
			if startPos == nil || resetPos[0] > startPos[0] {
				nextResetStr = ""
				nextStartColor = ""
			}
		}
		partStr = lastStartColor + partStr + nextResetStr
		lastStartColor = nextStartColor
		result += partStr + "\n"
	}
	return strings.TrimRight(result, "\n")
}

func (t *Table) resizeLastColumn(ttyWidth int) []int {
	sizes := t.columnsSize()
	if ttyWidth == 0 {
		return sizes
	}
	fullSize := 0
	toLastSize := 0
	for i, sz := range sizes {
		fullSize += sz
		if i != len(sizes)-1 {
			toLastSize += sz
		}
	}
	fullSize += len(sizes)*3 + 1
	toLastSize += (len(sizes)-1)*3 + 4
	available := ttyWidth - toLastSize
	if fullSize > ttyWidth && available > 1 {
		for _, row := range t.rows {
			row[len(sizes)-1] = splitJoinEvery(row[len(sizes)-1], available)
		}
	}
	return t.columnsSize()
}

func (t *Table) String() string {
	if t.Headers == nil && len(t.rows) < 1 {
		return ""
	}
	var ttyWidth int
	terminalFd := int(os.Stdout.Fd())
	if os.Getenv("TSURU_FORCE_WRAP") != "" {
		terminalFd = int(os.Stdin.Fd())
	}
	if terminal.IsTerminal(terminalFd) {
		ttyWidth, _, _ = terminal.GetSize(terminalFd)
	}
	sizes := t.resizeLastColumn(ttyWidth)
	result := t.separator()
	if t.Headers != nil {
		for column, header := range t.Headers {
			result += "| " + header
			result += strings.Repeat(" ", sizes[column]+1-len(header))
		}
		result += "|\n"
		result += t.separator()
	}
	result = t.addRows(t.rows, sizes, result)
	if !t.LineSeparator {
		result += t.separator()
	}
	return result
}

func (t *Table) Bytes() []byte {
	return []byte(t.String())
}

func (t *Table) AddRow(row Row) {
	t.rows.add(row)
}

func (t *Table) Rows() int {
	return t.rows.Len()
}

func runeLen(s string) int {
	for _, p := range ignoredPatterns {
		s = p.ReplaceAllString(s, "")
	}
	return len([]rune(s))
}

func (t *Table) columnsSize() []int {
	var columns int
	if t.Headers != nil {
		columns = len(t.Headers)
	} else {
		columns = len(t.rows[0])
	}
	sizes := make([]int, columns)
	for _, row := range t.rows {
		for i := 0; i < columns; i++ {
			rowParts := strings.Split(row[i], "\n")
			for _, part := range rowParts {
				partLen := runeLen(part)
				if partLen > sizes[i] {
					sizes[i] = partLen
				}
			}
		}
	}
	if t.Headers != nil {
		for i, header := range t.Headers {
			headerLen := runeLen(header)
			if headerLen > sizes[i] {
				sizes[i] = headerLen
			}
		}
	}
	return sizes
}

func (t *Table) separator() string {
	result := ""
	sizes := t.columnsSize()
	for i := 0; i < len(sizes); i++ {
		result = result + "+" + strings.Repeat("-", sizes[i]+2)
	}
	result = result + "+" + "\n"
	return result
}

type rowSlice []Row

type rowSliceByColumn struct {
	rowSlice
	columns []int
}

func (l rowSliceByColumn) Len() int {
	return len(l.rowSlice)
}

func (l rowSliceByColumn) Less(i, j int) bool {
	for _, c := range l.columns {
		v1, v2 := strings.ToLower(l.rowSlice[i][c]), strings.ToLower(l.rowSlice[j][c])
		if v1 == v2 {
			continue
		}
		return v1 < v2
	}
	return false
}

func (l rowSliceByColumn) Swap(i, j int) {
	l.rowSlice[i], l.rowSlice[j] = l.rowSlice[j], l.rowSlice[i]
}

func (l *rowSlice) add(r Row) {
	*l = append(*l, r)
}

func (l rowSlice) Len() int {
	return len(l)
}

func (l rowSlice) Less(i, j int) bool {
	return strings.ToLower(l[i][0]) < strings.ToLower(l[j][0])
}

func (l rowSlice) Swap(i, j int) {
	l[i], l[j] = l[j], l[i]
}

func Colorfy(msg string, fontcolor string, background string, effect string) string {
	if os.Getenv("TSURU_DISABLE_COLORS") != "" {
		return msg
	}
	return fmt.Sprintf(pattern, fontEffects[effect], fontColors[fontcolor], fontColors[background]+bgFactor, msg)
}
<|endoftext|>


<commit_before>package cmd

import (
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/go-macaron/switcher"
	"github.com/jacobhands/multiweb/cmd/flag"
	"gopkg.in/macaron.v1"
)

// CmdServer will serve the website
var CmdServer = cli.Command{
	Name:        "server",
	ShortName:   "s",
	Description: "Serves up websites",
	Action:      runCmdServer,
	Flags:       cmdServerFlags,
}

// Flags
var cmdServerFlags = []cli.Flag{
	cli.StringFlag{
		Name:  flag.Folder,
		Value: "./sites",
		Usage: "Folder containing websites. Eg. foo.com and bar.com",
	},
	cli.StringFlag{
		Name:  flag.BaseURL,
		Value: "sites.example.com",
		Usage: "The base domain to route with.",
	},
	cli.IntFlag{
		Name:  flag.Port,
		Value: 8080,
		Usage: "The port to listen to http requests on.",
	},
}

func runCmdServer(ctx *cli.Context) {
	println("Serving files!", ctx.String(flag.Folder), ctx.IsSet(flag.Folder))
	baseURL := ctx.String(flag.BaseURL)
	folder := ctx.String(flag.Folder)
	m := macaron.Classic()
	hs := switcher.NewHostSwitcher()
	// Set instance corresponding to host address.
	hs.Set("*."+baseURL, m)
	// http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc")))
	fss := fileServerSet{}
	files, _ := ioutil.ReadDir(folder)
	for _, f := range files {
		if f.IsDir() {
			domain := f.Name()
			folderRoot := folder + "/" + f.Name()
			println("NEW FOLDER ROOT: " + folderRoot)
			newFss := fileServer{
				Domain:     domain,
				FolderRoot: folderRoot,
				ServeHTTP: func(w http.ResponseWriter, r *http.Request) {
					println(r.RequestURI)
					dir := folderRoot + "/www/"
					http.FileServer(http.Dir(dir)).ServeHTTP(w, r)
				},
			}
			fss.FileServers = append(fss.FileServers, newFss)
		}
	}
	for i, v := range fss.FileServers {
		fss.Index[v.Domain] = i
	}
	m.Get("/*", fss.ServeHTTP)
	hs.RunOnAddr(":" + strconv.Itoa(ctx.Int(flag.Port)))
}

type fileServer struct {
	Domain     string
	FolderRoot string
	ServeHTTP  func(http.ResponseWriter, *http.Request)
}

type fileServerSet struct {
	FileServers []fileServer
	Index       map[string]int
}

func (f fileServerSet) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	domain := strings.Split(r.Host, ":")[0]
	for _, f := range f.FileServers {
		// println(f.Domain + " | " + r.Host)
		if f.Domain == domain {
			f.ServeHTTP(w, r)
			return
		}
	}
	http.NotFound(w, r)
}

func getSubDomain(domain, baseURL string) string {
	subDomain := strings.Replace(domain, "."+baseURL, "", 1)
	subDomain = strings.Split(subDomain, ":")[0]
	return subDomain
}
<commit_msg>Remove dead code<commit_after>package cmd

import (
	"io/ioutil"
	"net/http"
	"strconv"
	"strings"

	"github.com/codegangsta/cli"
	"github.com/go-macaron/switcher"
	"github.com/jacobhands/multiweb/cmd/flag"
	"gopkg.in/macaron.v1"
)

// CmdServer will serve the website
var CmdServer = cli.Command{
	Name:        "server",
	ShortName:   "s",
	Description: "Serves up websites",
	Action:      runCmdServer,
	Flags:       cmdServerFlags,
}

// Flags
var cmdServerFlags = []cli.Flag{
	cli.StringFlag{
		Name:  flag.Folder,
		Value: "./sites",
		Usage: "Folder containing websites. Eg. foo.com and bar.com",
	},
	cli.StringFlag{
		Name:  flag.BaseURL,
		Value: "sites.example.com",
		Usage: "The base domain to route with.",
	},
	cli.IntFlag{
		Name:  flag.Port,
		Value: 8080,
		Usage: "The port to listen to http requests on.",
	},
}

func runCmdServer(ctx *cli.Context) {
	println("Serving files!", ctx.String(flag.Folder), ctx.IsSet(flag.Folder))
	baseURL := ctx.String(flag.BaseURL)
	folder := ctx.String(flag.Folder)
	m := macaron.Classic()
	hs := switcher.NewHostSwitcher()
	// Set instance corresponding to host address.
	hs.Set("*."+baseURL, m)
	// http.ListenAndServe(":8080", http.FileServer(http.Dir("/usr/share/doc")))
	fss := fileServerSet{}
	files, _ := ioutil.ReadDir(folder)
	for _, f := range files {
		if f.IsDir() {
			domain := f.Name()
			folderRoot := folder + "/" + f.Name()
			println("NEW FOLDER ROOT: " + folderRoot)
			newFss := fileServer{
				Domain:     domain,
				FolderRoot: folderRoot,
				ServeHTTP: func(w http.ResponseWriter, r *http.Request) {
					println(r.RequestURI)
					dir := folderRoot + "/www/"
					http.FileServer(http.Dir(dir)).ServeHTTP(w, r)
				},
			}
			fss.FileServers = append(fss.FileServers, newFss)
		}
	}
	m.Get("/*", fss.ServeHTTP)
	hs.RunOnAddr(":" + strconv.Itoa(ctx.Int(flag.Port)))
}

type fileServer struct {
	Domain     string
	FolderRoot string
	ServeHTTP  func(http.ResponseWriter, *http.Request)
}

type fileServerSet struct {
	FileServers []fileServer
}

func (f fileServerSet) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	domain := strings.Split(r.Host, ":")[0]
	for _, f := range f.FileServers {
		// println(f.Domain + " | " + r.Host)
		if f.Domain == domain {
			f.ServeHTTP(w, r)
			return
		}
	}
	http.NotFound(w, r)
}

func getSubDomain(domain, baseURL string) string {
	subDomain := strings.Replace(domain, "."+baseURL, "", 1)
	subDomain = strings.Split(subDomain, ":")[0]
	return subDomain
}
<|endoftext|>