<commit_before>package main

import (
	// stdlib
	"bytes"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"text/template"

	// external
	"github.com/VictorLowther/go-git/git"
	"github.com/russross/blackfriday"
)

const (
	GOIKIVERSION = "0.0.1"
)

var (
	displayVersion bool
	config         Config
	templates      *template.Template
	validPath      *regexp.Regexp
	validLink      *regexp.Regexp
	repo           *git.Repo
)

type Config struct {
	Port    int
	Host    string
	DataDir string
	Name    string
}

type Author struct {
	Name  string
	Email string
}

type Revision struct {
	Object      string
	Title       string
	Description string
	Author      Author
	Timestamp   string
}

type Page struct {
	Author      Author
	Title       string
	Body        string
	Description string
	Site        Config
	Revisions   []Revision
}

type Result struct {
	Title string
 	Content string
}

type SearchPage struct {
	Title string
	Results []Result
	Site Config
}

func (p *Page) save() error {
	filename := fileName(p.Title)
	datapath := dataPath(config.DataDir, filename)

	err := os.MkdirAll(filepath.Dir(datapath), 0777)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(datapath, []byte(p.Body), 0600)
	if err != nil {
		return err
	}

	_, err = gitAdd(filename)
	if err != nil {
		return err
	}

	message := p.Description
	if len(message) == 0 {
		message = fmt.Sprintf("Update %s", filename)
	}
	stdout, err := gitCommit(message)
	if err != nil {
		return err
	}
	log.Println(stdout)

	return nil
}

func fileName(title string) string {
	return title + ".txt"
}

func dataPath(dir string, file string) string {
	return filepath.Join(dir, file)
}

func gitExec(command string, args ...string) (out *bytes.Buffer, err error) {
	err = nil
	res, out, stderr := repo.Git(command, args...)
	runErr := res.Run()
	if runErr != nil {
		return out, runErr
	} else if stderr.Len() > 0 {
		return out, fmt.Errorf(stderr.String())
	}
	return
}

func gitShow(file string, revision string) (out *bytes.Buffer, err error) {
	return gitExec("show", fmt.Sprintf("%s:%s", revision, file))
}

func gitAdd(file string) (out *bytes.Buffer, err error) {
	return gitExec("add", file)
}

func gitCommit(message string) (out *bytes.Buffer, err error) {
	return gitExec("commit", "-m", message)
}

func gitLog(file string) (out *bytes.Buffer, err error) {
	return gitExec("log", "--pretty=format:%h %ad %s", "--date=relative", file)
}

func gitGrep(keyword string) (out *bytes.Buffer, err error) {
	log.Println("grep keyword", keyword)
	return gitExec("grep", "--ignore-case", keyword)
}

func loadPage(title string, revision string) (*Page, error) {
	filename := fileName(title)
	if len(revision) == 0 {
		revision = "HEAD"
	}
	body, err := gitShow(filename, revision)
	if err != nil {
		return &Page{
			Title: title,
			Body:  body.String(),
			Site:  config,
		}, fmt.Errorf("Unable to load page content from %s at %s\n", filename, revision)
	}
	return &Page{Title: title, Body: body.String(), Site: config}, nil
}

func parseLog(bytes []byte) *Revision {
	line := string(bytes)
	re := regexp.MustCompile(`(.{0,7}) (\d+ \w+ ago) (.*)`)
	matches := re.FindStringSubmatch(line)
	if len(matches) == 4 {
		return &Revision{Object: matches[1], Timestamp: matches[2], Description: matches[3]}
	}
	return nil
	/* TODO
	   we want to show more information in the log; author for example
	   and then gather up the log, parsing it into Revisions (or rename to Log)
	   then we can use the historyHandler to display all available revisions and
	   provide links to show that content.

	   also look into what it takes to do diffs on the material as it should be
	   similar
	*/
}

func parseGrepOutput(output *bytes.Buffer) []Result {
	var err error
	var bytes []byte
	results := make([]Result, 0)

	log.Println("grep output", output.String())
	re := regexp.MustCompile(`(.+)\.txt:(.*)`)
	for err == nil {
		bytes, err = output.ReadBytes('\n')
		matches := re.FindStringSubmatch(string(bytes))
		if len(matches) == 3 {
			results = append(results, Result{Title: matches[1], Content: matches[2]})
		}
	}
	return results
}

func processLinks(content []byte, link *regexp.Regexp) []byte {
	return link.ReplaceAllFunc(content, func(match []byte) []byte {
		return link.ReplaceAll(match, []byte("[$1]($1)"))
	})
}

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	err := templates.ExecuteTemplate(w, tmpl, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func renderSearchTemplate(w http.ResponseWriter, tmpl string, p *SearchPage) {
	err := templates.ExecuteTemplate(w, tmpl, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		path := r.URL.Path
		log.Println(path)
		if path == "/" {
			http.Redirect(w, r, "/view/FrontPage", http.StatusFound)
			return
		}
		m := validPath.FindStringSubmatch(path)
		if m == nil {
			http.NotFound(w, r)
			return
		}
		fn(w, r, m[2])
	}
}

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
	revision := r.FormValue("revision")
	if revision == "" {
		revision = "HEAD"
	}

	p, err := loadPage(title, revision)
	if err != nil {
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return
	}

	content := []byte(p.Body)
	content = processLinks(content, validLink)
	content = blackfriday.MarkdownCommon(content)
	p.Body = string(content)

	renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *http.Request, title string) {
	revision := r.FormValue("revision")
	if revision == "" {
		revision = "HEAD"
	}

	p, err := loadPage(title, revision)
	if err != nil {
		p = &Page{Title: title, Site: config}
	}

	renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *http.Request, title string) {
	body := r.FormValue("body")
	description := r.FormValue("description")
	p := &Page{Title: title, Body: body, Description: description}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, r, "/view/"+title, http.StatusFound)
}

func historyHandler(w http.ResponseWriter, r *http.Request, title string) {
	author := Author{Name: "Anonymous", Email: ""}
	stdOut, logErr := gitLog(fileName(title))
	if logErr != nil {
		log.Println(logErr)
	}
	var bytes []byte
	var err error
	revisions := make([]Revision, 0)
	for err == nil {
		bytes, err = stdOut.ReadBytes('\n')
		revision := parseLog(bytes)
		if revision == nil {
			continue
		}
		revision.Author = author
		revision.Title = title
		revisions = append(revisions, *revision)
	}
	p := &Page{Title: title, Body: "", Site: config, Revisions: revisions, Author: author}
	renderTemplate(w, "history", p)
}

func searchHandler(w http.ResponseWriter, r *http.Request) {
	search := r.FormValue("search")
	grep, err := gitGrep(search)
	if err != nil {
		log.Println("error in search", err)
	}

	results := parseGrepOutput(grep)
	sp := &SearchPage{Title: "Search", Results: results, Site: config}
	renderSearchTemplate(w, "search", sp)
}

// Initialize the configuration options, templates and URL and link regexes.
func init() {
	flag.StringVar(&config.Name, "name", "Goiki", "Wiki name")
	flag.IntVar(&config.Port, "port", 4567, "Bind port")
	flag.StringVar(&config.Host, "host", "0.0.0.0", "Hostname or IP address to listen on")
	flag.StringVar(&config.DataDir, "data-dir", "./data", "Directory for page data")
	flag.BoolVar(&displayVersion, "version", false, "Display version and exit")

	templates = template.Must(template.ParseFiles("templates/_header.html", "templates/_footer.html", "templates/edit.html", "templates/view.html", "templates/history.html", "templates/search.html"))
	validPath = regexp.MustCompile("^/(edit|save|view|history)/([a-zA-Z0-9/]+)$")
	validLink = regexp.MustCompile(`\[([^\]]+)]\(\)`)
}

// Main. Parse the configuration flags and start the web server based on those
// configuration flags.
func main() {
	flag.Parse()

	// Display version and exit
	if displayVersion {
		fmt.Println("Goiki", GOIKIVERSION)
		return
	}

	log.Println(config)

	// Load repo
	var err error
	if repo, err = git.Open(config.DataDir); err != nil {
		log.Fatalf("Unable to open the repo at %s. Please check to make sure it exists and is initialized.\n%v", config.DataDir, err)
	}

	// Define routes
	http.Handle("/static/", http.FileServer(http.Dir("./")))
	http.HandleFunc("/search/", searchHandler)
	http.HandleFunc("/", makeHandler(viewHandler))
	http.HandleFunc("/view/", makeHandler(viewHandler))
	http.HandleFunc("/edit/", makeHandler(editHandler))
	http.HandleFunc("/save/", makeHandler(saveHandler))
	http.HandleFunc("/history/", makeHandler(historyHandler))

	l, err := net.Listen("tcp", fmt.Sprintf("%s:%d", config.Host, config.Port))
	if err != nil {
		log.Fatal(err)
	}

	s := &http.Server{}
	s.Serve(l)
	return
}
<commit_msg>Add security to edit/save actions<commit_after>package main

import (
	// stdlib
	"bytes"
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"text/template"

	// external
	"github.com/VictorLowther/go-git/git"
	auth "github.com/abbot/go-http-auth"
	"github.com/russross/blackfriday"
)

const (
	GOIKIVERSION = "0.0.1"
)

var (
	displayVersion bool
	config         Config
	templates      *template.Template
	validPath      *regexp.Regexp
	validLink      *regexp.Regexp
	repo           *git.Repo
	authenticator  *auth.BasicAuth
	users          map[string]User
)

type Config struct {
	Port     int
	Host     string
	DataDir  string
	AuthFile string
	Name     string
}

type User struct {
	Username string
	Password string
	Name     string
	Email    string
}

type Author struct {
	Name  string
	Email string
}

type Revision struct {
	Object      string
	Title       string
	Description string
	Author      Author
	Timestamp   string
}

type Page struct {
	Author      Author
	Title       string
	Body        string
	Description string
	Site        Config
	Revisions   []Revision
}

type Result struct {
	Title   string
	Content string
}

type SearchPage struct {
	Title   string
	Results []Result
	Site    Config
}

func (p *Page) save() error {
	filename := fileName(p.Title)
	datapath := dataPath(config.DataDir, filename)

	err := os.MkdirAll(filepath.Dir(datapath), 0777)
	if err != nil {
		return err
	}
	err = ioutil.WriteFile(datapath, []byte(p.Body), 0600)
	if err != nil {
		return err
	}

	_, err = gitAdd(filename)
	if err != nil {
		return err
	}

	message := p.Description
	if len(message) == 0 {
		message = fmt.Sprintf("Update %s", filename)
	}
	stdout, err := gitCommit(message)
	if err != nil {
		return err
	}
	log.Println(stdout)

	return nil
}

func fileName(title string) string {
	return title + ".txt"
}

func dataPath(dir string, file string) string {
	return filepath.Join(dir, file)
}

func gitExec(command string, args ...string) (out *bytes.Buffer, err error) {
	err = nil
	res, out, stderr := repo.Git(command, args...)
	runErr := res.Run()
	if runErr != nil {
		return out, runErr
	} else if stderr.Len() > 0 {
		return out, fmt.Errorf(stderr.String())
	}
	return
}

func gitShow(file string, revision string) (out *bytes.Buffer, err error) {
	return gitExec("show", fmt.Sprintf("%s:%s", revision, file))
}

func gitAdd(file string) (out *bytes.Buffer, err error) {
	return gitExec("add", file)
}

func gitCommit(message string) (out *bytes.Buffer, err error) {
	return gitExec("commit", "-m", message)
}

func gitLog(file string) (out *bytes.Buffer, err error) {
	return gitExec("log", "--pretty=format:%h %ad %s", "--date=relative", file)
}

func gitGrep(keyword string) (out *bytes.Buffer, err error) {
	log.Println("grep keyword", keyword)
	return gitExec("grep", "--ignore-case", keyword)
}

func loadPage(title string, revision string) (*Page, error) {
	filename := fileName(title)
	if len(revision) == 0 {
		revision = "HEAD"
	}
	body, err := gitShow(filename, revision)
	if err != nil {
		return &Page{
			Title: title,
			Body:  body.String(),
			Site:  config,
		}, fmt.Errorf("Unable to load page content from %s at %s\n", filename, revision)
	}
	return &Page{Title: title, Body: body.String(), Site: config}, nil
}

func parseLog(bytes []byte) *Revision {
	line := string(bytes)
	re := regexp.MustCompile(`(.{0,7}) (\d+ \w+ ago) (.*)`)
	matches := re.FindStringSubmatch(line)
	if len(matches) == 4 {
		return &Revision{Object: matches[1], Timestamp: matches[2], Description: matches[3]}
	}
	return nil
	/* TODO
	   we want to show more information in the log; author for example
	   and then gather up the log, parsing it into Revisions (or rename to Log)
	   then we can use the historyHandler to display all available revisions and
	   provide links to show that content.

	   also look into what it takes to do diffs on the material as it should be
	   similar
	*/
}

func parseGrepOutput(output *bytes.Buffer) []Result {
	var err error
	var bytes []byte
	results := make([]Result, 0)

	log.Println("grep output", output.String())
	re := regexp.MustCompile(`(.+)\.txt:(.*)`)
	for err == nil {
		bytes, err = output.ReadBytes('\n')
		matches := re.FindStringSubmatch(string(bytes))
		if len(matches) == 3 {
			results = append(results, Result{Title: matches[1], Content: matches[2]})
		}
	}
	return results
}

func processLinks(content []byte, link *regexp.Regexp) []byte {
	return link.ReplaceAllFunc(content, func(match []byte) []byte {
		return link.ReplaceAll(match, []byte("[$1]($1)"))
	})
}

func renderTemplate(w http.ResponseWriter, tmpl string, p *Page) {
	err := templates.ExecuteTemplate(w, tmpl, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func renderSearchTemplate(w http.ResponseWriter, tmpl string, p *SearchPage) {
	err := templates.ExecuteTemplate(w, tmpl, p)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

func makeHandler(fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		path := r.URL.Path
		log.Println(path)
		if path == "/" {
			http.Redirect(w, r, "/view/FrontPage", http.StatusFound)
			return
		}
		m := validPath.FindStringSubmatch(path)
		if m == nil {
			http.NotFound(w, r)
			return
		}
		fn(w, r, m[2])
	}
}

func makeAuthHandler(fn func(http.ResponseWriter, *auth.AuthenticatedRequest, string)) auth.AuthenticatedHandlerFunc {
	return func(w http.ResponseWriter, r *auth.AuthenticatedRequest) {
		path := r.URL.Path
		log.Println(path)
		m := validPath.FindStringSubmatch(path)
		if m == nil {
			http.NotFound(w, &r.Request)
			return
		}
		fn(w, r, m[2])
	}
}

func viewHandler(w http.ResponseWriter, r *http.Request, title string) {
	revision := r.FormValue("revision")
	if revision == "" {
		revision = "HEAD"
	}

	p, err := loadPage(title, revision)
	if err != nil {
		http.Redirect(w, r, "/edit/"+title, http.StatusFound)
		return
	}

	content := []byte(p.Body)
	content = processLinks(content, validLink)
	content = blackfriday.MarkdownCommon(content)
	p.Body = string(content)

	renderTemplate(w, "view", p)
}

func editHandler(w http.ResponseWriter, r *auth.AuthenticatedRequest, title string) {
	revision := r.FormValue("revision")
	if revision == "" {
		revision = "HEAD"
	}

	p, err := loadPage(title, revision)
	if err != nil {
		p = &Page{Title: title, Site: config}
	}

	renderTemplate(w, "edit", p)
}

func saveHandler(w http.ResponseWriter, r *auth.AuthenticatedRequest, title string) {
	body := r.FormValue("body")
	description := r.FormValue("description")
	p := &Page{Title: title, Body: body, Description: description}
	err := p.save()
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	http.Redirect(w, &r.Request, "/view/"+title, http.StatusFound)
}

func historyHandler(w http.ResponseWriter, r *http.Request, title string) {
	author := Author{Name: "Anonymous", Email: ""}
	stdOut, logErr := gitLog(fileName(title))
	if logErr != nil {
		log.Println(logErr)
	}
	var bytes []byte
	var err error
	revisions := make([]Revision, 0)
	for err == nil {
		bytes, err = stdOut.ReadBytes('\n')
		revision := parseLog(bytes)
		if revision == nil {
			continue
		}
		revision.Author = author
		revision.Title = title
		revisions = append(revisions, *revision)
	}
	p := &Page{Title: title, Body: "", Site: config, Revisions: revisions, Author: author}
	renderTemplate(w, "history", p)
}

func searchHandler(w http.ResponseWriter, r *http.Request) {
	search := r.FormValue("search")
	grep, err := gitGrep(search)
	if err != nil {
		log.Println("error in search", err)
	}

	results := parseGrepOutput(grep)
	sp := &SearchPage{Title: "Search", Results: results, Site: config}
	renderSearchTemplate(w, "search", sp)
}

// Initialize the configuration options, templates and URL and link regexes.
func init() {
	flag.StringVar(&config.Name, "name", "Goiki", "Wiki name")
	flag.IntVar(&config.Port, "port", 4567, "Bind port")
	flag.StringVar(&config.Host, "host", "0.0.0.0", "Hostname or IP address to listen on")
	flag.StringVar(&config.DataDir, "data", "./data", "Directory for page data")
	flag.StringVar(&config.AuthFile, "auth", "./auth.json", "File containing user authentication")
	flag.BoolVar(&displayVersion, "version", false, "Display version and exit")

	flag.Parse()

	users, _ = loadAuth(config.AuthFile)

	authenticator = auth.NewBasicAuthenticator(serviceAddress(config.Host, config.Port), secret)
	templates = template.Must(template.ParseFiles("templates/_header.html", "templates/_footer.html", "templates/edit.html", "templates/view.html", "templates/history.html", "templates/search.html"))
	validPath = regexp.MustCompile("^/(edit|save|view|history)/([a-zA-Z0-9/]+)$")
	validLink = regexp.MustCompile(`\[([^\]]+)]\(\)`)
}

func secret(username, realm string) string {
	return users[username].Password
}

func loadAuth(file string) (map[string]User, error) {
	var auth map[string]User
	authJson, err := ioutil.ReadFile(file)
	if err != nil {
		return auth, err
	}
	var users []User
	err = json.Unmarshal(authJson, &users)
	if err != nil {
		return auth, err
	}
	auth = make(map[string]User, len(users))
	for _, user := range users {
		auth[user.Username] = user
	}
	return auth, nil
}

func serviceAddress(host string, port int) string {
	return fmt.Sprintf("%s:%d", host, port)
}

// Main. Parse the configuration flags and start the web server based on those
// configuration flags.
func main() {
	flag.Parse()

	// Display version and exit
	if displayVersion {
		fmt.Println("Goiki", GOIKIVERSION)
		return
	}

	log.Println(config)

	// Load repo
	var err error
	if repo, err = git.Open(config.DataDir); err != nil {
		log.Fatalf("Unable to open the repo at %s. Please check to make sure it exists and is initialized.\n%v", config.DataDir, err)
	}

	// Define routes
	//http.HandleFunc("/", authenticator.Wrap(authHandler))
	http.Handle("/static/", http.FileServer(http.Dir("./")))
	http.HandleFunc("/search/", searchHandler)
	http.HandleFunc("/", makeHandler(viewHandler))
	http.HandleFunc("/view/", makeHandler(viewHandler))
	http.HandleFunc("/history/", makeHandler(historyHandler))

	http.HandleFunc("/edit/", authenticator.Wrap(makeAuthHandler(editHandler)))
	http.HandleFunc("/save/", authenticator.Wrap(makeAuthHandler(saveHandler)))

	l, err := net.Listen("tcp", serviceAddress(config.Host, config.Port))
	if err != nil {
		log.Fatal(err)
	}

	s := &http.Server{}
	s.Serve(l)
	return
}
<|endoftext|>


<commit_before>package api

// Scaling is used to query scaling-related API endpoints
type Scaling struct {
	client *Client
}

// Scaling returns a handle on the scaling endpoints.
func (c *Client) Scaling() *Scaling {
	return &Scaling{client: c}
}

func (s *Scaling) ListPolicies(q *QueryOptions) ([]*ScalingPolicyListStub, *QueryMeta, error) {
	var resp []*ScalingPolicyListStub
	qm, err := s.client.query("/v1/scaling/policies", &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return resp, qm, nil
}

func (s *Scaling) GetPolicy(ID string, q *QueryOptions) (*ScalingPolicy, *QueryMeta, error) {
	var policy ScalingPolicy
	qm, err := s.client.query("/v1/scaling/policy/"+ID, &policy, q)
	if err != nil {
		return nil, nil, err
	}
	return &policy, qm, nil
}

func (p *ScalingPolicy) Canonicalize(taskGroupCount int) {
	if p.Enabled == nil {
		p.Enabled = boolToPtr(true)
	}
	if p.Min == nil {
		var m int64 = int64(taskGroupCount)
		p.Min = &m
	}
}

// ScalingRequest is the payload for a generic scaling action
type ScalingRequest struct {
	Count   *int64
	Target  map[string]string
	Message string
	Error   bool
	Meta    map[string]interface{}
	WriteRequest
	// this is effectively a job update, so we need the ability to override policy.
	PolicyOverride bool
}

// ScalingPolicy is the user-specified API object for an autoscaling policy
type ScalingPolicy struct {
	ID          string
	Namespace   string
	Target      map[string]string
	Min         *int64
	Max         int64
	Policy      map[string]interface{}
	Enabled     *bool
	CreateIndex uint64
	ModifyIndex uint64
}

// ScalingPolicyListStub is used to return a subset of scaling policy information
// for the scaling policy list
type ScalingPolicyListStub struct {
	ID          string
	Enabled     bool
	Target      map[string]string
	CreateIndex uint64
	ModifyIndex uint64
}

// JobScaleStatusResponse is used to return information about job scaling status
type JobScaleStatusResponse struct {
	JobID          string
	JobCreateIndex uint64
	JobModifyIndex uint64
	JobStopped     bool
	TaskGroups     map[string]TaskGroupScaleStatus
}

type TaskGroupScaleStatus struct {
	Desired   int
	Placed    int
	Running   int
	Healthy   int
	Unhealthy int
	Events    []ScalingEvent
}

type ScalingEvent struct {
	Error       bool
	Message     string
	Meta        map[string]interface{}
	EvalID      *string
	Time        uint64
	CreateIndex uint64
}
<commit_msg>the api.ScalingEvent struct was missing the .Count field<commit_after>package api

// Scaling is used to query scaling-related API endpoints
type Scaling struct {
	client *Client
}

// Scaling returns a handle on the scaling endpoints.
func (c *Client) Scaling() *Scaling {
	return &Scaling{client: c}
}

func (s *Scaling) ListPolicies(q *QueryOptions) ([]*ScalingPolicyListStub, *QueryMeta, error) {
	var resp []*ScalingPolicyListStub
	qm, err := s.client.query("/v1/scaling/policies", &resp, q)
	if err != nil {
		return nil, nil, err
	}
	return resp, qm, nil
}

func (s *Scaling) GetPolicy(ID string, q *QueryOptions) (*ScalingPolicy, *QueryMeta, error) {
	var policy ScalingPolicy
	qm, err := s.client.query("/v1/scaling/policy/"+ID, &policy, q)
	if err != nil {
		return nil, nil, err
	}
	return &policy, qm, nil
}

func (p *ScalingPolicy) Canonicalize(taskGroupCount int) {
	if p.Enabled == nil {
		p.Enabled = boolToPtr(true)
	}
	if p.Min == nil {
		var m int64 = int64(taskGroupCount)
		p.Min = &m
	}
}

// ScalingRequest is the payload for a generic scaling action
type ScalingRequest struct {
	Count   *int64
	Target  map[string]string
	Message string
	Error   bool
	Meta    map[string]interface{}
	WriteRequest
	// this is effectively a job update, so we need the ability to override policy.
	PolicyOverride bool
}

// ScalingPolicy is the user-specified API object for an autoscaling policy
type ScalingPolicy struct {
	ID          string
	Namespace   string
	Target      map[string]string
	Min         *int64
	Max         int64
	Policy      map[string]interface{}
	Enabled     *bool
	CreateIndex uint64
	ModifyIndex uint64
}

// ScalingPolicyListStub is used to return a subset of scaling policy information
// for the scaling policy list
type ScalingPolicyListStub struct {
	ID          string
	Enabled     bool
	Target      map[string]string
	CreateIndex uint64
	ModifyIndex uint64
}

// JobScaleStatusResponse is used to return information about job scaling status
type JobScaleStatusResponse struct {
	JobID          string
	JobCreateIndex uint64
	JobModifyIndex uint64
	JobStopped     bool
	TaskGroups     map[string]TaskGroupScaleStatus
}

type TaskGroupScaleStatus struct {
	Desired   int
	Placed    int
	Running   int
	Healthy   int
	Unhealthy int
	Events    []ScalingEvent
}

type ScalingEvent struct {
	Count       *int64
	Error       bool
	Message     string
	Meta        map[string]interface{}
	EvalID      *string
	Time        uint64
	CreateIndex uint64
}
<|endoftext|>


<commit_before>package api

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

	"github.com/MyHomeworkSpace/api-server/data"
	"github.com/MyHomeworkSpace/api-server/errorlog"
	"github.com/labstack/echo"
)

var MainRegistry data.SchoolRegistry

type DetailedSchoolErrorResponse struct {
	Status  string                 `json:"status"`
	Error   string                 `json:"error"`
	Details map[string]interface{} `json:"details"`
}

type SchoolResultResponse struct {
	Status string             `json:"status"`
	School *data.SchoolResult `json:"school"`
}

func routeSchoolsEnroll(w http.ResponseWriter, r *http.Request, ec echo.Context, c RouteContext) {
	if ec.FormValue("school") == "" || ec.FormValue("data") == "" {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "missing_params"})
		return
	}

	// find school
	school, err := MainRegistry.GetSchoolByID(ec.FormValue("school"))
	if err == data.ErrNotFound {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
		return
	} else if err != nil {
		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	// check we're not already enrolled
	for _, userSchool := range c.User.Schools {
		if userSchool.SchoolID == school.ID() {
			// we are
			ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "already_enrolled"})
			return
		}
	}

	// parse data
	enrollDataString := ec.FormValue("data")
	enrollData := map[string]interface{}{}

	err = json.Unmarshal([]byte(enrollDataString), &enrollData)
	if err != nil {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
		return
	}

	// actually do it

	// new transaction
	tx, err := DB.Begin()
	if err != nil {
		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	// clear any existing school record
	_, err = tx.Exec("DELETE FROM schools WHERE schoolId = ? AND userId = ?", school.ID(), c.User.ID)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	// enroll
	result, err := school.Enroll(tx, c.User, enrollData)
	if err != nil {
		tx.Rollback()

		detailedSchoolError, ok := err.(data.DetailedSchoolError)
		if ok {
			// it wants to report something
			ec.JSON(http.StatusOK, DetailedSchoolErrorResponse{"error", detailedSchoolError.Code, detailedSchoolError.Details})
			return
		}

		schoolError, ok := err.(data.SchoolError)
		if ok {
			// it wants to report an error code
			ec.JSON(http.StatusOK, ErrorResponse{"error", schoolError.Code})
			return
		}

		// server error
		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	resultDataBytes, err := json.Marshal(result)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	resultDataString := string(resultDataBytes)

	// save the new data
	_, err = tx.Exec("INSERT INTO schools(schoolId, data, userId) VALUES(?, ?, ?)", school.ID(), resultDataString, c.User.ID)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	// go!
	err = tx.Commit()
	if err != nil {
		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	ec.JSON(http.StatusOK, StatusResponse{"ok"})
}

func routeSchoolsLookup(w http.ResponseWriter, r *http.Request, ec echo.Context, c RouteContext) {
	if ec.FormValue("email") == "" {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "missing_params"})
		return
	}

	email := ec.FormValue("email")

	emailParts := strings.Split(email, "@")

	if len(emailParts) != 2 {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
		return
	}

	domain := strings.ToLower(strings.TrimSpace(emailParts[1]))

	school, err := MainRegistry.GetSchoolByEmailDomain(domain)

	if err == data.ErrNotFound {
		ec.JSON(http.StatusOK, SchoolResultResponse{"ok", nil})
		return
	} else if err != nil {
		errorlog.LogError("looking up school by email domain", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	schoolResult := data.SchoolResult{
		SchoolID:    school.ID(),
		DisplayName: school.Name(),
	}

	ec.JSON(http.StatusOK, SchoolResultResponse{"ok", &schoolResult})
}

func routeSchoolsUnenroll(w http.ResponseWriter, r *http.Request, ec echo.Context, c RouteContext) {
	if ec.FormValue("school") == "" {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "missing_params"})
		return
	}

	// find school
	school, err := MainRegistry.GetSchoolByID(ec.FormValue("school"))
	if err == data.ErrNotFound {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
		return
	} else if err != nil {
		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	foundSchool := false

	// check we're already enrolled
	for _, userSchool := range c.User.Schools {
		if userSchool.SchoolID == school.ID() {
			// we are
			foundSchool = true
			break
		}
	}

	if !foundSchool {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "not_enrolled"})
		return
	}

	// remove it
	tx, err := DB.Begin()
	if err != nil {
		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	err = school.Unenroll(tx, c.User)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	_, err = tx.Exec("DELETE FROM schools WHERE schoolId = ? AND userId = ?", school.ID(), c.User.ID)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	err = tx.Commit()
	if err != nil {
		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	ec.JSON(http.StatusOK, StatusResponse{"ok"})
}
<commit_msg>allow reenrolling a school in order to update your schedule data<commit_after>package api

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

	"github.com/MyHomeworkSpace/api-server/data"
	"github.com/MyHomeworkSpace/api-server/errorlog"
	"github.com/labstack/echo"
)

var MainRegistry data.SchoolRegistry

type DetailedSchoolErrorResponse struct {
	Status  string                 `json:"status"`
	Error   string                 `json:"error"`
	Details map[string]interface{} `json:"details"`
}

type SchoolResultResponse struct {
	Status string             `json:"status"`
	School *data.SchoolResult `json:"school"`
}

func routeSchoolsEnroll(w http.ResponseWriter, r *http.Request, ec echo.Context, c RouteContext) {
	if ec.FormValue("school") == "" || ec.FormValue("data") == "" {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "missing_params"})
		return
	}

	var err error
	reenroll := false
	if ec.FormValue("reenroll") != "" {
		reenroll, err = strconv.ParseBool(ec.FormValue("reenroll"))
		if err != nil {
			ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
			return
		}
	}

	// find school
	school, err := MainRegistry.GetSchoolByID(ec.FormValue("school"))
	if err == data.ErrNotFound {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
		return
	} else if err != nil {
		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	// check we're not already enrolled
	enrolled := false
	for _, userSchool := range c.User.Schools {
		if userSchool.SchoolID == school.ID() {
			// we are
			enrolled = true
		}
	}

	if reenroll {
		if !enrolled {
			ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "not_enrolled"})
			return
		}
	} else {
		if enrolled {
			ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "already_enrolled"})
			return
		}
	}

	// parse data
	enrollDataString := ec.FormValue("data")
	enrollData := map[string]interface{}{}

	err = json.Unmarshal([]byte(enrollDataString), &enrollData)
	if err != nil {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
		return
	}

	// actually do it

	// new transaction
	tx, err := DB.Begin()
	if err != nil {
		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	// clear any existing school record
	_, err = tx.Exec("DELETE FROM schools WHERE schoolId = ? AND userId = ?", school.ID(), c.User.ID)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	if reenroll {
		err := school.Unenroll(tx, c.User)
		if err != nil {
			tx.Rollback()

			errorlog.LogError("enrolling in school - unenrolling before enroll", err)
			ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
			return
		}
	}

	// enroll
	result, err := school.Enroll(tx, c.User, enrollData)
	if err != nil {
		tx.Rollback()

		detailedSchoolError, ok := err.(data.DetailedSchoolError)
		if ok {
			// it wants to report something
			ec.JSON(http.StatusOK, DetailedSchoolErrorResponse{"error", detailedSchoolError.Code, detailedSchoolError.Details})
			return
		}

		schoolError, ok := err.(data.SchoolError)
		if ok {
			// it wants to report an error code
			ec.JSON(http.StatusOK, ErrorResponse{"error", schoolError.Code})
			return
		}

		// server error
		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	resultDataBytes, err := json.Marshal(result)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	resultDataString := string(resultDataBytes)

	// save the new data
	_, err = tx.Exec("INSERT INTO schools(schoolId, data, userId) VALUES(?, ?, ?)", school.ID(), resultDataString, c.User.ID)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	// go!
	err = tx.Commit()
	if err != nil {
		errorlog.LogError("enrolling in school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	ec.JSON(http.StatusOK, StatusResponse{"ok"})
}

func routeSchoolsLookup(w http.ResponseWriter, r *http.Request, ec echo.Context, c RouteContext) {
	if ec.FormValue("email") == "" {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "missing_params"})
		return
	}

	email := ec.FormValue("email")

	emailParts := strings.Split(email, "@")

	if len(emailParts) != 2 {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
		return
	}

	domain := strings.ToLower(strings.TrimSpace(emailParts[1]))

	school, err := MainRegistry.GetSchoolByEmailDomain(domain)

	if err == data.ErrNotFound {
		ec.JSON(http.StatusOK, SchoolResultResponse{"ok", nil})
		return
	} else if err != nil {
		errorlog.LogError("looking up school by email domain", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	schoolResult := data.SchoolResult{
		SchoolID:    school.ID(),
		DisplayName: school.Name(),
	}

	ec.JSON(http.StatusOK, SchoolResultResponse{"ok", &schoolResult})
}

func routeSchoolsUnenroll(w http.ResponseWriter, r *http.Request, ec echo.Context, c RouteContext) {
	if ec.FormValue("school") == "" {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "missing_params"})
		return
	}

	// find school
	school, err := MainRegistry.GetSchoolByID(ec.FormValue("school"))
	if err == data.ErrNotFound {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "invalid_params"})
		return
	} else if err != nil {
		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	foundSchool := false

	// check we're already enrolled
	for _, userSchool := range c.User.Schools {
		if userSchool.SchoolID == school.ID() {
			// we are
			foundSchool = true
			break
		}
	}

	if !foundSchool {
		ec.JSON(http.StatusBadRequest, ErrorResponse{"error", "not_enrolled"})
		return
	}

	// remove it
	tx, err := DB.Begin()
	if err != nil {
		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	err = school.Unenroll(tx, c.User)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	_, err = tx.Exec("DELETE FROM schools WHERE schoolId = ? AND userId = ?", school.ID(), c.User.ID)
	if err != nil {
		tx.Rollback()

		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	err = tx.Commit()
	if err != nil {
		errorlog.LogError("unenrolling from school", err)
		ec.JSON(http.StatusInternalServerError, ErrorResponse{"error", "internal_server_error"})
		return
	}

	ec.JSON(http.StatusOK, StatusResponse{"ok"})
}
<|endoftext|>


<commit_before>package gorpc

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"math"

	"github.com/gsdocker/gserrors"
)

//gsbuff public errors
var (
	ErrDecode    = errors.New("decode error")
	ErrWriteNone = errors.New("write nothing")
	ErrEncode    = errors.New("encoding error")
)

//Reader the gsbuff Reader interface, Mixin io.ByteReader and io.Reader interfaces
type Reader interface {
	io.ByteReader
	io.Reader
}

//Writer the gsbuff Writer interface, Mixin io.ByteWriterand io.Writer interfaces
type Writer interface {
	io.ByteWriter
	io.Writer
}

//ReadByte read byte from Reader interface
func ReadByte(reader Reader) (byte, error) {
	val, err := reader.ReadByte()

	if err != nil {
		return 0, gserrors.Newf(err, "read byte error")
	}

	return val, err
}

//ReadSByte read sbyte from Reader interface
func ReadSByte(reader Reader) (int8, error) {
	v, err := reader.ReadByte()
	return int8(v), err
}

//ReadUInt16 read uint16 from Reader interface
func ReadUInt16(reader Reader) (uint16, error) {
	buf := make([]byte, 2)

	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint16 error")
	}

	return uint16(buf[0]) | uint16(buf[1])<<8, nil
}

//ReadInt16 read uint16 from Reader interface
func ReadInt16(reader Reader) (int16, error) {
	v, err := ReadUInt16(reader)

	return int16(v), err
}

//ReadUint32 read uint32 from Reader interface
func ReadUint32(reader Reader) (uint32, error) {
	buf := make([]byte, 4)

	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint32 error")
	}

	return uint32(buf[3])<<24 | uint32(buf[2])<<16 | uint32(buf[1])<<8 | uint32(buf[0]), nil
}

//ReadInt32 read int32 from Reader interface
func ReadInt32(reader Reader) (int32, error) {
	v, err := ReadUint32(reader)

	return int32(v), err
}

//ReadUInt64 read uint16 from Reader interface
func ReadUInt64(reader Reader) (uint64, error) {
	buf := make([]byte, 8)
	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint64 error")
	}

	var ret uint64

	for i, v := range buf {
		ret |= uint64(v) << uint(i*8)
	}

	return ret, nil
}

//ReadInt64 read int16 from Reader interface
func ReadInt64(reader Reader) (int64, error) {
	v, err := ReadUInt64(reader)

	return int64(v), err
}

//ReadFloat32 read float32 from Reader interface
func ReadFloat32(reader Reader) (float32, error) {
	x, err := ReadUint32(reader)

	if err != nil {
		return 0, err
	}

	return math.Float32frombits(x), nil
}

//ReadFloat64 read float64 from Reader interface
func ReadFloat64(reader Reader) (float64, error) {
	x, err := ReadUInt64(reader)
	if err != nil {
		return 0, err
	}
	return math.Float64frombits(x), nil
}

//ReadString read string
func ReadString(reader Reader) (string, error) {
	len, err := ReadUInt16(reader)

	if err != nil {
		return "", gserrors.Newf(err, "read string len error")
	}

	buff := make([]byte, len)

	_, err = reader.Read(buff)

	if err != nil {
		return "", gserrors.Newf(err, "read string[%d] body error", len)
	}

	return string(buff), nil
}

//ReadBytes read bytes from reader
func ReadBytes(reader Reader, val []byte) error {
	_, err := reader.Read(val)

	if err != nil {
		return gserrors.Newf(err, "read bytes[%d] error", len(val))
	}

	return nil
}

//ReadBool read bool
func ReadBool(reader Reader) (bool, error) {
	b, err := ReadByte(reader)

	if b == 1 {
		return true, err
	}

	return false, err
}

//WriteByte write one byte into bytes buffer
func WriteByte(writer Writer, v byte) error {
	return writer.WriteByte(v)
}

//WriteBool write bool byte into bytes buffer
func WriteBool(writer Writer, v bool) error {
	if v {
		return writer.WriteByte(1)
	}
	return writer.WriteByte(0)
}

//WriteSByte write sbyte into stream
func WriteSByte(writer Writer, v int8) error {
	return writer.WriteByte(byte(v))
}

//WriteUInt16 write a little-endian uint16 into a writer stream
func WriteUInt16(writer Writer, v uint16) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	return nil
}

//WriteInt16 write a little-endian int16 into a writer stream
func WriteInt16(writer Writer, v int16) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	return nil
}

//WriteUint32 write a little-endian uint32 into a writer stream
func WriteUint32(writer Writer, v uint32) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	if err := WriteByte(writer, byte(v>>16)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>24)); err != nil {
		return err
	}

	return nil
}

//WriteInt32 write a little-endian int32 into a writer stream
func WriteInt32(writer Writer, v int32) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	if err := WriteByte(writer, byte(v>>16)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>24)); err != nil {
		return err
	}

	return nil
}

//WriteUInt64 write a little-endian uint64 into a writer stream
func WriteUInt64(writer Writer, v uint64) error {
	for i := uint(0); i < 8; i++ {
		if err := WriteByte(writer, byte(v>>(i*8))); err != nil {
			return err
		}
	}

	return nil
}

//WriteInt64 write a little-endian int64 into a writer stream
func WriteInt64(writer Writer, v int64) error {
	for i := uint(0); i < 8; i++ {
		if err := WriteByte(writer, byte(v>>(i*8))); err != nil {
			return err
		}
	}

	return nil
}

//WriteFloat32 write a little-endian int64 into a writer stream
func WriteFloat32(writer Writer, n float32) error {
	return WriteUint32(writer, math.Float32bits(n))
}

//WriteFloat64 write a little-endian int64 into a writer stream
func WriteFloat64(writer Writer, n float64) error {
	return WriteUInt64(writer, math.Float64bits(n))
}

//WriteString write string into stream
func WriteString(writer Writer, v string) error {
	err := WriteUInt16(writer, uint16(len(v)))
	if err != nil {
		return err
	}
	_, err = writer.Write([]byte(v))

	return err
}

//WriteBytes write string into stream
func WriteBytes(writer Writer, bytes []byte) error {
	_, err := writer.Write(bytes)
	return err
}

//Read read whole package from stream
func Read(reader io.Reader) ([]byte, error) {
	var header [2]byte

	if _, err := io.ReadFull(reader, header[:]); err != nil {
		return nil, gserrors.Newf(err, "read package length error")
	}

	length := binary.LittleEndian.Uint16(header[:])

	buff := make([]byte, length)

	if _, err := io.ReadFull(reader, buff); err != nil {
		return nil, gserrors.Newf(err, "read package body(%d) error", length)
	}

	return buff, nil
}

// Write write whole input data to stream
func Write(writer io.Writer, buff []byte) (int, error) {
	length := len(buff)

	if length > math.MaxUint16 {
		return 0, gserrors.Newf(ErrEncode, "send package out of range :%d", length)
	}

	var buf [2]byte

	binary.LittleEndian.PutUint16(buf[:], uint16(length))

	length, err := writer.Write(buf[:])

	if err != nil {
		return length, err
	}

	return writer.Write(buff)
}

//Stream the read/write stream
type Stream struct {
	reader io.Reader
	writer io.Writer
	rbuff  *bytes.Buffer
	wbuff  bytes.Buffer
}

// NewStream create new stream
func NewStream(reader io.Reader, writer io.Writer) *Stream {
	return &Stream{
		reader: reader,
		writer: writer,
	}
}

func (stream *Stream) Read(buf []byte) (int, error) {
	if stream.rbuff == nil || stream.rbuff.Len() == 0 {
		cached, err := Read(stream.reader)
		if err != nil {
			return 0, err
		}
		stream.rbuff = bytes.NewBuffer(cached)
	}

	return stream.rbuff.Read(buf)
}

//ReadByte implement Read interface
func (stream *Stream) ReadByte() (byte, error) {
	if stream.rbuff == nil || stream.rbuff.Len() == 0 {
		cached, err := Read(stream.reader)
		if err != nil {
			return 0, err
		}
		stream.rbuff = bytes.NewBuffer(cached)
	}

	return stream.rbuff.ReadByte()
}

func (stream *Stream) Write(buf []byte) (int, error) {
	return stream.wbuff.Write(buf)
}

//WriteByte implement Write interface
func (stream *Stream) WriteByte(val byte) error {
	return stream.wbuff.WriteByte(val)
}

// WBuf .
func (stream *Stream) WBuf() []byte {
	return stream.wbuff.Bytes()
}

//Flush .
func (stream *Stream) Flush() (int, error) {
	len, err := Write(stream.writer, stream.wbuff.Bytes())
	stream.wbuff.Reset()
	return len, err
}
<commit_msg>change ReadUint32/WriteUint32 to ReadUInt32/WriteUInt32<commit_after>package gorpc

import (
	"bytes"
	"encoding/binary"
	"errors"
	"io"
	"math"

	"github.com/gsdocker/gserrors"
)

//gsbuff public errors
var (
	ErrDecode    = errors.New("decode error")
	ErrWriteNone = errors.New("write nothing")
	ErrEncode    = errors.New("encoding error")
)

//Reader the gsbuff Reader interface, Mixin io.ByteReader and io.Reader interfaces
type Reader interface {
	io.ByteReader
	io.Reader
}

//Writer the gsbuff Writer interface, Mixin io.ByteWriterand io.Writer interfaces
type Writer interface {
	io.ByteWriter
	io.Writer
}

//ReadByte read byte from Reader interface
func ReadByte(reader Reader) (byte, error) {
	val, err := reader.ReadByte()

	if err != nil {
		return 0, gserrors.Newf(err, "read byte error")
	}

	return val, err
}

//ReadSByte read sbyte from Reader interface
func ReadSByte(reader Reader) (int8, error) {
	v, err := reader.ReadByte()
	return int8(v), err
}

//ReadUInt16 read uint16 from Reader interface
func ReadUInt16(reader Reader) (uint16, error) {
	buf := make([]byte, 2)

	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint16 error")
	}

	return uint16(buf[0]) | uint16(buf[1])<<8, nil
}

//ReadInt16 read uint16 from Reader interface
func ReadInt16(reader Reader) (int16, error) {
	v, err := ReadUInt16(reader)

	return int16(v), err
}

//ReadUInt32 read uint32 from Reader interface
func ReadUInt32(reader Reader) (uint32, error) {
	buf := make([]byte, 4)

	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint32 error")
	}

	return uint32(buf[3])<<24 | uint32(buf[2])<<16 | uint32(buf[1])<<8 | uint32(buf[0]), nil
}

//ReadInt32 read int32 from Reader interface
func ReadInt32(reader Reader) (int32, error) {
	v, err := ReadUInt32(reader)

	return int32(v), err
}

//ReadUInt64 read uint16 from Reader interface
func ReadUInt64(reader Reader) (uint64, error) {
	buf := make([]byte, 8)
	_, err := reader.Read(buf)

	if err != nil {
		return 0, gserrors.Newf(err, "read uint64 error")
	}

	var ret uint64

	for i, v := range buf {
		ret |= uint64(v) << uint(i*8)
	}

	return ret, nil
}

//ReadInt64 read int16 from Reader interface
func ReadInt64(reader Reader) (int64, error) {
	v, err := ReadUInt64(reader)

	return int64(v), err
}

//ReadFloat32 read float32 from Reader interface
func ReadFloat32(reader Reader) (float32, error) {
	x, err := ReadUInt32(reader)

	if err != nil {
		return 0, err
	}

	return math.Float32frombits(x), nil
}

//ReadFloat64 read float64 from Reader interface
func ReadFloat64(reader Reader) (float64, error) {
	x, err := ReadUInt64(reader)
	if err != nil {
		return 0, err
	}
	return math.Float64frombits(x), nil
}

//ReadString read string
func ReadString(reader Reader) (string, error) {
	len, err := ReadUInt16(reader)

	if err != nil {
		return "", gserrors.Newf(err, "read string len error")
	}

	buff := make([]byte, len)

	_, err = reader.Read(buff)

	if err != nil {
		return "", gserrors.Newf(err, "read string[%d] body error", len)
	}

	return string(buff), nil
}

//ReadBytes read bytes from reader
func ReadBytes(reader Reader, val []byte) error {
	_, err := reader.Read(val)

	if err != nil {
		return gserrors.Newf(err, "read bytes[%d] error", len(val))
	}

	return nil
}

//ReadBool read bool
func ReadBool(reader Reader) (bool, error) {
	b, err := ReadByte(reader)

	if b == 1 {
		return true, err
	}

	return false, err
}

//WriteByte write one byte into bytes buffer
func WriteByte(writer Writer, v byte) error {
	return writer.WriteByte(v)
}

//WriteBool write bool byte into bytes buffer
func WriteBool(writer Writer, v bool) error {
	if v {
		return writer.WriteByte(1)
	}
	return writer.WriteByte(0)
}

//WriteSByte write sbyte into stream
func WriteSByte(writer Writer, v int8) error {
	return writer.WriteByte(byte(v))
}

//WriteUInt16 write a little-endian uint16 into a writer stream
func WriteUInt16(writer Writer, v uint16) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	return nil
}

//WriteInt16 write a little-endian int16 into a writer stream
func WriteInt16(writer Writer, v int16) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	return nil
}

//WriteUInt32 write a little-endian uint32 into a writer stream
func WriteUInt32(writer Writer, v uint32) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	if err := WriteByte(writer, byte(v>>16)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>24)); err != nil {
		return err
	}

	return nil
}

//WriteInt32 write a little-endian int32 into a writer stream
func WriteInt32(writer Writer, v int32) error {
	if err := WriteByte(writer, byte(v)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>8)); err != nil {
		return err
	}

	if err := WriteByte(writer, byte(v>>16)); err != nil {
		return err
	}
	if err := WriteByte(writer, byte(v>>24)); err != nil {
		return err
	}

	return nil
}

//WriteUInt64 write a little-endian uint64 into a writer stream
func WriteUInt64(writer Writer, v uint64) error {
	for i := uint(0); i < 8; i++ {
		if err := WriteByte(writer, byte(v>>(i*8))); err != nil {
			return err
		}
	}

	return nil
}

//WriteInt64 write a little-endian int64 into a writer stream
func WriteInt64(writer Writer, v int64) error {
	for i := uint(0); i < 8; i++ {
		if err := WriteByte(writer, byte(v>>(i*8))); err != nil {
			return err
		}
	}

	return nil
}

//WriteFloat32 write a little-endian int64 into a writer stream
func WriteFloat32(writer Writer, n float32) error {
	return WriteUInt32(writer, math.Float32bits(n))
}

//WriteFloat64 write a little-endian int64 into a writer stream
func WriteFloat64(writer Writer, n float64) error {
	return WriteUInt64(writer, math.Float64bits(n))
}

//WriteString write string into stream
func WriteString(writer Writer, v string) error {
	err := WriteUInt16(writer, uint16(len(v)))
	if err != nil {
		return err
	}
	_, err = writer.Write([]byte(v))

	return err
}

//WriteBytes write string into stream
func WriteBytes(writer Writer, bytes []byte) error {
	_, err := writer.Write(bytes)
	return err
}

//Read read whole package from stream
func Read(reader io.Reader) ([]byte, error) {
	var header [2]byte

	if _, err := io.ReadFull(reader, header[:]); err != nil {
		return nil, gserrors.Newf(err, "read package length error")
	}

	length := binary.LittleEndian.Uint16(header[:])

	buff := make([]byte, length)

	if _, err := io.ReadFull(reader, buff); err != nil {
		return nil, gserrors.Newf(err, "read package body(%d) error", length)
	}

	return buff, nil
}

// Write write whole input data to stream
func Write(writer io.Writer, buff []byte) (int, error) {
	length := len(buff)

	if length > math.MaxUint16 {
		return 0, gserrors.Newf(ErrEncode, "send package out of range :%d", length)
	}

	var buf [2]byte

	binary.LittleEndian.PutUint16(buf[:], uint16(length))

	length, err := writer.Write(buf[:])

	if err != nil {
		return length, err
	}

	return writer.Write(buff)
}

//Stream the read/write stream
type Stream struct {
	reader io.Reader
	writer io.Writer
	rbuff  *bytes.Buffer
	wbuff  bytes.Buffer
}

// NewStream create new stream
func NewStream(reader io.Reader, writer io.Writer) *Stream {
	return &Stream{
		reader: reader,
		writer: writer,
	}
}

func (stream *Stream) Read(buf []byte) (int, error) {
	if stream.rbuff == nil || stream.rbuff.Len() == 0 {
		cached, err := Read(stream.reader)
		if err != nil {
			return 0, err
		}
		stream.rbuff = bytes.NewBuffer(cached)
	}

	return stream.rbuff.Read(buf)
}

//ReadByte implement Read interface
func (stream *Stream) ReadByte() (byte, error) {
	if stream.rbuff == nil || stream.rbuff.Len() == 0 {
		cached, err := Read(stream.reader)
		if err != nil {
			return 0, err
		}
		stream.rbuff = bytes.NewBuffer(cached)
	}

	return stream.rbuff.ReadByte()
}

func (stream *Stream) Write(buf []byte) (int, error) {
	return stream.wbuff.Write(buf)
}

//WriteByte implement Write interface
func (stream *Stream) WriteByte(val byte) error {
	return stream.wbuff.WriteByte(val)
}

// WBuf .
func (stream *Stream) WBuf() []byte {
	return stream.wbuff.Bytes()
}

//Flush .
func (stream *Stream) Flush() (int, error) {
	len, err := Write(stream.writer, stream.wbuff.Bytes())
	stream.wbuff.Reset()
	return len, err
}
<|endoftext|>


<commit_before>package cli

import (
	"fmt"
	"regexp"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	"k8s.io/apimachinery/pkg/runtime/schema"
	e2e "k8s.io/kubernetes/test/e2e/framework"

	exutil "github.com/openshift/origin/test/extended/util"
)

type explainExceptions struct {
	gv      schema.GroupVersion
	field   string
	pattern string
}

var (
	builtinTypes = []schema.GroupVersionResource{
		{Group: "apps.openshift.io", Version: "v1", Resource: "deploymentconfigs"},

		{Group: "build.openshift.io", Version: "v1", Resource: "buildconfigs"},
		{Group: "build.openshift.io", Version: "v1", Resource: "builds"},

		{Group: "image.openshift.io", Version: "v1", Resource: "imagestreamimports"},
		{Group: "image.openshift.io", Version: "v1", Resource: "imagestreams"},

		{Group: "project.openshift.io", Version: "v1", Resource: "projects"},

		{Group: "route.openshift.io", Version: "v1", Resource: "routes"},

		{Group: "security.openshift.io", Version: "v1", Resource: "podsecuritypolicyreviews"},
		{Group: "security.openshift.io", Version: "v1", Resource: "podsecuritypolicyselfsubjectreviews"},
		{Group: "security.openshift.io", Version: "v1", Resource: "podsecuritypolicysubjectreviews"},

		{Group: "template.openshift.io", Version: "v1", Resource: "templateinstances"},
	}

	crdTypes = []schema.GroupVersionResource{
		{Group: "operators.coreos.com", Version: "v1", Resource: "catalogsourceconfigs"},
		// FIXME:
		// {Group: "operators.coreos.com", Version: "v1", Resource: "catalogsources"},
		// {Group: "operators.coreos.com", Version: "v1", Resource: "clusterserviceversions"},
		// {Group: "operators.coreos.com", Version: "v1", Resource: "installplans"},
		{Group: "operators.coreos.com", Version: "v1", Resource: "operatorgroups"},
		{Group: "operators.coreos.com", Version: "v1", Resource: "operatorsources"},
		// FIXME:
		// {Group: "operators.coreos.com", Version: "v1", Resource: "subscriptions"},

		{Group: "autoscaling.openshift.io", Version: "v1", Resource: "clusterautoscalers"},
		// FIXME:
		// {Group: "autoscaling.openshift.io", Version: "v1", Resource: "machineautoscalers"},

		{Group: "config.openshift.io", Version: "v1", Resource: "apiservers"},
		{Group: "config.openshift.io", Version: "v1", Resource: "authentications"},
		{Group: "config.openshift.io", Version: "v1", Resource: "builds"},
		{Group: "config.openshift.io", Version: "v1", Resource: "clusteroperators"},
		{Group: "config.openshift.io", Version: "v1", Resource: "clusterversions"},
		{Group: "config.openshift.io", Version: "v1", Resource: "consoles"},
		{Group: "config.openshift.io", Version: "v1", Resource: "dnses"},
		{Group: "config.openshift.io", Version: "v1", Resource: "featuregates"},
		{Group: "config.openshift.io", Version: "v1", Resource: "images"},
		{Group: "config.openshift.io", Version: "v1", Resource: "infrastructures"},
		{Group: "config.openshift.io", Version: "v1", Resource: "ingresses"},
		{Group: "config.openshift.io", Version: "v1", Resource: "networks"},
		{Group: "config.openshift.io", Version: "v1", Resource: "oauths"},
		{Group: "config.openshift.io", Version: "v1", Resource: "projects"},
		{Group: "config.openshift.io", Version: "v1", Resource: "schedulers"},

		{Group: "cloudcredential.openshift.io", Version: "v1", Resource: "credentialsrequests"},

		// FIXME
		// {Group: "healthchecking.openshift.io", Version: "v1", Resource: "machinehealthchecks"},
		// {Group: "imageregistry.operator.openshift", Version: "v1", Resource: "configs"},

		{Group: "machine.openshift.io", Version: "v1beta1", Resource: "machines"},
		{Group: "machine.openshift.io", Version: "v1beta1", Resource: "machinesets"},

		{Group: "monitoring.coreos.com", Version: "v1", Resource: "alertmanagers"},
		{Group: "monitoring.coreos.com", Version: "v1", Resource: "prometheuses"},
		{Group: "monitoring.coreos.com", Version: "v1", Resource: "prometheusrules"},
		{Group: "monitoring.coreos.com", Version: "v1", Resource: "servicemonitors"},

		// FIXME
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "containerruntimeconfigs"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "controllerconfigs"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "kubeletconfigs"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "machineconfigpools"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "machineconfigs"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "mcoconfigs"},

		{Group: "network.openshift.io", Version: "v1", Resource: "egressnetworkpolicies"},

		{Group: "operator.openshift.io", Version: "v1", Resource: "authentications"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "consoles"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "dnses"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "ingresscontrollers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "kubeapiservers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "kubecontrollermanagers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "kubeschedulers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "networks"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftapiservers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftcontrollermanagers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "servicecas"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "servicecatalogapiservers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "servicecatalogcontrollermanagers"},

		{Group: "quota.openshift.io", Version: "v1", Resource: "clusterresourcequotas"},

		// FIXME
		// {Group: "samples.operator.openshift.io", Version: "v1", Resource: "configs"},

		{Group: "tuned.openshift.io", Version: "v1", Resource: "tuneds"},
	}

	specialTypes = []explainExceptions{
		{
			gv:      schema.GroupVersion{Group: "apps.openshift.io", Version: "v1"},
			field:   "dc.status.replicas",
			pattern: `FIELD\: +replicas`,
		},
		{
			gv:      schema.GroupVersion{Group: "route.openshift.io", Version: "v1"},
			field:   "route.metadata.name",
			pattern: `string`,
		},
		{
			gv:      schema.GroupVersion{Group: "authorization.openshift.io", Version: "v1"},
			field:   "resourceaccessreviews.resourceAPIGroup",
			pattern: `FIELD\: +resourceAPIGroup`,
		},
		{
			gv:      schema.GroupVersion{Group: "authorization.openshift.io", Version: "v1"},
			field:   "resourceaccessreviews.resourceAPIVersion",
			pattern: `FIELD\: +resourceAPIVersion`,
		},
		{
			gv:      schema.GroupVersion{Group: "authorization.openshift.io", Version: "v1"},
			field:   "rolebindingrestrictions.spec",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "images.dockerImageReference",
			pattern: `FIELD\: +dockerImageReference.*<string>`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "imagesignatures.imageIdentity",
			pattern: `FIELD\: +imageIdentity`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "imagestreamimages.image",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "imagestreammappings.image",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "imagestreamtags.tag",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "oauth.openshift.io", Version: "v1"},
			field:   "oauthaccesstokens.refreshToken",
			pattern: `FIELD\: +refreshToken`,
		},
		{
			gv:      schema.GroupVersion{Group: "oauth.openshift.io", Version: "v1"},
			field:   "oauthauthorizetokens.redirectURI",
			pattern: `FIELD\: +redirectURI`,
		},
		{
			gv:      schema.GroupVersion{Group: "oauth.openshift.io", Version: "v1"},
			field:   "oauthclientauthorizations.scopes",
			pattern: `FIELD\: +scopes`,
		},
		{
			gv:      schema.GroupVersion{Group: "oauth.openshift.io", Version: "v1"},
			field:   "oauthclients.redirectURIs",
			pattern: `FIELD\: +redirectURIs`,
		},
		{
			gv:      schema.GroupVersion{Group: "project.openshift.io", Version: "v1"},
			field:   "projectrequests.displayName",
			pattern: `FIELD\: +displayName`,
		},
		{
			gv:      schema.GroupVersion{Group: "security.openshift.io", Version: "v1"},
			field:   "rangeallocations.range",
			pattern: `FIELD\: +range`,
		},
		{
			gv:      schema.GroupVersion{Group: "template.openshift.io", Version: "v1"},
			field:   "processedtemplates.objects",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "template.openshift.io", Version: "v1"},
			field:   "templates.objects",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "user.openshift.io", Version: "v1"},
			field:   "identities.user",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "user.openshift.io", Version: "v1"},
			field:   "groups.users",
			pattern: `FIELD\: +users`,
		},
		{
			gv:      schema.GroupVersion{Group: "user.openshift.io", Version: "v1"},
			field:   "useridentitymappings.user",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "user.openshift.io", Version: "v1"},
			field:   "users.fullName",
			pattern: `FIELD\: +fullName`,
		},
		{
			gv:      schema.GroupVersion{Group: "network.openshift.io", Version: "v1"},
			field:   "clusternetworks.network",
			pattern: `FIELD\: +network`,
		},
		{
			gv:      schema.GroupVersion{Group: "network.openshift.io", Version: "v1"},
			field:   "hostsubnets.subnet",
			pattern: `FIELD\: +subnet`,
		},
		{
			gv:      schema.GroupVersion{Group: "network.openshift.io", Version: "v1"},
			field:   "netnamespaces.netname",
			pattern: `FIELD\: +netname`,
		},
	}
)

var _ = g.Describe("[cli] oc explain", func() {
	defer g.GinkgoRecover()

	oc := exutil.NewCLI("oc-explain", exutil.KubeConfigPath())

	g.It("should contain spec+status for builtinTypes", func() {
		for _, bt := range builtinTypes {
			e2e.Logf("Checking %s...", bt)
			o.Expect(verifySpecStatusExplain(oc, bt)).NotTo(o.HaveOccurred())
		}
	})

	g.It("should contain proper spec+status for CRDs", func() {
		for _, ct := range crdTypes {
			e2e.Logf("Checking %s...", ct)
			o.Expect(verifyCRDSpecStatusExplain(oc, ct)).NotTo(o.HaveOccurred())
		}
	})

	g.It("should contain proper fields description for special types", func() {
		for _, st := range specialTypes {
			e2e.Logf("Checking %s, Field=%s...", st.gv, st.field)
			o.Expect(verifyExplain(oc, st.pattern, st.field, fmt.Sprintf("--api-version=%s", st.gv))).NotTo(o.HaveOccurred())
		}
	})
})

func verifySpecStatusExplain(oc *exutil.CLI, gvr schema.GroupVersionResource) error {
	return verifyExplain(oc, `(?s)DESCRIPTION:.*FIELDS:.*spec.*<Object>.*[Ss]pec(ification)?.*status.*<Object>.*[Ss]tatus.*`, gvr.Resource, fmt.Sprintf("--api-version=%s", gvr.GroupVersion()))
}

func verifyCRDSpecStatusExplain(oc *exutil.CLI, gvr schema.GroupVersionResource) error {
	// TODO ideally we'd want to check for reasonable description in both spec and status
	return verifyExplain(oc, `(?s)DESCRIPTION:.*FIELDS:.*spec.*<.*>.*(status.*<.*>.*)?`, gvr.Resource, fmt.Sprintf("--api-version=%s", gvr.GroupVersion()))
}

func verifyExplain(oc *exutil.CLI, pattern string, args ...string) error {
	result, err := oc.Run("explain").Args(args...).Output()
	if err != nil {
		return fmt.Errorf("failed to explain %q: %v", args, err)
	}
	r := regexp.MustCompile(pattern)
	if !r.Match([]byte(result)) {
		return fmt.Errorf("oc explain %q result {%s} doesn't match pattern {%s}", args, result, pattern)
	}
	return nil
}
<commit_msg>add samples operator back into cli/explain extended test<commit_after>package cli

import (
	"fmt"
	"regexp"

	g "github.com/onsi/ginkgo"
	o "github.com/onsi/gomega"

	"k8s.io/apimachinery/pkg/runtime/schema"
	e2e "k8s.io/kubernetes/test/e2e/framework"

	exutil "github.com/openshift/origin/test/extended/util"
)

type explainExceptions struct {
	gv      schema.GroupVersion
	field   string
	pattern string
}

var (
	builtinTypes = []schema.GroupVersionResource{
		{Group: "apps.openshift.io", Version: "v1", Resource: "deploymentconfigs"},

		{Group: "build.openshift.io", Version: "v1", Resource: "buildconfigs"},
		{Group: "build.openshift.io", Version: "v1", Resource: "builds"},

		{Group: "image.openshift.io", Version: "v1", Resource: "imagestreamimports"},
		{Group: "image.openshift.io", Version: "v1", Resource: "imagestreams"},

		{Group: "project.openshift.io", Version: "v1", Resource: "projects"},

		{Group: "route.openshift.io", Version: "v1", Resource: "routes"},

		{Group: "security.openshift.io", Version: "v1", Resource: "podsecuritypolicyreviews"},
		{Group: "security.openshift.io", Version: "v1", Resource: "podsecuritypolicyselfsubjectreviews"},
		{Group: "security.openshift.io", Version: "v1", Resource: "podsecuritypolicysubjectreviews"},

		{Group: "template.openshift.io", Version: "v1", Resource: "templateinstances"},
	}

	crdTypes = []schema.GroupVersionResource{
		{Group: "operators.coreos.com", Version: "v1", Resource: "catalogsourceconfigs"},
		// FIXME:
		// {Group: "operators.coreos.com", Version: "v1", Resource: "catalogsources"},
		// {Group: "operators.coreos.com", Version: "v1", Resource: "clusterserviceversions"},
		// {Group: "operators.coreos.com", Version: "v1", Resource: "installplans"},
		{Group: "operators.coreos.com", Version: "v1", Resource: "operatorgroups"},
		{Group: "operators.coreos.com", Version: "v1", Resource: "operatorsources"},
		// FIXME:
		// {Group: "operators.coreos.com", Version: "v1", Resource: "subscriptions"},

		{Group: "autoscaling.openshift.io", Version: "v1", Resource: "clusterautoscalers"},
		// FIXME:
		// {Group: "autoscaling.openshift.io", Version: "v1", Resource: "machineautoscalers"},

		{Group: "config.openshift.io", Version: "v1", Resource: "apiservers"},
		{Group: "config.openshift.io", Version: "v1", Resource: "authentications"},
		{Group: "config.openshift.io", Version: "v1", Resource: "builds"},
		{Group: "config.openshift.io", Version: "v1", Resource: "clusteroperators"},
		{Group: "config.openshift.io", Version: "v1", Resource: "clusterversions"},
		{Group: "config.openshift.io", Version: "v1", Resource: "consoles"},
		{Group: "config.openshift.io", Version: "v1", Resource: "dnses"},
		{Group: "config.openshift.io", Version: "v1", Resource: "featuregates"},
		{Group: "config.openshift.io", Version: "v1", Resource: "images"},
		{Group: "config.openshift.io", Version: "v1", Resource: "infrastructures"},
		{Group: "config.openshift.io", Version: "v1", Resource: "ingresses"},
		{Group: "config.openshift.io", Version: "v1", Resource: "networks"},
		{Group: "config.openshift.io", Version: "v1", Resource: "oauths"},
		{Group: "config.openshift.io", Version: "v1", Resource: "projects"},
		{Group: "config.openshift.io", Version: "v1", Resource: "schedulers"},

		{Group: "cloudcredential.openshift.io", Version: "v1", Resource: "credentialsrequests"},

		// FIXME
		// {Group: "healthchecking.openshift.io", Version: "v1", Resource: "machinehealthchecks"},
		// {Group: "imageregistry.operator.openshift", Version: "v1", Resource: "configs"},

		{Group: "machine.openshift.io", Version: "v1beta1", Resource: "machines"},
		{Group: "machine.openshift.io", Version: "v1beta1", Resource: "machinesets"},

		{Group: "monitoring.coreos.com", Version: "v1", Resource: "alertmanagers"},
		{Group: "monitoring.coreos.com", Version: "v1", Resource: "prometheuses"},
		{Group: "monitoring.coreos.com", Version: "v1", Resource: "prometheusrules"},
		{Group: "monitoring.coreos.com", Version: "v1", Resource: "servicemonitors"},

		// FIXME
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "containerruntimeconfigs"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "controllerconfigs"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "kubeletconfigs"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "machineconfigpools"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "machineconfigs"},
		// {Group: "machineconfiguration.openshift.io", Version: "v1", Resource: "mcoconfigs"},

		{Group: "network.openshift.io", Version: "v1", Resource: "egressnetworkpolicies"},

		{Group: "operator.openshift.io", Version: "v1", Resource: "authentications"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "consoles"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "dnses"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "ingresscontrollers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "kubeapiservers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "kubecontrollermanagers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "kubeschedulers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "networks"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftapiservers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "openshiftcontrollermanagers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "servicecas"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "servicecatalogapiservers"},
		{Group: "operator.openshift.io", Version: "v1", Resource: "servicecatalogcontrollermanagers"},

		{Group: "quota.openshift.io", Version: "v1", Resource: "clusterresourcequotas"},

		{Group: "samples.operator.openshift.io", Version: "v1", Resource: "configs"},

		{Group: "tuned.openshift.io", Version: "v1", Resource: "tuneds"},
	}

	specialTypes = []explainExceptions{
		{
			gv:      schema.GroupVersion{Group: "apps.openshift.io", Version: "v1"},
			field:   "dc.status.replicas",
			pattern: `FIELD\: +replicas`,
		},
		{
			gv:      schema.GroupVersion{Group: "route.openshift.io", Version: "v1"},
			field:   "route.metadata.name",
			pattern: `string`,
		},
		{
			gv:      schema.GroupVersion{Group: "authorization.openshift.io", Version: "v1"},
			field:   "resourceaccessreviews.resourceAPIGroup",
			pattern: `FIELD\: +resourceAPIGroup`,
		},
		{
			gv:      schema.GroupVersion{Group: "authorization.openshift.io", Version: "v1"},
			field:   "resourceaccessreviews.resourceAPIVersion",
			pattern: `FIELD\: +resourceAPIVersion`,
		},
		{
			gv:      schema.GroupVersion{Group: "authorization.openshift.io", Version: "v1"},
			field:   "rolebindingrestrictions.spec",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "images.dockerImageReference",
			pattern: `FIELD\: +dockerImageReference.*<string>`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "imagesignatures.imageIdentity",
			pattern: `FIELD\: +imageIdentity`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "imagestreamimages.image",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "imagestreammappings.image",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "image.openshift.io", Version: "v1"},
			field:   "imagestreamtags.tag",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "oauth.openshift.io", Version: "v1"},
			field:   "oauthaccesstokens.refreshToken",
			pattern: `FIELD\: +refreshToken`,
		},
		{
			gv:      schema.GroupVersion{Group: "oauth.openshift.io", Version: "v1"},
			field:   "oauthauthorizetokens.redirectURI",
			pattern: `FIELD\: +redirectURI`,
		},
		{
			gv:      schema.GroupVersion{Group: "oauth.openshift.io", Version: "v1"},
			field:   "oauthclientauthorizations.scopes",
			pattern: `FIELD\: +scopes`,
		},
		{
			gv:      schema.GroupVersion{Group: "oauth.openshift.io", Version: "v1"},
			field:   "oauthclients.redirectURIs",
			pattern: `FIELD\: +redirectURIs`,
		},
		{
			gv:      schema.GroupVersion{Group: "project.openshift.io", Version: "v1"},
			field:   "projectrequests.displayName",
			pattern: `FIELD\: +displayName`,
		},
		{
			gv:      schema.GroupVersion{Group: "security.openshift.io", Version: "v1"},
			field:   "rangeallocations.range",
			pattern: `FIELD\: +range`,
		},
		{
			gv:      schema.GroupVersion{Group: "template.openshift.io", Version: "v1"},
			field:   "processedtemplates.objects",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "template.openshift.io", Version: "v1"},
			field:   "templates.objects",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "user.openshift.io", Version: "v1"},
			field:   "identities.user",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "user.openshift.io", Version: "v1"},
			field:   "groups.users",
			pattern: `FIELD\: +users`,
		},
		{
			gv:      schema.GroupVersion{Group: "user.openshift.io", Version: "v1"},
			field:   "useridentitymappings.user",
			pattern: `FIELDS\:.*`,
		},
		{
			gv:      schema.GroupVersion{Group: "user.openshift.io", Version: "v1"},
			field:   "users.fullName",
			pattern: `FIELD\: +fullName`,
		},
		{
			gv:      schema.GroupVersion{Group: "network.openshift.io", Version: "v1"},
			field:   "clusternetworks.network",
			pattern: `FIELD\: +network`,
		},
		{
			gv:      schema.GroupVersion{Group: "network.openshift.io", Version: "v1"},
			field:   "hostsubnets.subnet",
			pattern: `FIELD\: +subnet`,
		},
		{
			gv:      schema.GroupVersion{Group: "network.openshift.io", Version: "v1"},
			field:   "netnamespaces.netname",
			pattern: `FIELD\: +netname`,
		},
	}
)

var _ = g.Describe("[cli] oc explain", func() {
	defer g.GinkgoRecover()

	oc := exutil.NewCLI("oc-explain", exutil.KubeConfigPath())

	g.It("should contain spec+status for builtinTypes", func() {
		for _, bt := range builtinTypes {
			e2e.Logf("Checking %s...", bt)
			o.Expect(verifySpecStatusExplain(oc, bt)).NotTo(o.HaveOccurred())
		}
	})

	g.It("should contain proper spec+status for CRDs", func() {
		for _, ct := range crdTypes {
			e2e.Logf("Checking %s...", ct)
			o.Expect(verifyCRDSpecStatusExplain(oc, ct)).NotTo(o.HaveOccurred())
		}
	})

	g.It("should contain proper fields description for special types", func() {
		for _, st := range specialTypes {
			e2e.Logf("Checking %s, Field=%s...", st.gv, st.field)
			o.Expect(verifyExplain(oc, st.pattern, st.field, fmt.Sprintf("--api-version=%s", st.gv))).NotTo(o.HaveOccurred())
		}
	})
})

func verifySpecStatusExplain(oc *exutil.CLI, gvr schema.GroupVersionResource) error {
	return verifyExplain(oc, `(?s)DESCRIPTION:.*FIELDS:.*spec.*<Object>.*[Ss]pec(ification)?.*status.*<Object>.*[Ss]tatus.*`, gvr.Resource, fmt.Sprintf("--api-version=%s", gvr.GroupVersion()))
}

func verifyCRDSpecStatusExplain(oc *exutil.CLI, gvr schema.GroupVersionResource) error {
	// TODO ideally we'd want to check for reasonable description in both spec and status
	return verifyExplain(oc, `(?s)DESCRIPTION:.*FIELDS:.*spec.*<.*>.*(status.*<.*>.*)?`, gvr.Resource, fmt.Sprintf("--api-version=%s", gvr.GroupVersion()))
}

func verifyExplain(oc *exutil.CLI, pattern string, args ...string) error {
	result, err := oc.Run("explain").Args(args...).Output()
	if err != nil {
		return fmt.Errorf("failed to explain %q: %v", args, err)
	}
	r := regexp.MustCompile(pattern)
	if !r.Match([]byte(result)) {
		return fmt.Errorf("oc explain %q result {%s} doesn't match pattern {%s}", args, result, pattern)
	}
	return nil
}
<|endoftext|>


<commit_before>package mapping

import (
	"errors"
	"imposm3/element"
	"imposm3/logging"
	"regexp"
	"strconv"
	"strings"
)

var log = logging.NewLogger("mapping")

var AvailableFieldTypes map[string]FieldType

func init() {
	AvailableFieldTypes = map[string]FieldType{
		"bool":                 {"bool", "bool", Bool, nil},
		"boolint":              {"boolint", "int8", BoolInt, nil},
		"id":                   {"id", "int64", Id, nil},
		"string":               {"string", "string", String, nil},
		"direction":            {"direction", "int8", Direction, nil},
		"integer":              {"integer", "int32", Integer, nil},
		"mapping_key":          {"mapping_key", "string", Key, nil},
		"mapping_value":        {"mapping_value", "string", Value, nil},
		"geometry":             {"geometry", "geometry", Geometry, nil},
		"validated_geometry":   {"validated_geometry", "validated_geometry", Geometry, nil},
		"wayzorder":            {"wayzorder", "int32", WayZOrder, nil},
		"pseudoarea":           {"pseudoarea", "float32", PseudoArea, nil},
		"zorder":               {"zorder", "int32", nil, MakeZOrder},
		"string_suffixreplace": {"string_suffixreplace", "string", nil, MakeSuffixReplace},
	}
}

type MakeValue func(string, *element.OSMElem, Match) interface{}

type MakeMakeValue func(string, FieldType, Field) (MakeValue, error)

type FieldSpec struct {
	Key  string
	Type FieldType
}

func (f *FieldSpec) Value(elem *element.OSMElem, match Match) interface{} {
	if f.Type.Func != nil {
		return f.Type.Func(elem.Tags[f.Key], elem, match)
	}
	return nil
}

type TableFields struct {
	fields []FieldSpec
}

func (t *TableFields) MakeRow(elem *element.OSMElem, match Match) []interface{} {
	var row []interface{}
	for _, field := range t.fields {
		row = append(row, field.Value(elem, match))
	}
	return row
}

func (field *Field) FieldType() *FieldType {
	if fieldType, ok := AvailableFieldTypes[field.Type]; ok {
		if fieldType.MakeFunc != nil {
			makeValue, err := fieldType.MakeFunc(field.Name, fieldType, *field)
			if err != nil {
				log.Print(err)
			}
			fieldType = FieldType{fieldType.Name, fieldType.GoType, makeValue, nil}
		}
		return &fieldType
	}
	return nil
}

func (t *Table) TableFields() *TableFields {
	result := TableFields{}

	for _, mappingField := range t.Fields {
		field := FieldSpec{}
		field.Key = mappingField.Key

		fieldType := mappingField.FieldType()
		if fieldType != nil {
			field.Type = *fieldType
		} else {
			log.Warn("unhandled type:", mappingField.Type)
		}
		result.fields = append(result.fields, field)
	}
	return &result
}

type FieldType struct {
	Name     string
	GoType   string
	Func     MakeValue
	MakeFunc MakeMakeValue
}

func Bool(val string, elem *element.OSMElem, match Match) interface{} {
	if val == "" || val == "0" || val == "false" || val == "no" {
		return false
	}
	return true
}

func BoolInt(val string, elem *element.OSMElem, match Match) interface{} {
	if val == "" || val == "0" || val == "false" || val == "no" {
		return 0
	}
	return 1
}

func String(val string, elem *element.OSMElem, match Match) interface{} {
	return val
}

func Integer(val string, elem *element.OSMElem, match Match) interface{} {
	v, err := strconv.ParseInt(val, 10, 32)
	if err != nil {
		return nil
	}
	return v
}

func Id(val string, elem *element.OSMElem, match Match) interface{} {
	return elem.Id
}

func Key(val string, elem *element.OSMElem, match Match) interface{} {
	return match.Key
}

func Value(val string, elem *element.OSMElem, match Match) interface{} {
	return match.Value
}

func Direction(val string, elem *element.OSMElem, match Match) interface{} {
	if val == "1" || val == "yes" || val == "true" {
		return 1
	} else if val == "-1" {
		return -1
	} else {
		return 0
	}
}

func Geometry(val string, elem *element.OSMElem, match Match) interface{} {
	return string(elem.Geom.Wkb)
}

func PseudoArea(val string, elem *element.OSMElem, match Match) interface{} {
	area := elem.Geom.Geom.Area()
	if area == 0.0 {
		return nil
	}
	return float32(area)
}

var wayRanks map[string]int

func init() {
	wayRanks = map[string]int{
		"minor":          3,
		"road":           3,
		"unclassified":   3,
		"residential":    3,
		"tertiary_link":  3,
		"tertiary":       4,
		"secondary_link": 3,
		"secondary":      5,
		"primary_link":   3,
		"primary":        6,
		"trunk_link":     3,
		"trunk":          8,
		"motorway_link":  3,
		"motorway":       9,
	}
}

func WayZOrder(val string, elem *element.OSMElem, match Match) interface{} {
	var z int32
	layer, _ := strconv.ParseInt(elem.Tags["layer"], 10, 64)
	z += int32(layer) * 10

	rank := wayRanks[match.Value]

	if rank == 0 {
		if _, ok := elem.Tags["railway"]; ok {
			rank = 7
		}
	}
	z += int32(rank)

	tunnel := elem.Tags["tunnel"]
	if tunnel == "true" || tunnel == "yes" || tunnel == "1" {
		z -= 10
	}
	bridge := elem.Tags["bridge"]
	if bridge == "true" || bridge == "yes" || bridge == "1" {
		z += 10
	}

	return z
}

func MakeZOrder(fieldName string, fieldType FieldType, field Field) (MakeValue, error) {
	_rankList, ok := field.Args["ranks"]
	if !ok {
		return nil, errors.New("missing ranks in args for zorder")
	}

	rankList, ok := _rankList.([]interface{})
	if !ok {
		return nil, errors.New("ranks in args for zorder not a list")
	}

	ranks := make(map[string]int)
	for i, rank := range rankList {
		rankName, ok := rank.(string)
		if !ok {
			return nil, errors.New("rank in ranks not a string")
		}

		ranks[rankName] = len(rankList) - i
	}
	zOrder := func(val string, elem *element.OSMElem, match Match) interface{} {
		if r, ok := ranks[match.Value]; ok {
			return r
		}
		return nil
	}

	return zOrder, nil
}

func MakeSuffixReplace(fieldName string, fieldType FieldType, field Field) (MakeValue, error) {
	_changes, ok := field.Args["suffixes"]
	if !ok {
		return nil, errors.New("missing suffixes in args for string_suffixreplace")
	}

	changes, ok := _changes.(map[string]interface{})
	if !ok {
		return nil, errors.New("suffixes in args for string_suffixreplace not a dict")
	}

	var suffixes []string
	for k, _ := range changes {
		suffixes = append(suffixes, k)
	}
	reStr := `(` + strings.Join(suffixes, "|") + `)\b`
	re := regexp.MustCompile(reStr)

	replFunc := func(match string) string {
		return changes[match].(string)
	}

	suffixReplace := func(val string, elem *element.OSMElem, match Match) interface{} {
		if val != "" {
			return re.ReplaceAllStringFunc(val, replFunc)
		}
		return val
	}

	return suffixReplace, nil
}

func asHex(b []byte) string {
	digits := "0123456789ABCDEF"
	buf := make([]byte, 0, len(b)*2)
	n := len(b)

	for i := 0; i < n; i++ {
		c := b[i]
		buf = append(buf, digits[c>>4], digits[c&0xF])
	}
	return string(buf)
}
<commit_msg>abort if it fails to create fieldtype<commit_after>package mapping

import (
	"errors"
	"imposm3/element"
	"imposm3/logging"
	"regexp"
	"strconv"
	"strings"
)

var log = logging.NewLogger("mapping")

var AvailableFieldTypes map[string]FieldType

func init() {
	AvailableFieldTypes = map[string]FieldType{
		"bool":                 {"bool", "bool", Bool, nil},
		"boolint":              {"boolint", "int8", BoolInt, nil},
		"id":                   {"id", "int64", Id, nil},
		"string":               {"string", "string", String, nil},
		"direction":            {"direction", "int8", Direction, nil},
		"integer":              {"integer", "int32", Integer, nil},
		"mapping_key":          {"mapping_key", "string", Key, nil},
		"mapping_value":        {"mapping_value", "string", Value, nil},
		"geometry":             {"geometry", "geometry", Geometry, nil},
		"validated_geometry":   {"validated_geometry", "validated_geometry", Geometry, nil},
		"wayzorder":            {"wayzorder", "int32", WayZOrder, nil},
		"pseudoarea":           {"pseudoarea", "float32", PseudoArea, nil},
		"zorder":               {"zorder", "int32", nil, MakeZOrder},
		"string_suffixreplace": {"string_suffixreplace", "string", nil, MakeSuffixReplace},
	}
}

type MakeValue func(string, *element.OSMElem, Match) interface{}

type MakeMakeValue func(string, FieldType, Field) (MakeValue, error)

type FieldSpec struct {
	Key  string
	Type FieldType
}

func (f *FieldSpec) Value(elem *element.OSMElem, match Match) interface{} {
	if f.Type.Func != nil {
		return f.Type.Func(elem.Tags[f.Key], elem, match)
	}
	return nil
}

type TableFields struct {
	fields []FieldSpec
}

func (t *TableFields) MakeRow(elem *element.OSMElem, match Match) []interface{} {
	var row []interface{}
	for _, field := range t.fields {
		row = append(row, field.Value(elem, match))
	}
	return row
}

func (field *Field) FieldType() *FieldType {
	if fieldType, ok := AvailableFieldTypes[field.Type]; ok {
		if fieldType.MakeFunc != nil {
			makeValue, err := fieldType.MakeFunc(field.Name, fieldType, *field)
			if err != nil {
				log.Print(err)
				return nil
			}
			fieldType = FieldType{fieldType.Name, fieldType.GoType, makeValue, nil}
		}
		return &fieldType
	}
	return nil
}

func (t *Table) TableFields() *TableFields {
	result := TableFields{}

	for _, mappingField := range t.Fields {
		field := FieldSpec{}
		field.Key = mappingField.Key

		fieldType := mappingField.FieldType()
		if fieldType != nil {
			field.Type = *fieldType
		} else {
			log.Warn("unhandled type:", mappingField.Type)
		}
		result.fields = append(result.fields, field)
	}
	return &result
}

type FieldType struct {
	Name     string
	GoType   string
	Func     MakeValue
	MakeFunc MakeMakeValue
}

func Bool(val string, elem *element.OSMElem, match Match) interface{} {
	if val == "" || val == "0" || val == "false" || val == "no" {
		return false
	}
	return true
}

func BoolInt(val string, elem *element.OSMElem, match Match) interface{} {
	if val == "" || val == "0" || val == "false" || val == "no" {
		return 0
	}
	return 1
}

func String(val string, elem *element.OSMElem, match Match) interface{} {
	return val
}

func Integer(val string, elem *element.OSMElem, match Match) interface{} {
	v, err := strconv.ParseInt(val, 10, 32)
	if err != nil {
		return nil
	}
	return v
}

func Id(val string, elem *element.OSMElem, match Match) interface{} {
	return elem.Id
}

func Key(val string, elem *element.OSMElem, match Match) interface{} {
	return match.Key
}

func Value(val string, elem *element.OSMElem, match Match) interface{} {
	return match.Value
}

func Direction(val string, elem *element.OSMElem, match Match) interface{} {
	if val == "1" || val == "yes" || val == "true" {
		return 1
	} else if val == "-1" {
		return -1
	} else {
		return 0
	}
}

func Geometry(val string, elem *element.OSMElem, match Match) interface{} {
	return string(elem.Geom.Wkb)
}

func PseudoArea(val string, elem *element.OSMElem, match Match) interface{} {
	area := elem.Geom.Geom.Area()
	if area == 0.0 {
		return nil
	}
	return float32(area)
}

var wayRanks map[string]int

func init() {
	wayRanks = map[string]int{
		"minor":          3,
		"road":           3,
		"unclassified":   3,
		"residential":    3,
		"tertiary_link":  3,
		"tertiary":       4,
		"secondary_link": 3,
		"secondary":      5,
		"primary_link":   3,
		"primary":        6,
		"trunk_link":     3,
		"trunk":          8,
		"motorway_link":  3,
		"motorway":       9,
	}
}

func WayZOrder(val string, elem *element.OSMElem, match Match) interface{} {
	var z int32
	layer, _ := strconv.ParseInt(elem.Tags["layer"], 10, 64)
	z += int32(layer) * 10

	rank := wayRanks[match.Value]

	if rank == 0 {
		if _, ok := elem.Tags["railway"]; ok {
			rank = 7
		}
	}
	z += int32(rank)

	tunnel := elem.Tags["tunnel"]
	if tunnel == "true" || tunnel == "yes" || tunnel == "1" {
		z -= 10
	}
	bridge := elem.Tags["bridge"]
	if bridge == "true" || bridge == "yes" || bridge == "1" {
		z += 10
	}

	return z
}

func MakeZOrder(fieldName string, fieldType FieldType, field Field) (MakeValue, error) {
	_rankList, ok := field.Args["ranks"]
	if !ok {
		return nil, errors.New("missing ranks in args for zorder")
	}

	rankList, ok := _rankList.([]interface{})
	if !ok {
		return nil, errors.New("ranks in args for zorder not a list")
	}

	ranks := make(map[string]int)
	for i, rank := range rankList {
		rankName, ok := rank.(string)
		if !ok {
			return nil, errors.New("rank in ranks not a string")
		}

		ranks[rankName] = len(rankList) - i
	}
	zOrder := func(val string, elem *element.OSMElem, match Match) interface{} {
		if r, ok := ranks[match.Value]; ok {
			return r
		}
		return nil
	}

	return zOrder, nil
}

func MakeSuffixReplace(fieldName string, fieldType FieldType, field Field) (MakeValue, error) {
	_changes, ok := field.Args["suffixes"]
	if !ok {
		return nil, errors.New("missing suffixes in args for string_suffixreplace")
	}

	changes, ok := _changes.(map[string]interface{})
	if !ok {
		return nil, errors.New("suffixes in args for string_suffixreplace not a dict")
	}

	var suffixes []string
	for k, _ := range changes {
		suffixes = append(suffixes, k)
	}
	reStr := `(` + strings.Join(suffixes, "|") + `)\b`
	re := regexp.MustCompile(reStr)

	replFunc := func(match string) string {
		return changes[match].(string)
	}

	suffixReplace := func(val string, elem *element.OSMElem, match Match) interface{} {
		if val != "" {
			return re.ReplaceAllStringFunc(val, replFunc)
		}
		return val
	}

	return suffixReplace, nil
}

func asHex(b []byte) string {
	digits := "0123456789ABCDEF"
	buf := make([]byte, 0, len(b)*2)
	n := len(b)

	for i := 0; i < n; i++ {
		c := b[i]
		buf = append(buf, digits[c>>4], digits[c&0xF])
	}
	return string(buf)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"path"
	"path/filepath"
	"reflect"
	"regexp"

	"github.com/osteele/gojekyll/helpers"
)

var (
	frontMatterMatcher     = regexp.MustCompile(`(?s)^---\n(.+?\n)---\n`)
	emptyFontMatterMatcher = regexp.MustCompile(`(?s)^---\n+---\n`)
)

// Page is a Jekyll page.
type Page interface {
	Path() string
	Site() *Site
	Source() string
	Static() bool
	Published() bool
	Permalink() string
	TemplateObject() VariableMap
	Write(io.Writer) error
	DebugVariables() VariableMap

	initPermalink() error
}

type pageFields struct {
	relpath     string      // relative to site source, e.g. "_post/base.ext"
	permalink   string      // cached permalink
	frontMatter VariableMap // page front matter, merged with defaults
	collection  *Collection
	site        *Site
}

func (p *pageFields) String() string {
	return fmt.Sprintf("%s{Path=%v, Permalink=%v}", reflect.TypeOf(p).Name(), p.relpath, p.permalink)
}

func (p *pageFields) Path() string      { return p.relpath }
func (p *pageFields) Permalink() string { return p.permalink }
func (p *pageFields) Published() bool   { return p.frontMatter.Bool("published", true) }
func (p *pageFields) Site() *Site       { return p.site }

// ReadPage reads a Page from a file, using defaults as the default front matter.
func ReadPage(site *Site, collection *Collection, relpath string, defaults VariableMap) (p Page, err error) {
	magic, err := helpers.ReadFileMagic(filepath.Join(site.Source, relpath))
	if err != nil {
		return
	}

	fields := pageFields{site: site, collection: collection, relpath: relpath, frontMatter: defaults}
	if string(magic) == "---\n" {
		p, err = NewDynamicPage(fields)
		if err != nil {
			return
		}
	} else {
		p = &StaticPage{fields}
	}
	// Compute this after creating the page, in order to pick up the front matter.
	err = p.initPermalink()
	if err != nil {
		return
	}
	return
}

func (p *StaticPage) Write(w io.Writer) error {
	source, err := ioutil.ReadFile(p.Source())
	if err != nil {
		return err
	}
	_, err = w.Write(source)
	return err
}

// TemplateObject returns the attributes of the template page object.
// See https://jekyllrb.com/docs/variables/#page-variables
func (p *pageFields) TemplateObject() VariableMap {
	var (
		relpath = "/" + filepath.ToSlash(p.relpath)
		base    = path.Base(relpath)
		ext     = path.Ext(relpath)
	)

	return VariableMap{
		"path":          relpath,
		"modified_time": 0, // TODO
		"name":          base,
		"basename":      helpers.PathWithoutExtension(base),
		"extname":       ext,
	}
}

// DebugVariables returns a map that's useful to present during diagnostics.
// For a static page, this is just the page's template object attributes.
func (p *pageFields) DebugVariables() VariableMap {
	return p.TemplateObject()
}

// Source returns the file path of the page source.
func (p *pageFields) Source() string {
	return filepath.Join(p.site.Source, p.relpath)
}

// StaticPage is a static page.
type StaticPage struct {
	pageFields
}

// Static returns a bool indicating that the page is a static page.
func (p *StaticPage) Static() bool { return true }

// TemplateObject returns metadata for use in the representation of the page as a collection item
func (p *StaticPage) TemplateObject() VariableMap {
	return MergeVariableMaps(p.frontMatter, p.pageFields.TemplateObject())
}
<commit_msg>Implement modified_time page variable<commit_after>package main

import (
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"reflect"
	"regexp"
	"time"

	"github.com/osteele/gojekyll/helpers"
)

var (
	frontMatterMatcher     = regexp.MustCompile(`(?s)^---\n(.+?\n)---\n`)
	emptyFontMatterMatcher = regexp.MustCompile(`(?s)^---\n+---\n`)
)

// Page is a Jekyll page.
type Page interface {
	Path() string
	Site() *Site
	Source() string
	Static() bool
	Published() bool
	Permalink() string
	TemplateObject() VariableMap
	Write(io.Writer) error
	DebugVariables() VariableMap

	initPermalink() error
}

type pageFields struct {
	relpath     string // relative to site source, e.g. "_post/base.ext"
	permalink   string // cached permalink
	modTime     time.Time
	frontMatter VariableMap // page front matter, merged with defaults
	collection  *Collection
	site        *Site
}

func (p *pageFields) String() string {
	return fmt.Sprintf("%s{Path=%v, Permalink=%v}", reflect.TypeOf(p).Name(), p.relpath, p.permalink)
}

func (p *pageFields) Path() string      { return p.relpath }
func (p *pageFields) Permalink() string { return p.permalink }
func (p *pageFields) Published() bool   { return p.frontMatter.Bool("published", true) }
func (p *pageFields) Site() *Site       { return p.site }

// ReadPage reads a Page from a file, using defaults as the default front matter.
func ReadPage(site *Site, collection *Collection, relpath string, defaults VariableMap) (p Page, err error) {
	abspath := filepath.Join(site.Source, relpath)
	magic, err := helpers.ReadFileMagic(abspath)
	if err != nil {
		return
	}
	info, err := os.Stat(abspath)
	if err != nil {
		return
	}

	fields := pageFields{
		site:        site,
		collection:  collection,
		modTime:     info.ModTime(),
		relpath:     relpath,
		frontMatter: defaults,
	}
	if string(magic) == "---\n" {
		p, err = NewDynamicPage(fields)
		if err != nil {
			return
		}
	} else {
		p = &StaticPage{fields}
	}
	// Compute this after creating the page, in order to pick up the front matter.
	err = p.initPermalink()
	if err != nil {
		return
	}
	return
}

func (p *StaticPage) Write(w io.Writer) error {
	source, err := ioutil.ReadFile(p.Source())
	if err != nil {
		return err
	}
	_, err = w.Write(source)
	return err
}

// TemplateObject returns the attributes of the template page object.
// See https://jekyllrb.com/docs/variables/#page-variables
func (p *pageFields) TemplateObject() VariableMap {
	var (
		relpath = "/" + filepath.ToSlash(p.relpath)
		base    = path.Base(relpath)
		ext     = path.Ext(relpath)
	)

	return VariableMap{
		"path":          relpath,
		"modified_time": p.modTime,
		"name":          base,
		"basename":      helpers.PathWithoutExtension(base),
		"extname":       ext,
	}
}

// DebugVariables returns a map that's useful to present during diagnostics.
// For a static page, this is just the page's template object attributes.
func (p *pageFields) DebugVariables() VariableMap {
	return p.TemplateObject()
}

// Source returns the file path of the page source.
func (p *pageFields) Source() string {
	return filepath.Join(p.site.Source, p.relpath)
}

// StaticPage is a static page.
type StaticPage struct {
	pageFields
}

// Static returns a bool indicating that the page is a static page.
func (p *StaticPage) Static() bool { return true }

// TemplateObject returns metadata for use in the representation of the page as a collection item
func (p *StaticPage) TemplateObject() VariableMap {
	return MergeVariableMaps(p.frontMatter, p.pageFields.TemplateObject())
}
<|endoftext|>