<commit_before>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"golang.org/x/net/context"

	"tchaik.com/index"
	"tchaik.com/index/attr"

	"tchaik.com/store"
)

// Library is a type which encompases the components which form a full library.
type Library struct {
	index.Library

	collections map[string]index.Collection
	filters     map[string][]index.FilterItem
	recent      []index.Path
	searcher    index.Searcher
}

type libraryFileSystem struct {
	store.FileSystem
	index.Library
}

// Open implements store.FileSystem and rewrites ID values to their corresponding Location
// values using the index.Library.
func (l *libraryFileSystem) Open(ctx context.Context, path string) (http.File, error) {
	t, ok := l.Library.Track(strings.Trim(path, "/")) // IDs arrive with leading slash
	if !ok {
		return nil, fmt.Errorf("could not find track: %v", path)
	}

	loc := t.GetString("Location")
	if loc == "" {
		return nil, fmt.Errorf("invalid (empty) location for track: %v", path)
	}
	loc = filepath.ToSlash(loc)
	return l.FileSystem.Open(ctx, loc)
}

type group struct {
	Name        string
	Key         index.Key
	TotalTime   interface{} `json:",omitempty"`
	Artist      interface{} `json:",omitempty"`
	AlbumArtist interface{} `json:",omitempty"`
	Composer    interface{} `json:",omitempty"`
	BitRate     interface{} `json:",omitempty"`
	DiscNumber  interface{} `json:",omitempty"`
	ListStyle   interface{} `json:",omitempty"`
	ID          interface{} `json:",omitempty"`
	Year        interface{} `json:",omitempty"`
	Groups      []group     `json:",omitempty"`
	Tracks      []track     `json:",omitempty"`
	Favourite   bool        `json:",omitempty"`
	Checklist   bool        `json:",omitempty"`
}

type track struct {
	ID          string   `json:",omitempty"`
	Name        string   `json:",omitempty"`
	Album       string   `json:",omitempty"`
	Artist      []string `json:",omitempty"`
	AlbumArtist []string `json:",omitempty"`
	Composer    []string `json:",omitempty"`
	Year        int      `json:",omitempty"`
	DiscNumber  int      `json:",omitempty"`
	TotalTime   int      `json:",omitempty"`
	BitRate     int      `json:",omitempty"`
	Favourite   bool     `json:",omitempty"`
	Checklist   bool     `json:",omitempty"`
}

// StringSliceEqual is a function used to compare two interface{} types which are assumed
// to be of type []string (or interface{}(nil)).
func StringSliceEqual(x, y interface{}) bool {
	// Annoyingly we have to cater for zero values from map[string]interface{}
	// which don't have the correct type wrapping the nil.
	if x == nil || y == nil {
		return x == nil && y == nil
	}
	xs := x.([]string) // NB: panics here are acceptable: should not be called on a non-'Strings' field.
	ys := y.([]string)
	if len(xs) != len(ys) {
		return false
	}
	for i, xss := range xs {
		if ys[i] != xss {
			return false
		}
	}
	return true
}

func buildCollection(h group, c index.Collection) group {
	getField := func(f string, g index.Group, c index.Collection) interface{} {
		if StringSliceEqual(g.Field(f), c.Field(f)) {
			return nil
		}
		return g.Field(f)
	}

	for _, k := range c.Keys() {
		g := c.Get(k)
		g = index.FirstTrackAttr(attr.Strings("AlbumArtist"), g)
		g = index.CommonGroupAttr([]attr.Interface{attr.Strings("Artist")}, g)
		h.Groups = append(h.Groups, group{
			Name:        g.Name(),
			Key:         k,
			AlbumArtist: getField("AlbumArtist", g, c),
			Artist:      getField("Artist", g, c),
		})
	}
	return h
}

func build(g index.Group, key index.Key) group {
	h := group{
		Name:        g.Name(),
		Key:         key,
		TotalTime:   g.Field("TotalTime"),
		Artist:      g.Field("Artist"),
		AlbumArtist: g.Field("AlbumArtist"),
		Composer:    g.Field("Composer"),
		Year:        g.Field("Year"),
		BitRate:     g.Field("BitRate"),
		DiscNumber:  g.Field("DiscNumber"),
		ListStyle:   g.Field("ListStyle"),
		ID:          g.Field("ID"),
	}

	if c, ok := g.(index.Collection); ok {
		return buildCollection(h, c)
	}

	getString := func(t index.Track, field string) string {
		if g.Field(field) != nil {
			return ""
		}
		return t.GetString(field)
	}

	getStrings := func(t index.Track, field string) []string {
		if g.Field(field) != nil {
			return nil
		}
		return t.GetStrings(field)
	}

	getInt := func(t index.Track, field string) int {
		if g.Field(field) != nil {
			return 0
		}
		return t.GetInt(field)
	}

	for _, t := range g.Tracks() {
		h.Tracks = append(h.Tracks, track{
			ID:        t.GetString("ID"),
			Name:      t.GetString("Name"),
			TotalTime: t.GetInt("TotalTime"),
			// Potentially common fields (don't want to re-transmit everything)
			Artist:      getStrings(t, "Artist"),
			AlbumArtist: getStrings(t, "AlbumArtist"),
			Composer:    getStrings(t, "Composer"),
			Album:       getString(t, "Album"),
			Year:        getInt(t, "Year"),
			DiscNumber:  getInt(t, "DiscNumber"),
			BitRate:     getInt(t, "BitRate"),
		})
	}
	return h
}

func (l *Library) Build(c index.Collection, p index.Path) (index.Group, error) {
	if len(p) == 0 {
		return c, nil
	}

	var g index.Group = c
	k := index.Key(p[0])
	g = c.Get(k)

	if g == nil {
		return g, fmt.Errorf("invalid path: near '%v'", p[0])
	}

	index.Sort(g.Tracks(), index.MultiSort(index.SortByInt("DiscNumber"), index.SortByInt("TrackNumber")))
	g = index.Transform(g, index.SplitList("Artist", "AlbumArtist", "Composer"))
	g = index.Transform(g, index.TrimTrackNumPrefix)
	c = index.Collect(g, index.ByPrefix("Name"))
	g = index.SubTransform(c, index.TrimEnumPrefix)
	g = index.SumGroupIntAttr("TotalTime", g)
	commonFields := []attr.Interface{
		attr.String("Album"),
		attr.Strings("Artist"),
		attr.Strings("AlbumArtist"),
		attr.Strings("Composer"),
		attr.Int("Year"),
		attr.Int("BitRate"),
		attr.Int("DiscNumber"),
	}
	g = index.CommonGroupAttr(commonFields, g)
	g = index.RemoveEmptyCollections(g)

	for i, k := range p[1:] {
		var ok bool
		c, ok = g.(index.Collection)
		if !ok {
			return nil, fmt.Errorf("retrieved Group is not a Collection")
		}

		g = c.Get(k)
		if g == nil {
			return g, fmt.Errorf("invalid path near '%v'", p[1:][i])
		}

		if _, ok = g.(index.Collection); !ok {
			if i == len(p[1:])-1 {
				break
			}
			return nil, fmt.Errorf("retrieved Group isn't a Collection: %v", p)
		}
	}
	if g == nil {
		return g, fmt.Errorf("could not find group")
	}
	g = index.FirstTrackAttr(attr.String("ID"), g)
	return g, nil
}

// Fetch returns a group from the collection with the given path.
func (l *Library) Fetch(c index.Collection, p index.Path) (group, error) {
	if len(p) == 0 {
		return build(c, index.Key("Root")), nil
	}

	k := index.Key(p[0])
	g, err := l.Build(c, p)
	if err != nil {
		return group{}, err
	}
	return build(g, k), nil
}

// FileSystem wraps the http.FileSystem in a library lookup which will translate /ID
// requests into their corresponding track paths.
func (l *Library) FileSystem(fs store.FileSystem) store.FileSystem {
	return store.Trace(&libraryFileSystem{fs, l.Library}, "libraryFileSystem")
}

// ExpandPaths constructs a collection (group) whose sub-groups are taken from the "Root"
// collection.
func (l *Library) ExpandPaths(paths []index.Path) group {
	return build(index.NewPathsCollection(l.collections["Root"], paths), index.Key("Root"))
}
<commit_msg>Refactored root group construction into rootCollection type.<commit_after>// Copyright 2015, David Howden
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"fmt"
	"net/http"
	"path/filepath"
	"strings"

	"golang.org/x/net/context"

	"tchaik.com/index"
	"tchaik.com/index/attr"

	"tchaik.com/store"
)

// Library is a type which encompases the components which form a full library.
type Library struct {
	index.Library

	collections map[string]index.Collection
	filters     map[string][]index.FilterItem
	recent      []index.Path
	searcher    index.Searcher
}

type libraryFileSystem struct {
	store.FileSystem
	index.Library
}

// Open implements store.FileSystem and rewrites ID values to their corresponding Location
// values using the index.Library.
func (l *libraryFileSystem) Open(ctx context.Context, path string) (http.File, error) {
	t, ok := l.Library.Track(strings.Trim(path, "/")) // IDs arrive with leading slash
	if !ok {
		return nil, fmt.Errorf("could not find track: %v", path)
	}

	loc := t.GetString("Location")
	if loc == "" {
		return nil, fmt.Errorf("invalid (empty) location for track: %v", path)
	}
	loc = filepath.ToSlash(loc)
	return l.FileSystem.Open(ctx, loc)
}

type group struct {
	Name        string
	Key         index.Key
	TotalTime   interface{} `json:",omitempty"`
	Artist      interface{} `json:",omitempty"`
	AlbumArtist interface{} `json:",omitempty"`
	Composer    interface{} `json:",omitempty"`
	BitRate     interface{} `json:",omitempty"`
	DiscNumber  interface{} `json:",omitempty"`
	ListStyle   interface{} `json:",omitempty"`
	ID          interface{} `json:",omitempty"`
	Year        interface{} `json:",omitempty"`
	Groups      []group     `json:",omitempty"`
	Tracks      []track     `json:",omitempty"`
	Favourite   bool        `json:",omitempty"`
	Checklist   bool        `json:",omitempty"`
}

type track struct {
	ID          string   `json:",omitempty"`
	Name        string   `json:",omitempty"`
	Album       string   `json:",omitempty"`
	Artist      []string `json:",omitempty"`
	AlbumArtist []string `json:",omitempty"`
	Composer    []string `json:",omitempty"`
	Year        int      `json:",omitempty"`
	DiscNumber  int      `json:",omitempty"`
	TotalTime   int      `json:",omitempty"`
	BitRate     int      `json:",omitempty"`
	Favourite   bool     `json:",omitempty"`
	Checklist   bool     `json:",omitempty"`
}

// StringSliceEqual is a function used to compare two interface{} types which are assumed
// to be of type []string (or interface{}(nil)).
func StringSliceEqual(x, y interface{}) bool {
	// Annoyingly we have to cater for zero values from map[string]interface{}
	// which don't have the correct type wrapping the nil.
	if x == nil || y == nil {
		return x == nil && y == nil
	}
	xs := x.([]string) // NB: panics here are acceptable: should not be called on a non-'Strings' field.
	ys := y.([]string)
	if len(xs) != len(ys) {
		return false
	}
	for i, xss := range xs {
		if ys[i] != xss {
			return false
		}
	}
	return true
}

func buildCollection(h group, c index.Collection) group {
	getField := func(f string, g index.Group, c index.Collection) interface{} {
		if StringSliceEqual(g.Field(f), c.Field(f)) {
			return nil
		}
		return g.Field(f)
	}

	for _, k := range c.Keys() {
		g := c.Get(k)
		g = index.FirstTrackAttr(attr.Strings("AlbumArtist"), g)
		g = index.CommonGroupAttr([]attr.Interface{attr.Strings("Artist")}, g)
		h.Groups = append(h.Groups, group{
			Name:        g.Name(),
			Key:         k,
			AlbumArtist: getField("AlbumArtist", g, c),
			Artist:      getField("Artist", g, c),
		})
	}
	return h
}

func build(g index.Group, key index.Key) group {
	h := group{
		Name:        g.Name(),
		Key:         key,
		TotalTime:   g.Field("TotalTime"),
		Artist:      g.Field("Artist"),
		AlbumArtist: g.Field("AlbumArtist"),
		Composer:    g.Field("Composer"),
		Year:        g.Field("Year"),
		BitRate:     g.Field("BitRate"),
		DiscNumber:  g.Field("DiscNumber"),
		ListStyle:   g.Field("ListStyle"),
		ID:          g.Field("ID"),
	}

	if c, ok := g.(index.Collection); ok {
		return buildCollection(h, c)
	}

	getString := func(t index.Track, field string) string {
		if g.Field(field) != nil {
			return ""
		}
		return t.GetString(field)
	}

	getStrings := func(t index.Track, field string) []string {
		if g.Field(field) != nil {
			return nil
		}
		return t.GetStrings(field)
	}

	getInt := func(t index.Track, field string) int {
		if g.Field(field) != nil {
			return 0
		}
		return t.GetInt(field)
	}

	for _, t := range g.Tracks() {
		h.Tracks = append(h.Tracks, track{
			ID:        t.GetString("ID"),
			Name:      t.GetString("Name"),
			TotalTime: t.GetInt("TotalTime"),
			// Potentially common fields (don't want to re-transmit everything)
			Artist:      getStrings(t, "Artist"),
			AlbumArtist: getStrings(t, "AlbumArtist"),
			Composer:    getStrings(t, "Composer"),
			Album:       getString(t, "Album"),
			Year:        getInt(t, "Year"),
			DiscNumber:  getInt(t, "DiscNumber"),
			BitRate:     getInt(t, "BitRate"),
		})
	}
	return h
}

type rootCollection struct {
	index.Collection
}

func (r *rootCollection) Get(k index.Key) index.Group {
	g := r.Collection.Get(k)
	if g == nil {
		return g
	}

	index.Sort(g.Tracks(), index.MultiSort(index.SortByInt("DiscNumber"), index.SortByInt("TrackNumber")))
	g = index.Transform(g, index.SplitList("Artist", "AlbumArtist", "Composer"))
	g = index.Transform(g, index.TrimTrackNumPrefix)
	c := index.Collect(g, index.ByPrefix("Name"))
	g = index.SubTransform(c, index.TrimEnumPrefix)
	g = index.SumGroupIntAttr("TotalTime", g)
	commonFields := []attr.Interface{
		attr.String("Album"),
		attr.Strings("Artist"),
		attr.Strings("AlbumArtist"),
		attr.Strings("Composer"),
		attr.Int("Year"),
		attr.Int("BitRate"),
		attr.Int("DiscNumber"),
	}
	g = index.CommonGroupAttr(commonFields, g)
	g = index.RemoveEmptyCollections(g)
	return g
}

func (l *Library) Build(c index.Collection, p index.Path) (index.Group, error) {
	if len(p) == 0 {
		return c, nil
	}

	c = &rootCollection{c}
	var g index.Group = c
	k := index.Key(p[0])
	g = c.Get(k)

	if g == nil {
		return g, fmt.Errorf("invalid path: near '%v'", p[0])
	}

	for i, k := range p[1:] {
		var ok bool
		c, ok = g.(index.Collection)
		if !ok {
			return nil, fmt.Errorf("retrieved Group is not a Collection")
		}

		g = c.Get(k)
		if g == nil {
			return g, fmt.Errorf("invalid path near '%v'", p[1:][i])
		}

		if _, ok = g.(index.Collection); !ok {
			if i == len(p[1:])-1 {
				break
			}
			return nil, fmt.Errorf("retrieved Group isn't a Collection: %v", p)
		}
	}
	if g == nil {
		return g, fmt.Errorf("could not find group")
	}
	g = index.FirstTrackAttr(attr.String("ID"), g)
	return g, nil
}

// Fetch returns a group from the collection with the given path.
func (l *Library) Fetch(c index.Collection, p index.Path) (group, error) {
	if len(p) == 0 {
		return build(c, index.Key("Root")), nil
	}

	k := index.Key(p[0])
	g, err := l.Build(c, p)
	if err != nil {
		return group{}, err
	}
	return build(g, k), nil
}

// FileSystem wraps the http.FileSystem in a library lookup which will translate /ID
// requests into their corresponding track paths.
func (l *Library) FileSystem(fs store.FileSystem) store.FileSystem {
	return store.Trace(&libraryFileSystem{fs, l.Library}, "libraryFileSystem")
}

// ExpandPaths constructs a collection (group) whose sub-groups are taken from the "Root"
// collection.
func (l *Library) ExpandPaths(paths []index.Path) group {
	return build(index.NewPathsCollection(l.collections["Root"], paths), index.Key("Root"))
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/sha1"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/dhowden/tag"
	"github.com/tchaik/tchaik/index"
)

var fileExtensions = []string{".mp3", ".m4a", ".flac"}

// Library is an implementation of index.Library.
type Library struct {
	tracks map[string]*Track
}

func (l *Library) Track(id string) (index.Track, bool) {
	t, ok := l.tracks[id]
	return t, ok
}

func (l *Library) Tracks() []index.Track {
	tracks := make([]index.Track, 0, len(l.tracks))
	for _, t := range l.tracks {
		tracks = append(tracks, t)
	}
	return tracks
}

// Track is a wrapper around tag.Metadata which implements index.Track
type Track struct {
	tag.Metadata
	Location    string
	FileInfo    os.FileInfo
	CreatedTime time.Time
}

func (m *Track) GetString(name string) string {
	switch name {
	case "Name":
		title := m.Title()
		if title == "" {
			fileName := m.FileInfo.Name()
			ext := filepath.Ext(fileName)
			title = strings.TrimSuffix(fileName, ext)
		}
		return title
	case "Album":
		return m.Album()
	case "Artist":
		return m.Artist()
	case "Composer":
		return m.Composer()
	case "Location":
		return m.Location
	case "TrackID":
		sum := sha1.Sum([]byte(m.Location))
		return string(fmt.Sprintf("%x", sum))
	}
	return ""
}

func (m *Track) GetInt(name string) int {
	switch name {
	case "Year":
		return m.Year()
	case "TrackNumber":
		x, _ := m.Track()
		return x
	case "TrackCount":
		_, n := m.Track()
		return n
	case "DiscNumber":
		x, _ := m.Disc()
		return x
	case "DiscCount":
		_, n := m.Disc()
		return n
	}
	return 0
}

func (m *Track) GetTime(name string) time.Time {
	switch name {
	case "DateModified":
		return m.FileInfo.ModTime()
	case "DateAdded":
		return m.CreatedTime
	}
	return time.Time{}
}

func validExtension(path string) bool {
	ext := strings.ToLower(filepath.Ext(filepath.Base(path)))
	for _, x := range fileExtensions {
		if ext == x {
			return true
		}
	}
	return false
}

func walk(root string) <-chan string {
	ch := make(chan string)
	fn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		ch <- path
		return nil
	}

	go func() {
		err := filepath.Walk(root, fn)
		if err != nil {
			log.Println(err)
		}
		close(ch)
	}()
	return ch
}

func processPath(path string) (*Track, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	m, err := tag.ReadFrom(f)
	if err != nil {
		return nil, err
	}

	fileInfo, err := f.Stat()
	if err != nil {
		return nil, err
	}

	createdTime, err := getCreatedTime(path)
	if err != nil {
		return nil, err
	}

	return &Track{
		Metadata:    m,
		Location:    path,
		FileInfo:    fileInfo,
		CreatedTime: createdTime,
	}, nil
}
<commit_msg>Add AlbumArtist support to the walk mode in tchimport<commit_after>package main

import (
	"crypto/sha1"
	"fmt"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/dhowden/tag"
	"github.com/tchaik/tchaik/index"
)

var fileExtensions = []string{".mp3", ".m4a", ".flac"}

// Library is an implementation of index.Library.
type Library struct {
	tracks map[string]*Track
}

func (l *Library) Track(id string) (index.Track, bool) {
	t, ok := l.tracks[id]
	return t, ok
}

func (l *Library) Tracks() []index.Track {
	tracks := make([]index.Track, 0, len(l.tracks))
	for _, t := range l.tracks {
		tracks = append(tracks, t)
	}
	return tracks
}

// Track is a wrapper around tag.Metadata which implements index.Track
type Track struct {
	tag.Metadata
	Location    string
	FileInfo    os.FileInfo
	CreatedTime time.Time
}

func (m *Track) GetString(name string) string {
	switch name {
	case "Name":
		title := m.Title()
		if title == "" {
			fileName := m.FileInfo.Name()
			ext := filepath.Ext(fileName)
			title = strings.TrimSuffix(fileName, ext)
		}
		return title
	case "Album":
		return m.Album()
	case "Artist":
		return m.Artist()
	case "AlbumArtist":
		return m.AlbumArtist()
	case "Composer":
		return m.Composer()
	case "Location":
		return m.Location
	case "TrackID":
		sum := sha1.Sum([]byte(m.Location))
		return string(fmt.Sprintf("%x", sum))
	}
	return ""
}

func (m *Track) GetInt(name string) int {
	switch name {
	case "Year":
		return m.Year()
	case "TrackNumber":
		x, _ := m.Track()
		return x
	case "TrackCount":
		_, n := m.Track()
		return n
	case "DiscNumber":
		x, _ := m.Disc()
		return x
	case "DiscCount":
		_, n := m.Disc()
		return n
	}
	return 0
}

func (m *Track) GetTime(name string) time.Time {
	switch name {
	case "DateModified":
		return m.FileInfo.ModTime()
	case "DateAdded":
		return m.CreatedTime
	}
	return time.Time{}
}

func validExtension(path string) bool {
	ext := strings.ToLower(filepath.Ext(filepath.Base(path)))
	for _, x := range fileExtensions {
		if ext == x {
			return true
		}
	}
	return false
}

func walk(root string) <-chan string {
	ch := make(chan string)
	fn := func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if info.IsDir() {
			return nil
		}
		ch <- path
		return nil
	}

	go func() {
		err := filepath.Walk(root, fn)
		if err != nil {
			log.Println(err)
		}
		close(ch)
	}()
	return ch
}

func processPath(path string) (*Track, error) {
	f, err := os.Open(path)
	if err != nil {
		return nil, err
	}
	defer f.Close()

	m, err := tag.ReadFrom(f)
	if err != nil {
		return nil, err
	}

	fileInfo, err := f.Stat()
	if err != nil {
		return nil, err
	}

	createdTime, err := getCreatedTime(path)
	if err != nil {
		return nil, err
	}

	return &Track{
		Metadata:    m,
		Location:    path,
		FileInfo:    fileInfo,
		CreatedTime: createdTime,
	}, nil
}
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package main

import (
	"fmt"
	"github.com/jvehent/cljs"
	"mig"
	migdb "mig/database"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"time"
)

type pagination struct {
	Limit  float64 `json:"limit"`
	Offset float64 `json:"offset"`
	Next   string  `json:"next"`
}

// search runs searches
func search(respWriter http.ResponseWriter, request *http.Request) {
	var (
		err         error
		p           migdb.SearchParameters
		filterFound bool
	)
	opid := getOpID(request)
	loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
	resource := cljs.New(loc)
	defer func() {
		if e := recover(); e != nil {
			// on panic, log and return error to client, including the search parameters
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("%v", e)}.Err()
			resource.AddItem(cljs.Item{
				Href: loc,
				Data: []cljs.Data{{Name: "search parameters", Value: p}},
			})
			resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("%v", e)})
			if fmt.Sprintf("%v", e) == "no results found" {
				respond(404, resource, respWriter, request)
			} else {
				respond(500, resource, respWriter, request)
			}

		}
		ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving search()"}.Debug()
	}()

	p, filterFound, err = parseSearchParameters(request.URL.Query())
	if err != nil {
		panic(err)
	}

	// run the search based on the type
	var results interface{}
	switch p.Type {
	case "action":
		results, err = ctx.DB.SearchActions(p)
	case "agent":
		if p.Target != "" {
			results, err = ctx.DB.ActiveAgentsByTarget(p.Target)
		} else {
			results, err = ctx.DB.SearchAgents(p)
		}
	case "command":
		results, err = ctx.DB.SearchCommands(p, filterFound)
	case "investigator":
		results, err = ctx.DB.SearchInvestigators(p)
	default:
		panic("search type is invalid")
	}
	if err != nil {
		panic(err)
	}

	// prepare the output in the requested format
	switch p.Report {
	case "complianceitems":
		if p.Type != "command" {
			panic("compliance items reporting is only available for the 'command' type")
		}
		items, err := commandsToComplianceItems(results.([]mig.Command))
		if err != nil {
			panic(err)
		}
		for i, item := range items {
			err = resource.AddItem(cljs.Item{
				Href: fmt.Sprintf("%s%s/search?type=command?agentname=%s&commandid=%s&actionid=%s&threatfamily=compliance&report=complianceitems",
					ctx.Server.Host, ctx.Server.BaseRoute, item.Target, p.CommandID, p.ActionID),
				Data: []cljs.Data{{Name: "compliance item", Value: item}},
			})
			if err != nil {
				panic(err)
			}
			if float64(i) > p.Limit {
				break
			}
		}
	case "geolocations":
		if p.Type != "command" {
			panic("geolocations reporting is only available for the 'command' type")
		}
		items, err := commandsToGeolocations(results.([]mig.Command))
		if err != nil {
			panic(err)
		}
		for i, item := range items {
			err = resource.AddItem(cljs.Item{
				Href: fmt.Sprintf("%s%s/search?type=command?agentname=%s&commandid=%s&actionid=%s&report=geolocations",
					ctx.Server.Host, ctx.Server.BaseRoute, item.Endpoint, p.CommandID, p.ActionID),
				Data: []cljs.Data{{Name: "geolocation", Value: item}},
			})
			if err != nil {
				panic(err)
			}
			if float64(i) > p.Limit {
				break
			}
		}
	default:
		switch p.Type {
		case "action":
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d actions", len(results.([]mig.Action)))}
			if len(results.([]mig.Action)) == 0 {
				panic("no results found")
			}
			for i, r := range results.([]mig.Action) {
				err = resource.AddItem(cljs.Item{
					Href: fmt.Sprintf("%s%s/action?actionid=%.0f",
						ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
					Data: []cljs.Data{{Name: p.Type, Value: r}},
				})
				if err != nil {
					panic(err)
				}
				if float64(i) > p.Limit {
					break
				}
			}
		case "agent":
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d agents", len(results.([]mig.Agent)))}
			if len(results.([]mig.Agent)) == 0 {
				panic("no results found")
			}
			for i, r := range results.([]mig.Agent) {
				err = resource.AddItem(cljs.Item{
					Href: fmt.Sprintf("%s%s/agent?agentid=%.0f",
						ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
					Data: []cljs.Data{{Name: p.Type, Value: r}},
				})
				if err != nil {
					panic(err)
				}
				if float64(i) > p.Limit {
					break
				}
			}
		case "command":
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d commands", len(results.([]mig.Command)))}
			if len(results.([]mig.Command)) == 0 {
				panic("no results found")
			}
			for i, r := range results.([]mig.Command) {
				err = resource.AddItem(cljs.Item{
					Href: fmt.Sprintf("%s%s/command?commandid=%.0f",
						ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
					Data: []cljs.Data{{Name: p.Type, Value: r}},
				})
				if err != nil {
					panic(err)
				}
				if float64(i) > p.Limit {
					break
				}
			}
		case "investigator":
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d investigators", len(results.([]mig.Investigator)))}
			if len(results.([]mig.Investigator)) == 0 {
				panic("no results found")
			}
			for i, r := range results.([]mig.Investigator) {
				err = resource.AddItem(cljs.Item{
					Href: fmt.Sprintf("%s%s/investigator?investigatorid=%.0f",
						ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
					Data: []cljs.Data{{Name: p.Type, Value: r}},
				})
				if err != nil {
					panic(err)
				}
				if float64(i) > p.Limit {
					break
				}
			}
		}
	}
	// if needed, add pagination info
	if p.Offset > 0 {
		// go to next limit and offset values before making the next URL
		nextCount := p.Limit - p.Offset
		nextP := p
		nextP.Limit += nextCount
		nextP.Offset += nextCount
		page := pagination{
			Limit:  p.Limit,
			Offset: p.Offset,
			Next:   ctx.Server.BaseURL + "/search?" + nextP.String(),
		}
		err = resource.AddItem(cljs.Item{
			Href: loc,
			Data: []cljs.Data{{Name: "pagination", Value: page}},
		})
		if err != nil {
			panic(err)
		}
	}
	// add search parameters at the end of the response
	err = resource.AddItem(cljs.Item{
		Href: loc,
		Data: []cljs.Data{{Name: "search parameters", Value: p}},
	})
	if err != nil {
		panic(err)
	}
	respond(200, resource, respWriter, request)
}

// truere is a case insensitive regex that matches the string 'true'
var truere = regexp.MustCompile("(?i)^true$")

// false is a case insensitive regex that matches the string 'false'
var falsere = regexp.MustCompile("(?i)^false$")

// parseSearchParameters transforms a query string into search parameters in the migdb format
func parseSearchParameters(qp url.Values) (p migdb.SearchParameters, filterFound bool, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("parseSearchParameters()-> %v", e)
		}
	}()
	p = migdb.NewSearchParameters()
	for queryParams, _ := range qp {
		switch queryParams {
		case "actionname":
			p.ActionName = qp["actionname"][0]
		case "actionid":
			p.ActionID = qp["actionid"][0]
		case "after":
			p.After, err = time.Parse(time.RFC3339, qp["after"][0])
			if err != nil {
				panic("after date not in RFC3339 format")
			}
		case "agentid":
			p.AgentID = qp["agentid"][0]
		case "agentname":
			p.AgentName = qp["agentname"][0]
		case "before":
			p.Before, err = time.Parse(time.RFC3339, qp["before"][0])
			if err != nil {
				panic("before date not in RFC3339 format")
			}
		case "commandid":
			p.CommandID = qp["commandid"][0]
		case "foundanything":
			if truere.MatchString(qp["foundanything"][0]) {
				p.FoundAnything = true
			} else if falsere.MatchString(qp["foundanything"][0]) {
				p.FoundAnything = false
			} else {
				panic("foundanything parameter must be true or false")
			}
			filterFound = true
		case "investigatorid":
			p.InvestigatorID = qp["investigatorid"][0]
		case "investigatorname":
			p.InvestigatorName = qp["investigatorname"][0]
		case "limit":
			p.Limit, err = strconv.ParseFloat(qp["limit"][0], 64)
			if err != nil {
				panic("invalid limit parameter")
			}
		case "offset":
			p.Offset, err = strconv.ParseFloat(qp["offset"][0], 64)
			if err != nil {
				panic("invalid offset parameter")
			}
		case "report":
			switch qp["report"][0] {
			case "complianceitems":
				p.Report = qp["report"][0]
			case "geolocations":
				p.Report = qp["report"][0]
			default:
				panic("report not implemented")
			}
		case "status":
			p.Status = qp["status"][0]
		case "target":
			p.Target = qp["target"][0]
		case "threatfamily":
			p.ThreatFamily = qp["threatfamily"][0]
		case "type":
			p.Type = qp["type"][0]
		}
	}
	return
}
<commit_msg>[minor] fix next offset indicator in api response<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor: Julien Vehent jvehent@mozilla.com [:ulfr]
package main

import (
	"fmt"
	"github.com/jvehent/cljs"
	"mig"
	migdb "mig/database"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"time"
)

type pagination struct {
	Limit  float64 `json:"limit"`
	Offset float64 `json:"offset"`
	Next   string  `json:"next"`
}

// search runs searches
func search(respWriter http.ResponseWriter, request *http.Request) {
	var (
		err         error
		p           migdb.SearchParameters
		filterFound bool
	)
	opid := getOpID(request)
	loc := fmt.Sprintf("%s%s", ctx.Server.Host, request.URL.String())
	resource := cljs.New(loc)
	defer func() {
		if e := recover(); e != nil {
			// on panic, log and return error to client, including the search parameters
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("%v", e)}.Err()
			resource.AddItem(cljs.Item{
				Href: loc,
				Data: []cljs.Data{{Name: "search parameters", Value: p}},
			})
			resource.SetError(cljs.Error{Code: fmt.Sprintf("%.0f", opid), Message: fmt.Sprintf("%v", e)})
			if fmt.Sprintf("%v", e) == "no results found" {
				respond(404, resource, respWriter, request)
			} else {
				respond(500, resource, respWriter, request)
			}

		}
		ctx.Channels.Log <- mig.Log{OpID: opid, Desc: "leaving search()"}.Debug()
	}()

	p, filterFound, err = parseSearchParameters(request.URL.Query())
	if err != nil {
		panic(err)
	}

	// run the search based on the type
	var results interface{}
	switch p.Type {
	case "action":
		results, err = ctx.DB.SearchActions(p)
	case "agent":
		if p.Target != "" {
			results, err = ctx.DB.ActiveAgentsByTarget(p.Target)
		} else {
			results, err = ctx.DB.SearchAgents(p)
		}
	case "command":
		results, err = ctx.DB.SearchCommands(p, filterFound)
	case "investigator":
		results, err = ctx.DB.SearchInvestigators(p)
	default:
		panic("search type is invalid")
	}
	if err != nil {
		panic(err)
	}

	// prepare the output in the requested format
	switch p.Report {
	case "complianceitems":
		if p.Type != "command" {
			panic("compliance items reporting is only available for the 'command' type")
		}
		items, err := commandsToComplianceItems(results.([]mig.Command))
		if err != nil {
			panic(err)
		}
		for i, item := range items {
			err = resource.AddItem(cljs.Item{
				Href: fmt.Sprintf("%s%s/search?type=command?agentname=%s&commandid=%s&actionid=%s&threatfamily=compliance&report=complianceitems",
					ctx.Server.Host, ctx.Server.BaseRoute, item.Target, p.CommandID, p.ActionID),
				Data: []cljs.Data{{Name: "compliance item", Value: item}},
			})
			if err != nil {
				panic(err)
			}
			if float64(i) > p.Limit {
				break
			}
		}
	case "geolocations":
		if p.Type != "command" {
			panic("geolocations reporting is only available for the 'command' type")
		}
		items, err := commandsToGeolocations(results.([]mig.Command))
		if err != nil {
			panic(err)
		}
		for i, item := range items {
			err = resource.AddItem(cljs.Item{
				Href: fmt.Sprintf("%s%s/search?type=command?agentname=%s&commandid=%s&actionid=%s&report=geolocations",
					ctx.Server.Host, ctx.Server.BaseRoute, item.Endpoint, p.CommandID, p.ActionID),
				Data: []cljs.Data{{Name: "geolocation", Value: item}},
			})
			if err != nil {
				panic(err)
			}
			if float64(i) > p.Limit {
				break
			}
		}
	default:
		switch p.Type {
		case "action":
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d actions", len(results.([]mig.Action)))}
			if len(results.([]mig.Action)) == 0 {
				panic("no results found")
			}
			for i, r := range results.([]mig.Action) {
				err = resource.AddItem(cljs.Item{
					Href: fmt.Sprintf("%s%s/action?actionid=%.0f",
						ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
					Data: []cljs.Data{{Name: p.Type, Value: r}},
				})
				if err != nil {
					panic(err)
				}
				if float64(i) > p.Limit {
					break
				}
			}
		case "agent":
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d agents", len(results.([]mig.Agent)))}
			if len(results.([]mig.Agent)) == 0 {
				panic("no results found")
			}
			for i, r := range results.([]mig.Agent) {
				err = resource.AddItem(cljs.Item{
					Href: fmt.Sprintf("%s%s/agent?agentid=%.0f",
						ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
					Data: []cljs.Data{{Name: p.Type, Value: r}},
				})
				if err != nil {
					panic(err)
				}
				if float64(i) > p.Limit {
					break
				}
			}
		case "command":
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d commands", len(results.([]mig.Command)))}
			if len(results.([]mig.Command)) == 0 {
				panic("no results found")
			}
			for i, r := range results.([]mig.Command) {
				err = resource.AddItem(cljs.Item{
					Href: fmt.Sprintf("%s%s/command?commandid=%.0f",
						ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
					Data: []cljs.Data{{Name: p.Type, Value: r}},
				})
				if err != nil {
					panic(err)
				}
				if float64(i) > p.Limit {
					break
				}
			}
		case "investigator":
			ctx.Channels.Log <- mig.Log{OpID: opid, Desc: fmt.Sprintf("returning search results with %d investigators", len(results.([]mig.Investigator)))}
			if len(results.([]mig.Investigator)) == 0 {
				panic("no results found")
			}
			for i, r := range results.([]mig.Investigator) {
				err = resource.AddItem(cljs.Item{
					Href: fmt.Sprintf("%s%s/investigator?investigatorid=%.0f",
						ctx.Server.Host, ctx.Server.BaseRoute, r.ID),
					Data: []cljs.Data{{Name: p.Type, Value: r}},
				})
				if err != nil {
					panic(err)
				}
				if float64(i) > p.Limit {
					break
				}
			}
		}
	}
	// if needed, add pagination info
	if p.Offset > 0 {
		nextP := p
		nextP.Offset += p.Limit
		page := pagination{
			Limit:  p.Limit,
			Offset: p.Offset,
			Next:   ctx.Server.BaseURL + "/search?" + nextP.String(),
		}
		err = resource.AddItem(cljs.Item{
			Href: loc,
			Data: []cljs.Data{{Name: "pagination", Value: page}},
		})
		if err != nil {
			panic(err)
		}
	}
	// add search parameters at the end of the response
	err = resource.AddItem(cljs.Item{
		Href: loc,
		Data: []cljs.Data{{Name: "search parameters", Value: p}},
	})
	if err != nil {
		panic(err)
	}
	respond(200, resource, respWriter, request)
}

// truere is a case insensitive regex that matches the string 'true'
var truere = regexp.MustCompile("(?i)^true$")

// false is a case insensitive regex that matches the string 'false'
var falsere = regexp.MustCompile("(?i)^false$")

// parseSearchParameters transforms a query string into search parameters in the migdb format
func parseSearchParameters(qp url.Values) (p migdb.SearchParameters, filterFound bool, err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("parseSearchParameters()-> %v", e)
		}
	}()
	p = migdb.NewSearchParameters()
	for queryParams, _ := range qp {
		switch queryParams {
		case "actionname":
			p.ActionName = qp["actionname"][0]
		case "actionid":
			p.ActionID = qp["actionid"][0]
		case "after":
			p.After, err = time.Parse(time.RFC3339, qp["after"][0])
			if err != nil {
				panic("after date not in RFC3339 format")
			}
		case "agentid":
			p.AgentID = qp["agentid"][0]
		case "agentname":
			p.AgentName = qp["agentname"][0]
		case "before":
			p.Before, err = time.Parse(time.RFC3339, qp["before"][0])
			if err != nil {
				panic("before date not in RFC3339 format")
			}
		case "commandid":
			p.CommandID = qp["commandid"][0]
		case "foundanything":
			if truere.MatchString(qp["foundanything"][0]) {
				p.FoundAnything = true
			} else if falsere.MatchString(qp["foundanything"][0]) {
				p.FoundAnything = false
			} else {
				panic("foundanything parameter must be true or false")
			}
			filterFound = true
		case "investigatorid":
			p.InvestigatorID = qp["investigatorid"][0]
		case "investigatorname":
			p.InvestigatorName = qp["investigatorname"][0]
		case "limit":
			p.Limit, err = strconv.ParseFloat(qp["limit"][0], 64)
			if err != nil {
				panic("invalid limit parameter")
			}
		case "offset":
			p.Offset, err = strconv.ParseFloat(qp["offset"][0], 64)
			if err != nil {
				panic("invalid offset parameter")
			}
		case "report":
			switch qp["report"][0] {
			case "complianceitems":
				p.Report = qp["report"][0]
			case "geolocations":
				p.Report = qp["report"][0]
			default:
				panic("report not implemented")
			}
		case "status":
			p.Status = qp["status"][0]
		case "target":
			p.Target = qp["target"][0]
		case "threatfamily":
			p.ThreatFamily = qp["threatfamily"][0]
		case "type":
			p.Type = qp["type"][0]
		}
	}
	return
}
<|endoftext|>


<commit_before>package semver

import (
	"fmt"
	"strconv"
	"strings"
	"unicode"
)

type wildcardType int

const (
	noneWildcard  wildcardType = iota
	majorWildcard wildcardType = 1
	minorWildcard wildcardType = 2
	patchWildcard wildcardType = 3
)

func wildcardTypefromInt(i int) wildcardType {
	switch i {
	case 1:
		return majorWildcard
	case 2:
		return minorWildcard
	case 3:
		return patchWildcard
	default:
		return noneWildcard
	}
}

type comparator func(Version, Version) bool

var (
	compEQ comparator = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) == 0
	}
	compNE = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) != 0
	}
	compGT = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) == 1
	}
	compGE = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) >= 0
	}
	compLT = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) == -1
	}
	compLE = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) <= 0
	}
)

type versionRange struct {
	v Version
	c comparator
}

// rangeFunc creates a Range from the given versionRange.
func (vr *versionRange) rangeFunc() Range {
	return Range(func(v Version) bool {
		return vr.c(v, vr.v)
	})
}

// Range represents a range of versions.
// A Range can be used to check if a Version satisfies it:
//
//     range, err := semver.ParseRange(">1.0.0 <2.0.0")
//     range(semver.MustParse("1.1.1") // returns true
type Range func(Version) bool

// OR combines the existing Range with another Range using logical OR.
func (rf Range) OR(f Range) Range {
	return Range(func(v Version) bool {
		return rf(v) || f(v)
	})
}

// AND combines the existing Range with another Range using logical AND.
func (rf Range) AND(f Range) Range {
	return Range(func(v Version) bool {
		return rf(v) && f(v)
	})
}

// ParseRange parses a range and returns a Range.
// If the range could not be parsed an error is returned.
//
// Valid ranges are:
//   - "<1.0.0"
//   - "<=1.0.0"
//   - ">1.0.0"
//   - ">=1.0.0"
//   - "1.0.0", "=1.0.0", "==1.0.0"
//   - "!1.0.0", "!=1.0.0"
//
// A Range can consist of multiple ranges separated by space:
// Ranges can be linked by logical AND:
//   - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
//   - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
//
// Ranges can also be linked by logical OR:
//   - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
//
// AND has a higher precedence than OR. It's not possible to use brackets.
//
// Ranges can be combined by both AND and OR
//
//  - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
func ParseRange(s string) (Range, error) {
	parts := splitAndTrim(s)
	orParts, err := splitORParts(parts)
	if err != nil {
		return nil, err
	}
	expandedParts, err := expandWildcardVersion(orParts)
	if err != nil {
		return nil, err
	}
	var orFn Range
	for _, p := range expandedParts {
		var andFn Range
		for _, ap := range p {
			opStr, vStr, err := splitComparatorVersion(ap)
			if err != nil {
				return nil, err
			}
			vr, err := buildVersionRange(opStr, vStr)
			if err != nil {
				return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
			}
			rf := vr.rangeFunc()

			// Set function
			if andFn == nil {
				andFn = rf
			} else { // Combine with existing function
				andFn = andFn.AND(rf)
			}
		}
		if orFn == nil {
			orFn = andFn
		} else {
			orFn = orFn.OR(andFn)
		}

	}
	return orFn, nil
}

// splitORParts splits the already cleaned parts by '||'.
// Checks for invalid positions of the operator and returns an
// error if found.
func splitORParts(parts []string) ([][]string, error) {
	var ORparts [][]string
	last := 0
	for i, p := range parts {
		if p == "||" {
			if i == 0 {
				return nil, fmt.Errorf("First element in range is '||'")
			}
			ORparts = append(ORparts, parts[last:i])
			last = i + 1
		}
	}
	if last == len(parts) {
		return nil, fmt.Errorf("Last element in range is '||'")
	}
	ORparts = append(ORparts, parts[last:])
	return ORparts, nil
}

// buildVersionRange takes a slice of 2: operator and version
// and builds a versionRange, otherwise an error.
func buildVersionRange(opStr, vStr string) (*versionRange, error) {
	c := parseComparator(opStr)
	if c == nil {
		return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
	}
	v, err := Parse(vStr)
	if err != nil {
		return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
	}

	return &versionRange{
		v: v,
		c: c,
	}, nil

}

// inArray checks if a byte is contained in an array of bytes
func inArray(s byte, list []byte) bool {
	for _, el := range list {
		if el == s {
			return true
		}
	}
	return false
}

// splitAndTrim splits a range string by spaces and cleans whitespaces
func splitAndTrim(s string) (result []string) {
	last := 0
	var lastChar byte
	excludeFromSplit := []byte{'>', '<', '='}
	for i := 0; i < len(s); i++ {
		if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
			if last < i-1 {
				result = append(result, s[last:i])
			}
			last = i + 1
		} else if s[i] != ' ' {
			lastChar = s[i]
		}
	}
	if last < len(s)-1 {
		result = append(result, s[last:])
	}

	for i, v := range result {
		result[i] = strings.Replace(v, " ", "", -1)
	}

	// parts := strings.Split(s, " ")
	// for _, x := range parts {
	// 	if s := strings.TrimSpace(x); len(s) != 0 {
	// 		result = append(result, s)
	// 	}
	// }
	return
}

// splitComparatorVersion splits the comparator from the version.
// Input must be free of leading or trailing spaces.
func splitComparatorVersion(s string) (string, string, error) {
	i := strings.IndexFunc(s, unicode.IsDigit)
	if i == -1 {
		return "", "", fmt.Errorf("Could not get version from string: %q", s)
	}
	return strings.TrimSpace(s[0:i]), s[i:], nil
}

// getWildcardType will return the type of wildcard that the
// passed version contains
func getWildcardType(vStr string) wildcardType {
	parts := strings.Split(vStr, ".")
	nparts := len(parts)
	wildcard := parts[nparts-1]

	possibleWildcardType := wildcardTypefromInt(nparts)
	if wildcard == "x" {
		return possibleWildcardType
	}

	return noneWildcard
}

// createVersionFromWildcard will convert a wildcard version
// into a regular version, replacing 'x's with '0's, handling
// special cases like '1.x.x' and '1.x'
func createVersionFromWildcard(vStr string) string {
	// handle 1.x.x
	vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
	vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
	parts := strings.Split(vStr2, ".")

	// handle 1.x
	if len(parts) == 2 {
		return vStr2 + ".0"
	}

	return vStr2
}

// incrementMajorVersion will increment the major version
// of the passed version
func incrementMajorVersion(vStr string) (string, error) {
	parts := strings.Split(vStr, ".")
	i, err := strconv.Atoi(parts[0])
	if err != nil {
		return "", err
	}
	parts[0] = strconv.Itoa(i + 1)

	return strings.Join(parts, "."), nil
}

// incrementMajorVersion will increment the minor version
// of the passed version
func incrementMinorVersion(vStr string) (string, error) {
	parts := strings.Split(vStr, ".")
	i, err := strconv.Atoi(parts[1])
	if err != nil {
		return "", err
	}
	parts[1] = strconv.Itoa(i + 1)

	return strings.Join(parts, "."), nil
}

// expandWildcardVersion will expand wildcards inside versions
// following these rules:
//
// * when dealing with patch wildcards:
// >= 1.2.x    will become    >= 1.2.0
// <= 1.2.x    will become    <  1.3.0
// >  1.2.x    will become    >= 1.3.0
// <  1.2.x    will become    <  1.2.0
// != 1.2.x    will become    <  1.2.0 >= 1.3.0
//
// * when dealing with minor wildcards:
// >= 1.x      will become    >= 1.0.0
// <= 1.x      will become    <  2.0.0
// >  1.x      will become    >= 2.0.0
// <  1.0      will become    <  1.0.0
// != 1.x      will become    <  1.0.0 >= 2.0.0
//
// * when dealing with wildcards without
// version operator:
// 1.2.x       will become    >= 1.2.0 < 1.3.0
// 1.x         will become    >= 1.0.0 < 2.0.0
func expandWildcardVersion(parts [][]string) ([][]string, error) {
	var expandedParts [][]string
	for _, p := range parts {
		var newParts []string
		for _, ap := range p {
			if strings.Index(ap, "x") != -1 {
				opStr, vStr, err := splitComparatorVersion(ap)
				if err != nil {
					return nil, err
				}

				versionWildcardType := getWildcardType(vStr)
				flatVersion := createVersionFromWildcard(vStr)

				var resultOperator string
				var shouldIncrementVersion bool
				switch opStr {
				case ">":
					resultOperator = ">="
					shouldIncrementVersion = true
				case ">=":
					resultOperator = ">="
				case "<":
					resultOperator = "<"
				case "<=":
					resultOperator = "<"
					shouldIncrementVersion = true
				case "", "=", "==":
					newParts = append(newParts, ">="+flatVersion)
					resultOperator = "<"
					shouldIncrementVersion = true
				case "!=", "!":
					newParts = append(newParts, "<"+flatVersion)
					resultOperator = ">="
					shouldIncrementVersion = true
				}

				var resultVersion string
				if shouldIncrementVersion {
					switch versionWildcardType {
					case patchWildcard:
						resultVersion, _ = incrementMinorVersion(flatVersion)
					case minorWildcard:
						resultVersion, _ = incrementMajorVersion(flatVersion)
					}
				} else {
					resultVersion = flatVersion
				}

				ap = resultOperator + resultVersion
			}
			newParts = append(newParts, ap)
		}
		expandedParts = append(expandedParts, newParts)
	}

	return expandedParts, nil
}

func parseComparator(s string) comparator {
	switch s {
	case "==":
		fallthrough
	case "":
		fallthrough
	case "=":
		return compEQ
	case ">":
		return compGT
	case ">=":
		return compGE
	case "<":
		return compLT
	case "<=":
		return compLE
	case "!":
		fallthrough
	case "!=":
		return compNE
	}

	return nil
}

// MustParseRange is like ParseRange but panics if the range cannot be parsed.
func MustParseRange(s string) Range {
	r, err := ParseRange(s)
	if err != nil {
		panic(`semver: ParseRange(` + s + `): ` + err.Error())
	}
	return r
}
<commit_msg>simplify cases of strings.Index with strings.Contains<commit_after>package semver

import (
	"fmt"
	"strconv"
	"strings"
	"unicode"
)

type wildcardType int

const (
	noneWildcard  wildcardType = iota
	majorWildcard wildcardType = 1
	minorWildcard wildcardType = 2
	patchWildcard wildcardType = 3
)

func wildcardTypefromInt(i int) wildcardType {
	switch i {
	case 1:
		return majorWildcard
	case 2:
		return minorWildcard
	case 3:
		return patchWildcard
	default:
		return noneWildcard
	}
}

type comparator func(Version, Version) bool

var (
	compEQ comparator = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) == 0
	}
	compNE = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) != 0
	}
	compGT = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) == 1
	}
	compGE = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) >= 0
	}
	compLT = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) == -1
	}
	compLE = func(v1 Version, v2 Version) bool {
		return v1.Compare(v2) <= 0
	}
)

type versionRange struct {
	v Version
	c comparator
}

// rangeFunc creates a Range from the given versionRange.
func (vr *versionRange) rangeFunc() Range {
	return Range(func(v Version) bool {
		return vr.c(v, vr.v)
	})
}

// Range represents a range of versions.
// A Range can be used to check if a Version satisfies it:
//
//     range, err := semver.ParseRange(">1.0.0 <2.0.0")
//     range(semver.MustParse("1.1.1") // returns true
type Range func(Version) bool

// OR combines the existing Range with another Range using logical OR.
func (rf Range) OR(f Range) Range {
	return Range(func(v Version) bool {
		return rf(v) || f(v)
	})
}

// AND combines the existing Range with another Range using logical AND.
func (rf Range) AND(f Range) Range {
	return Range(func(v Version) bool {
		return rf(v) && f(v)
	})
}

// ParseRange parses a range and returns a Range.
// If the range could not be parsed an error is returned.
//
// Valid ranges are:
//   - "<1.0.0"
//   - "<=1.0.0"
//   - ">1.0.0"
//   - ">=1.0.0"
//   - "1.0.0", "=1.0.0", "==1.0.0"
//   - "!1.0.0", "!=1.0.0"
//
// A Range can consist of multiple ranges separated by space:
// Ranges can be linked by logical AND:
//   - ">1.0.0 <2.0.0" would match between both ranges, so "1.1.1" and "1.8.7" but not "1.0.0" or "2.0.0"
//   - ">1.0.0 <3.0.0 !2.0.3-beta.2" would match every version between 1.0.0 and 3.0.0 except 2.0.3-beta.2
//
// Ranges can also be linked by logical OR:
//   - "<2.0.0 || >=3.0.0" would match "1.x.x" and "3.x.x" but not "2.x.x"
//
// AND has a higher precedence than OR. It's not possible to use brackets.
//
// Ranges can be combined by both AND and OR
//
//  - `>1.0.0 <2.0.0 || >3.0.0 !4.2.1` would match `1.2.3`, `1.9.9`, `3.1.1`, but not `4.2.1`, `2.1.1`
func ParseRange(s string) (Range, error) {
	parts := splitAndTrim(s)
	orParts, err := splitORParts(parts)
	if err != nil {
		return nil, err
	}
	expandedParts, err := expandWildcardVersion(orParts)
	if err != nil {
		return nil, err
	}
	var orFn Range
	for _, p := range expandedParts {
		var andFn Range
		for _, ap := range p {
			opStr, vStr, err := splitComparatorVersion(ap)
			if err != nil {
				return nil, err
			}
			vr, err := buildVersionRange(opStr, vStr)
			if err != nil {
				return nil, fmt.Errorf("Could not parse Range %q: %s", ap, err)
			}
			rf := vr.rangeFunc()

			// Set function
			if andFn == nil {
				andFn = rf
			} else { // Combine with existing function
				andFn = andFn.AND(rf)
			}
		}
		if orFn == nil {
			orFn = andFn
		} else {
			orFn = orFn.OR(andFn)
		}

	}
	return orFn, nil
}

// splitORParts splits the already cleaned parts by '||'.
// Checks for invalid positions of the operator and returns an
// error if found.
func splitORParts(parts []string) ([][]string, error) {
	var ORparts [][]string
	last := 0
	for i, p := range parts {
		if p == "||" {
			if i == 0 {
				return nil, fmt.Errorf("First element in range is '||'")
			}
			ORparts = append(ORparts, parts[last:i])
			last = i + 1
		}
	}
	if last == len(parts) {
		return nil, fmt.Errorf("Last element in range is '||'")
	}
	ORparts = append(ORparts, parts[last:])
	return ORparts, nil
}

// buildVersionRange takes a slice of 2: operator and version
// and builds a versionRange, otherwise an error.
func buildVersionRange(opStr, vStr string) (*versionRange, error) {
	c := parseComparator(opStr)
	if c == nil {
		return nil, fmt.Errorf("Could not parse comparator %q in %q", opStr, strings.Join([]string{opStr, vStr}, ""))
	}
	v, err := Parse(vStr)
	if err != nil {
		return nil, fmt.Errorf("Could not parse version %q in %q: %s", vStr, strings.Join([]string{opStr, vStr}, ""), err)
	}

	return &versionRange{
		v: v,
		c: c,
	}, nil

}

// inArray checks if a byte is contained in an array of bytes
func inArray(s byte, list []byte) bool {
	for _, el := range list {
		if el == s {
			return true
		}
	}
	return false
}

// splitAndTrim splits a range string by spaces and cleans whitespaces
func splitAndTrim(s string) (result []string) {
	last := 0
	var lastChar byte
	excludeFromSplit := []byte{'>', '<', '='}
	for i := 0; i < len(s); i++ {
		if s[i] == ' ' && !inArray(lastChar, excludeFromSplit) {
			if last < i-1 {
				result = append(result, s[last:i])
			}
			last = i + 1
		} else if s[i] != ' ' {
			lastChar = s[i]
		}
	}
	if last < len(s)-1 {
		result = append(result, s[last:])
	}

	for i, v := range result {
		result[i] = strings.Replace(v, " ", "", -1)
	}

	// parts := strings.Split(s, " ")
	// for _, x := range parts {
	// 	if s := strings.TrimSpace(x); len(s) != 0 {
	// 		result = append(result, s)
	// 	}
	// }
	return
}

// splitComparatorVersion splits the comparator from the version.
// Input must be free of leading or trailing spaces.
func splitComparatorVersion(s string) (string, string, error) {
	i := strings.IndexFunc(s, unicode.IsDigit)
	if i == -1 {
		return "", "", fmt.Errorf("Could not get version from string: %q", s)
	}
	return strings.TrimSpace(s[0:i]), s[i:], nil
}

// getWildcardType will return the type of wildcard that the
// passed version contains
func getWildcardType(vStr string) wildcardType {
	parts := strings.Split(vStr, ".")
	nparts := len(parts)
	wildcard := parts[nparts-1]

	possibleWildcardType := wildcardTypefromInt(nparts)
	if wildcard == "x" {
		return possibleWildcardType
	}

	return noneWildcard
}

// createVersionFromWildcard will convert a wildcard version
// into a regular version, replacing 'x's with '0's, handling
// special cases like '1.x.x' and '1.x'
func createVersionFromWildcard(vStr string) string {
	// handle 1.x.x
	vStr2 := strings.Replace(vStr, ".x.x", ".x", 1)
	vStr2 = strings.Replace(vStr2, ".x", ".0", 1)
	parts := strings.Split(vStr2, ".")

	// handle 1.x
	if len(parts) == 2 {
		return vStr2 + ".0"
	}

	return vStr2
}

// incrementMajorVersion will increment the major version
// of the passed version
func incrementMajorVersion(vStr string) (string, error) {
	parts := strings.Split(vStr, ".")
	i, err := strconv.Atoi(parts[0])
	if err != nil {
		return "", err
	}
	parts[0] = strconv.Itoa(i + 1)

	return strings.Join(parts, "."), nil
}

// incrementMajorVersion will increment the minor version
// of the passed version
func incrementMinorVersion(vStr string) (string, error) {
	parts := strings.Split(vStr, ".")
	i, err := strconv.Atoi(parts[1])
	if err != nil {
		return "", err
	}
	parts[1] = strconv.Itoa(i + 1)

	return strings.Join(parts, "."), nil
}

// expandWildcardVersion will expand wildcards inside versions
// following these rules:
//
// * when dealing with patch wildcards:
// >= 1.2.x    will become    >= 1.2.0
// <= 1.2.x    will become    <  1.3.0
// >  1.2.x    will become    >= 1.3.0
// <  1.2.x    will become    <  1.2.0
// != 1.2.x    will become    <  1.2.0 >= 1.3.0
//
// * when dealing with minor wildcards:
// >= 1.x      will become    >= 1.0.0
// <= 1.x      will become    <  2.0.0
// >  1.x      will become    >= 2.0.0
// <  1.0      will become    <  1.0.0
// != 1.x      will become    <  1.0.0 >= 2.0.0
//
// * when dealing with wildcards without
// version operator:
// 1.2.x       will become    >= 1.2.0 < 1.3.0
// 1.x         will become    >= 1.0.0 < 2.0.0
func expandWildcardVersion(parts [][]string) ([][]string, error) {
	var expandedParts [][]string
	for _, p := range parts {
		var newParts []string
		for _, ap := range p {
			if strings.Contains(ap, "x") {
				opStr, vStr, err := splitComparatorVersion(ap)
				if err != nil {
					return nil, err
				}

				versionWildcardType := getWildcardType(vStr)
				flatVersion := createVersionFromWildcard(vStr)

				var resultOperator string
				var shouldIncrementVersion bool
				switch opStr {
				case ">":
					resultOperator = ">="
					shouldIncrementVersion = true
				case ">=":
					resultOperator = ">="
				case "<":
					resultOperator = "<"
				case "<=":
					resultOperator = "<"
					shouldIncrementVersion = true
				case "", "=", "==":
					newParts = append(newParts, ">="+flatVersion)
					resultOperator = "<"
					shouldIncrementVersion = true
				case "!=", "!":
					newParts = append(newParts, "<"+flatVersion)
					resultOperator = ">="
					shouldIncrementVersion = true
				}

				var resultVersion string
				if shouldIncrementVersion {
					switch versionWildcardType {
					case patchWildcard:
						resultVersion, _ = incrementMinorVersion(flatVersion)
					case minorWildcard:
						resultVersion, _ = incrementMajorVersion(flatVersion)
					}
				} else {
					resultVersion = flatVersion
				}

				ap = resultOperator + resultVersion
			}
			newParts = append(newParts, ap)
		}
		expandedParts = append(expandedParts, newParts)
	}

	return expandedParts, nil
}

func parseComparator(s string) comparator {
	switch s {
	case "==":
		fallthrough
	case "":
		fallthrough
	case "=":
		return compEQ
	case ">":
		return compGT
	case ">=":
		return compGE
	case "<":
		return compLT
	case "<=":
		return compLE
	case "!":
		fallthrough
	case "!=":
		return compNE
	}

	return nil
}

// MustParseRange is like ParseRange but panics if the range cannot be parsed.
func MustParseRange(s string) Range {
	r, err := ParseRange(s)
	if err != nil {
		panic(`semver: ParseRange(` + s + `): ` + err.Error())
	}
	return r
}
<|endoftext|>


<commit_before>package collector

import (
	"bufio"
	"bytes"
	log "github.com/ulricqin/goutils/logtool"
	"github.com/ulricqin/goutils/slicetool"
	"github.com/ulricqin/goutils/systool"
	"io"
	"strconv"
	"strings"
)

func ListenPorts() []int64 {
	bs, err := systool.CmdOutBytes("ss", "-n", "-l")
	if err != nil {
		log.Error("ss -n -l exec fail: %s", err)
		return []int64{}
	}

	reader := bufio.NewReader(bytes.NewBuffer(bs))

	// ignore the first line
	var line []byte
	line, _, err = reader.ReadLine()
	if err == io.EOF || err != nil {
		return []int64{}
	}

	ret := []int64{}

	for {
		line, _, err = reader.ReadLine()
		if err == io.EOF || err != nil {
			break
		}

		arr := strings.Fields(string(line))
		if len(arr) != 4 {
			log.Error("output of [ss -n -l] format error")
			continue
		}

		location := strings.LastIndex(arr[2], ":")
		port := arr[2][location+1:]

		if p, e := strconv.ParseInt(port, 10, 64); e != nil {
			log.Error("parse port to int64 fail: %s", e)
			continue
		} else {
			ret = append(ret, p)
		}

	}

	ret = slicetool.SliceUniqueInt64(ret)

	log.Info("listening ports: %v", ret)
	return ret
}
<commit_msg>bugfix: listen port<commit_after>package collector

import (
	"bufio"
	"bytes"
	log "github.com/ulricqin/goutils/logtool"
	"github.com/ulricqin/goutils/slicetool"
	"github.com/ulricqin/goutils/systool"
	"io"
	"strconv"
	"strings"
)

func ListenPorts() []int64 {
	bs, err := systool.CmdOutBytes("ss", "-n", "-l")
	if err != nil {
		log.Error("ss -n -l exec fail: %s", err)
		return []int64{}
	}

	reader := bufio.NewReader(bytes.NewBuffer(bs))

	// ignore the first line
	var line []byte
	line, _, err = reader.ReadLine()
	if err == io.EOF || err != nil {
		return []int64{}
	}

	ret := []int64{}

	for {
		line, _, err = reader.ReadLine()
		if err == io.EOF || err != nil {
			break
		}

		arr := strings.Fields(string(line))
		arrlen := len(arr)

		if arrlen != 4 && arrlen != 5 {
			log.Error("output of [ss -n -l] format error")
			continue
		}

		ci := 2
		if arrlen == 5 {
			ci = 3
		}

		location := strings.LastIndex(arr[ci], ":")
		port := arr[ci][location+1:]

		if p, e := strconv.ParseInt(port, 10, 64); e != nil {
			log.Error("parse port to int64 fail: %s", e)
			continue
		} else {
			ret = append(ret, p)
		}

	}

	ret = slicetool.SliceUniqueInt64(ret)

	log.Info("listening ports: %v", ret)
	return ret
}
<|endoftext|>


<commit_before>package commands

import (
	"fmt"
	"github.com/jingweno/gh/git"
	"github.com/jingweno/gh/github"
	"github.com/jingweno/gh/utils"
	"os"
)

var cmdCiStatus = &Command{
	Run:   ciStatus,
	Usage: "ci-status [COMMIT]",
	Short: "Show CI status of a commit",
	Long: `Looks up the SHA for COMMIT in GitHub Status API and displays the latest
status. If no COMMIT is provided, HEAD will be used. Exits with one of:

success (0), error (1), failure (1), pending (2), no status (3)
`,
}

/*
  $ gh ci-status
  > (prints CI state of HEAD and exits with appropriate code)
  > One of: success (0), error (1), failure (1), pending (2), no status (3)

  $ gh ci-status BRANCH
  > (prints CI state of BRANCH and exits with appropriate code)
  > One of: success (0), error (1), failure (1), pending (2), no status (3)

  $ gh ci-status SHA
  > (prints CI state of SHA and exits with appropriate code)
  > One of: success (0), error (1), failure (1), pending (2), no status (3)
*/
func ciStatus(cmd *Command, args *Args) {
	ref := "HEAD"
	if !args.IsParamsEmpty() {
		ref = args.RemoveParam(0)
	}

	ref, err := git.Ref(ref)
	utils.Check(err)

	args.Replace("", "")
	if args.Noop {
		fmt.Printf("Would request CI status for %s", ref)
	} else {
		state, targetURL, desc, exitCode, err := fetchCiStatus(ref)
		utils.Check(err)
		fmt.Println(state)
		if targetURL != "" {
			fmt.Println(targetURL)
		}
		if desc != "" {
			fmt.Println(desc)
		}

		os.Exit(exitCode)
	}

}

func fetchCiStatus(ref string) (state, targetURL, desc string, exitCode int, err error) {
	gh := github.New()
	status, err := gh.CiStatus(ref)
	if err != nil {
		return
	}

	if status == nil {
		state = "no status"
	} else {
		state = status.State
		targetURL = status.TargetURL
		desc = status.Description
	}

	switch state {
	case "success":
		exitCode = 0
	case "failure", "error":
		exitCode = 1
	case "pending":
		exitCode = 2
	default:
		exitCode = 3
	}

	return
}
<commit_msg>Trim space<commit_after>package commands

import (
	"fmt"
	"github.com/jingweno/gh/git"
	"github.com/jingweno/gh/github"
	"github.com/jingweno/gh/utils"
	"os"
)

var cmdCiStatus = &Command{
	Run:   ciStatus,
	Usage: "ci-status [COMMIT]",
	Short: "Show CI status of a commit",
	Long: `Looks up the SHA for COMMIT in GitHub Status API and displays the latest
status. If no COMMIT is provided, HEAD will be used. Exits with one of:

success (0), error (1), failure (1), pending (2), no status (3)
`,
}

/*
  $ gh ci-status
  > (prints CI state of HEAD and exits with appropriate code)
  > One of: success (0), error (1), failure (1), pending (2), no status (3)

  $ gh ci-status BRANCH
  > (prints CI state of BRANCH and exits with appropriate code)
  > One of: success (0), error (1), failure (1), pending (2), no status (3)

  $ gh ci-status SHA
  > (prints CI state of SHA and exits with appropriate code)
  > One of: success (0), error (1), failure (1), pending (2), no status (3)
*/
func ciStatus(cmd *Command, args *Args) {
	ref := "HEAD"
	if !args.IsParamsEmpty() {
		ref = args.RemoveParam(0)
	}

	ref, err := git.Ref(ref)
	utils.Check(err)

	args.Replace("", "")
	if args.Noop {
		fmt.Printf("Would request CI status for %s", ref)
	} else {
		state, targetURL, desc, exitCode, err := fetchCiStatus(ref)
		utils.Check(err)
		fmt.Println(state)
		if targetURL != "" {
			fmt.Println(targetURL)
		}
		if desc != "" {
			fmt.Println(desc)
		}

		os.Exit(exitCode)
	}
}

func fetchCiStatus(ref string) (state, targetURL, desc string, exitCode int, err error) {
	gh := github.New()
	status, err := gh.CiStatus(ref)
	if err != nil {
		return
	}

	if status == nil {
		state = "no status"
	} else {
		state = status.State
		targetURL = status.TargetURL
		desc = status.Description
	}

	switch state {
	case "success":
		exitCode = 0
	case "failure", "error":
		exitCode = 1
	case "pending":
		exitCode = 2
	default:
		exitCode = 3
	}

	return
}
<|endoftext|>


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

package commands

import (
	"bytes"
	"errors"
	"path/filepath"
	"strings"
	"time"

	"github.com/gohugoio/hugo/helpers"
	"github.com/gohugoio/hugo/hugofs"
	"github.com/spf13/cobra"
	jww "github.com/spf13/jwalterweatherman"
)

var _ cmder = (*newThemeCmd)(nil)

type newThemeCmd struct {
	*baseCmd
	hugoBuilderCommon
}

func newNewThemeCmd() *newThemeCmd {
	ccmd := &newThemeCmd{baseCmd: newBaseCmd(nil)}

	cmd := &cobra.Command{
		Use:   "theme [name]",
		Short: "Create a new theme",
		Long: `Create a new theme (skeleton) called [name] in the current directory.
New theme is a skeleton. Please add content to the touched files. Add your
name to the copyright line in the license and adjust the theme.toml file
as you see fit.`,
		RunE: ccmd.newTheme,
	}

	ccmd.cmd = cmd

	return ccmd
}

// newTheme creates a new Hugo theme template
func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
	c, err := initializeConfig(false, false, &n.hugoBuilderCommon, n, nil)

	if err != nil {
		return err
	}

	if len(args) < 1 {
		return newUserError("theme name needs to be provided")
	}

	createpath := c.hugo.PathSpec.AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0]))
	jww.FEEDBACK.Println("Creating theme at", createpath)

	cfg := c.DepsCfg

	if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x {
		return errors.New(createpath + " already exists")
	}

	mkdir(createpath, "layouts", "_default")
	mkdir(createpath, "layouts", "partials")

	touchFile(cfg.Fs.Source, createpath, "layouts", "index.html")
	touchFile(cfg.Fs.Source, createpath, "layouts", "404.html")
	touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "list.html")
	touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "single.html")

	baseofDefault := []byte(`
    {{- partial "head.html" . -}}

        {{- partial "header.html" . -}}

        {{- block "main" . }}{{- end }}

        {{- partial "footer.html" . -}}

`)
	err = helpers.WriteToDisk(filepath.Join(createpath, "layouts", "_default", "baseof.html"), bytes.NewReader(baseofDefault), cfg.Fs.Source)
	if err != nil {
		return err
	}

	touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "header.html")
	touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "footer.html")

	mkdir(createpath, "archetypes")

	archDefault := []byte("+++\n+++\n")

	err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), cfg.Fs.Source)
	if err != nil {
		return err
	}

	mkdir(createpath, "static", "js")
	mkdir(createpath, "static", "css")

	by := []byte(`The MIT License (MIT)

Copyright (c) ` + time.Now().Format("2006") + ` YOUR_NAME_HERE

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`)

	err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE.md"), bytes.NewReader(by), cfg.Fs.Source)
	if err != nil {
		return err
	}

	n.createThemeMD(cfg.Fs, createpath)

	return nil
}

func (n *newThemeCmd) createThemeMD(fs *hugofs.Fs, inpath string) (err error) {

	by := []byte(`# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example

name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE.md"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.41"

[author]
  name = ""
  homepage = ""

# If porting an existing theme
[original]
  name = ""
  homepage = ""
  repo = ""
`)

	err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs.Source)
	if err != nil {
		return
	}

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

package commands

import (
	"bytes"
	"errors"
	"path/filepath"
	"strings"
	"time"

	"github.com/gohugoio/hugo/helpers"
	"github.com/gohugoio/hugo/hugofs"
	"github.com/spf13/cobra"
	jww "github.com/spf13/jwalterweatherman"
)

var _ cmder = (*newThemeCmd)(nil)

type newThemeCmd struct {
	*baseCmd
	hugoBuilderCommon
}

func newNewThemeCmd() *newThemeCmd {
	ccmd := &newThemeCmd{baseCmd: newBaseCmd(nil)}

	cmd := &cobra.Command{
		Use:   "theme [name]",
		Short: "Create a new theme",
		Long: `Create a new theme (skeleton) called [name] in the current directory.
New theme is a skeleton. Please add content to the touched files. Add your
name to the copyright line in the license and adjust the theme.toml file
as you see fit.`,
		RunE: ccmd.newTheme,
	}

	ccmd.cmd = cmd

	return ccmd
}

// newTheme creates a new Hugo theme template
func (n *newThemeCmd) newTheme(cmd *cobra.Command, args []string) error {
	c, err := initializeConfig(false, false, &n.hugoBuilderCommon, n, nil)

	if err != nil {
		return err
	}

	if len(args) < 1 {
		return newUserError("theme name needs to be provided")
	}

	createpath := c.hugo.PathSpec.AbsPathify(filepath.Join(c.Cfg.GetString("themesDir"), args[0]))
	jww.FEEDBACK.Println("Creating theme at", createpath)

	cfg := c.DepsCfg

	if x, _ := helpers.Exists(createpath, cfg.Fs.Source); x {
		return errors.New(createpath + " already exists")
	}

	mkdir(createpath, "layouts", "_default")
	mkdir(createpath, "layouts", "partials")

	touchFile(cfg.Fs.Source, createpath, "layouts", "index.html")
	touchFile(cfg.Fs.Source, createpath, "layouts", "404.html")
	touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "list.html")
	touchFile(cfg.Fs.Source, createpath, "layouts", "_default", "single.html")

	baseofDefault := []byte(`
    {{- partial "head.html" . -}}

        {{- partial "header.html" . -}}

        {{- block "main" . }}{{- end }}

        {{- partial "footer.html" . -}}

`)
	err = helpers.WriteToDisk(filepath.Join(createpath, "layouts", "_default", "baseof.html"), bytes.NewReader(baseofDefault), cfg.Fs.Source)
	if err != nil {
		return err
	}

	touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "header.html")
	touchFile(cfg.Fs.Source, createpath, "layouts", "partials", "footer.html")

	mkdir(createpath, "archetypes")

	archDefault := []byte("+++\n+++\n")

	err = helpers.WriteToDisk(filepath.Join(createpath, "archetypes", "default.md"), bytes.NewReader(archDefault), cfg.Fs.Source)
	if err != nil {
		return err
	}

	mkdir(createpath, "static", "js")
	mkdir(createpath, "static", "css")

	by := []byte(`The MIT License (MIT)

Copyright (c) ` + time.Now().Format("2006") + ` YOUR_NAME_HERE

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
`)

	err = helpers.WriteToDisk(filepath.Join(createpath, "LICENSE"), bytes.NewReader(by), cfg.Fs.Source)
	if err != nil {
		return err
	}

	n.createThemeMD(cfg.Fs, createpath)

	return nil
}

func (n *newThemeCmd) createThemeMD(fs *hugofs.Fs, inpath string) (err error) {

	by := []byte(`# theme.toml template for a Hugo theme
# See https://github.com/gohugoio/hugoThemes#themetoml for an example

name = "` + strings.Title(helpers.MakeTitle(filepath.Base(inpath))) + `"
license = "MIT"
licenselink = "https://github.com/yourname/yourtheme/blob/master/LICENSE"
description = ""
homepage = "http://example.com/"
tags = []
features = []
min_version = "0.41"

[author]
  name = ""
  homepage = ""

# If porting an existing theme
[original]
  name = ""
  homepage = ""
  repo = ""
`)

	err = helpers.WriteToDisk(filepath.Join(inpath, "theme.toml"), bytes.NewReader(by), fs.Source)
	if err != nil {
		return
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) OpenFaaS project 2018. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package commands

import (
	"testing"

	"github.com/openfaas/faas-cli/stack"
)

func Test_PushValidation(t *testing.T) {
	testCases := []struct {
		name     string
		scenario string
		image    string
		isValid  bool
	}{
		{scenario: "Valid image with username", name: "cli", image: "alexellis/faas-cli", isValid: true},
		{scenario: "Valid image with remote repo", name: "cli", image: "10.1.95.201:5000/faas-cli", isValid: true},
		{scenario: "Invalid image - missing prefix", name: "cli", image: "faas-cli", isValid: false},
	}

	for _, testCase := range testCases {
		functions := map[string]stack.Function{
			"cli": stack.Function{
				Name:  testCase.name,
				Image: testCase.image,
			},
		}
		invalidImages := validateImages(functions)
		if len(invalidImages) > 0 && testCase.isValid == true {
			t.Logf("scenario: %s want %s to be valid, but was invalid", testCase.scenario, testCase.image)
			t.Fail()
		}
		if len(invalidImages) == 0 && testCase.isValid == false {
			t.Logf("scenario: %s want %s to be invalid, but was valid", testCase.scenario, testCase.image)
			t.Fail()
		}

	}
}
<commit_msg>Update license<commit_after>// Copyright (c) OpenFaaS Project 2018. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

package commands

import (
	"testing"

	"github.com/openfaas/faas-cli/stack"
)

func Test_PushValidation(t *testing.T) {
	testCases := []struct {
		name     string
		scenario string
		image    string
		isValid  bool
	}{
		{scenario: "Valid image with username", name: "cli", image: "alexellis/faas-cli", isValid: true},
		{scenario: "Valid image with remote repo", name: "cli", image: "10.1.95.201:5000/faas-cli", isValid: true},
		{scenario: "Invalid image - missing prefix", name: "cli", image: "faas-cli", isValid: false},
	}

	for _, testCase := range testCases {
		functions := map[string]stack.Function{
			"cli": stack.Function{
				Name:  testCase.name,
				Image: testCase.image,
			},
		}
		invalidImages := validateImages(functions)
		if len(invalidImages) > 0 && testCase.isValid == true {
			t.Logf("scenario: %s want %s to be valid, but was invalid", testCase.scenario, testCase.image)
			t.Fail()
		}
		if len(invalidImages) == 0 && testCase.isValid == false {
			t.Logf("scenario: %s want %s to be invalid, but was valid", testCase.scenario, testCase.image)
			t.Fail()
		}

	}
}
<|endoftext|>


<commit_before>package testing

import (
	"golang.org/x/net/context"

	"github.com/pachyderm/pachyderm/src/client/auth"
)

// InactiveAPIServer (in the auth/testing package) is an implementation of the
// pachyderm auth api that returns NotActivatedError for all requests. This is
// meant to be used with local PFS and PPS servers for testing, and should
// never be used in a real Pachyderm cluster
type InactiveAPIServer struct{}

// ActivateEnterpriseToken implements the ActivateEnterpriseToken RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) ActivateEnterpriseToken(ctx context.Context, req *auth.ActivateEnterpriseTokenRequest) (resp *auth.ActivateEnterpriseTokenResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetEnterpriseTokenState implements the GetEnterpriseTokenState RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetEnterpriseTokenState(ctx context.Context, req *auth.GetEnterpriseTokenStateRequest) (resp *auth.GetEnterpriseTokenStateResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// EnableAuth implements the EnableAuth RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) EnableAuth(ctx context.Context, req *auth.EnableAuthRequest) (resp *auth.EnableAuthResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// DisableAuth implements the DisableAuth RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) DisableAuth(ctx context.Context, req *auth.DisableAuthRequest) (resp *auth.DisableAuthResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetAdmins implements the GetAdmins RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetAdmins(ctx context.Context, req *auth.GetAdminsRequest) (resp *auth.GetAdminsResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// ModifyAdmins implements the ModifyAdmins RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) ModifyAdmins(ctx context.Context, req *auth.ModifyAdminsRequest) (resp *auth.ModifyAdminsResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// Authenticate implements the Authenticate RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) Authenticate(ctx context.Context, req *auth.AuthenticateRequest) (resp *auth.AuthenticateResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// Authorize implements the Authorize RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) Authorize(ctx context.Context, req *auth.AuthorizeRequest) (resp *auth.AuthorizeResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// WhoAmI implements the WhoAmI RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) WhoAmI(ctx context.Context, req *auth.WhoAmIRequest) (resp *auth.WhoAmIResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// SetScope implements the SetScope RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) SetScope(ctx context.Context, req *auth.SetScopeRequest) (resp *auth.SetScopeResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetScope implements the GetScope RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetScope(ctx context.Context, req *auth.GetScopeRequest) (resp *auth.GetScopeResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetACL implements the GetACL RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetACL(ctx context.Context, req *auth.GetACLRequest) (resp *auth.GetACLResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// SetACL implements the SetACL RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) SetACL(ctx context.Context, req *auth.SetACLRequest) (resp *auth.SetACLResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetCapability implements the GetCapability RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetCapability(ctx context.Context, req *auth.GetCapabilityRequest) (resp *auth.GetCapabilityResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// RevokeAuthToken implements the RevokeAuthToken RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) RevokeAuthToken(ctx context.Context, req *auth.RevokeAuthTokenRequest) (resp *auth.RevokeAuthTokenResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}
<commit_msg>Update InactiveAPIServer in auth/testing to export the new auth API and fix PFS tests<commit_after>package testing

import (
	"golang.org/x/net/context"

	"github.com/pachyderm/pachyderm/src/client/auth"
)

// InactiveAPIServer (in the auth/testing package) is an implementation of the
// pachyderm auth api that returns NotActivatedError for all requests. This is
// meant to be used with local PFS and PPS servers for testing, and should
// never be used in a real Pachyderm cluster
type InactiveAPIServer struct{}

// Activate implements the Activate RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) Activate(ctx context.Context, req *auth.ActivateRequest) (resp *auth.ActivateResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// Deactivate implements the Deactivate RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) Deactivate(ctx context.Context, req *auth.DeactivateRequest) (resp *auth.DeactivateResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetAdmins implements the GetAdmins RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetAdmins(ctx context.Context, req *auth.GetAdminsRequest) (resp *auth.GetAdminsResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// ModifyAdmins implements the ModifyAdmins RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) ModifyAdmins(ctx context.Context, req *auth.ModifyAdminsRequest) (resp *auth.ModifyAdminsResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// Authenticate implements the Authenticate RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) Authenticate(ctx context.Context, req *auth.AuthenticateRequest) (resp *auth.AuthenticateResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// Authorize implements the Authorize RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) Authorize(ctx context.Context, req *auth.AuthorizeRequest) (resp *auth.AuthorizeResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// WhoAmI implements the WhoAmI RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) WhoAmI(ctx context.Context, req *auth.WhoAmIRequest) (resp *auth.WhoAmIResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// SetScope implements the SetScope RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) SetScope(ctx context.Context, req *auth.SetScopeRequest) (resp *auth.SetScopeResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetScope implements the GetScope RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetScope(ctx context.Context, req *auth.GetScopeRequest) (resp *auth.GetScopeResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetACL implements the GetACL RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetACL(ctx context.Context, req *auth.GetACLRequest) (resp *auth.GetACLResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// SetACL implements the SetACL RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) SetACL(ctx context.Context, req *auth.SetACLRequest) (resp *auth.SetACLResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// GetCapability implements the GetCapability RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) GetCapability(ctx context.Context, req *auth.GetCapabilityRequest) (resp *auth.GetCapabilityResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}

// RevokeAuthToken implements the RevokeAuthToken RPC, but just returns NotActivatedError
func (a *InactiveAPIServer) RevokeAuthToken(ctx context.Context, req *auth.RevokeAuthTokenRequest) (resp *auth.RevokeAuthTokenResponse, retErr error) {
	return nil, auth.NotActivatedError{}
}
<|endoftext|>