<commit_before>/*
 * (c) 2013, Caoimhe Chaos <caoimhechaos@protonmail.com>,
 *	     Starship Factory. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the name  of the Starship Factory  nor the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"crypto/sha1"
	"encoding/base64"
	"expvar"
	"hash"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

var fmap = template.FuncMap{
	"html": template.HTMLEscaper,
	"url":  UserInputFormatter,
}

func UserInputFormatter(v ...interface{}) string {
	return template.HTMLEscapeString(url.QueryEscape(v[0].(string)))
}

// Statistics.
var numRequests *expvar.Int = expvar.NewInt("num-http-requests")
var numSubmitted *expvar.Int = expvar.NewInt("num-successful-form-submissions")
var numSubmitErrors *expvar.Map = expvar.NewMap("num-form-submission-errors")

// Regular expressions for verification of the email and phone number fields.
var emailRe *regexp.Regexp
var phoneRe *regexp.Regexp

// Data type for the HTTP handler which takes the requests. We require the
// templates and a passthrough object for static content requests, so we
// need to hold some state.
type FormInputHandler struct {
	applicationTmpl *template.Template
	printTmpl       *template.Template
	passthrough     http.Handler
}

// Data used by the HTML template. Contains not just data entered so far,
// but also some error texts in case there was a problem submitting data.
type FormInputData struct {
	MemberData *Member
	Comment    string
	CommonErr  string
	FieldErr   map[string]string
}

// Parse the form data from the membership signup form and verify that it
// can be considered acceptable. If the data looks correct, return the
// print template for the user to sign and send in.
func (self *FormInputHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var err error
	var data FormInputData
	var fee float64
	var ok bool = true
	numRequests.Add(1)

	// Pass JavaScript and CSS requests through to the passthrough handler.
	if strings.HasPrefix(req.URL.Path, "/css/") ||
		strings.HasPrefix(req.URL.Path, "/js/") ||
		req.URL.Path == "/favicon.ico" {
		self.passthrough.ServeHTTP(w, req)
		return
	}

	data.FieldErr = make(map[string]string)

	if err = req.ParseForm(); err != nil {
		data.CommonErr = err.Error()
		numSubmitErrors.Add(err.Error(), 1)
		self.applicationTmpl.Execute(w, data)
		return
	}

	// No data entered: the user is probably just going to the web site
	// for the first time, so data validation is useless.
	if len(req.PostForm) == 0 {
		self.applicationTmpl.Execute(w, data)
		return
	}

	// You might think that it would be a good idea to split the name
	// field into first and last name, which might even work for this
	// specific, localized use case, but it is bad practice, because
	// some countries don't have the concept of last names, and would
	// set a bad precedent for people reading and using this code.
	var name string = req.PostFormValue("mr[name]")
	if len(name) <= 0 {
		numSubmitErrors.Add("no-name", 1)
		data.FieldErr["name"] = "Ein Name ist erforderlich"
	} else {
		data.MemberData.Name = &name
	}

	// The same applies to the address. There just is no globally common
	// format for home addresses, not everything has a house number, and
	// we don't want to encourage people to think so.
	var address string = req.PostFormValue("mr[address]")
	if len(address) <= 0 {
		numSubmitErrors.Add("no-street", 1)
		data.FieldErr["address"] = "Eine Adresse ist erforderlich"
	} else {
		data.MemberData.Street = &address
	}

	var city string = req.PostFormValue("mr[city]")
	if len(city) <= 0 {
		numSubmitErrors.Add("no-city", 1)
		data.FieldErr["city"] = "Ein Wohnort ist erforderlich"
	} else {
		data.MemberData.Street = &city
	}

	// As for zip codes, you don't even have to go to India to find out
	// why enforcing a format there doesn't work. Many people around
	// here believe that a 4-5 digit number is enough to represent a zip
	// code. But then the Netherlands have zip codes like «4201 EB»
	// (where EB is part of the zip code). If you consider adding an
	// exception for this, please note that British zip codes look like
	// «G1 1PP». The only realistic way to deal with these is to allow
	// free text for zip codes.
	var zip string = req.PostFormValue("mr[zip]")
	if len(zip) <= 0 {
		numSubmitErrors.Add("no-zip", 1)
		data.FieldErr["zip"] = "Eine Postleitzahl ist erforderlich"
	} else {
		data.MemberData.Zipcode = &zip
	}

	// The country could arguably be a list.
	var country string = req.PostFormValue("mr[country]")
	if len(country) <= 0 {
		numSubmitErrors.Add("no-country", 1)
		data.FieldErr["country"] = "Ein Wohnland ist erforderlich"
	} else {
		data.MemberData.Country = &country
	}

	var email string = req.PostFormValue("mr[email]")
	if !emailRe.MatchString(email) {
		if len(email) > 0 {
			data.FieldErr["email"] = "Mailadresse sollte im Format a@b.ch sein"
			numSubmitErrors.Add("bad-email-format", 1)
		} else {
			data.FieldErr["email"] = "Muss angegeben werden"
			numSubmitErrors.Add("no-email", 1)
		}
		ok = false
	} else {
		data.MemberData.Email = &email
	}

	var phone string = req.PostFormValue("mr[telephone]")
	if len(phone) > 0 && !phoneRe.MatchString(phone) {
		data.FieldErr["telephone"] = "Telephonnummer sollte im Format +41 79 123 45 67 sein"
		numSubmitErrors.Add("bad-phone-format", 1)
		ok = false
	} else {
		data.MemberData.Phone = &phone
	}

	// TODO(caoimhe): Verify the user name field.
	var username string = req.PostFormValue("mr[username]")
	if len(username) > 0 {
		data.MemberData.Username = &username
	}

	var pw string = req.PostFormValue("mr[password]")
	if pw != req.PostFormValue("mr[passwordConfirm]") {
		data.FieldErr["password"] = "Passworte stimmen nicht überein"
		numSubmitErrors.Add("password-mismatch", 1)
		ok = false
	} else {
		var h hash.Hash = sha1.New()
		h.Write([]byte(pw))
		pw = "{SHA}" + base64.StdEncoding.EncodeToString(h.Sum([]byte{}))
		data.MemberData.Pwhash = &pw
	}

	if req.PostFormValue("mr[statutes]") != "accepted" {
		data.FieldErr["statutes"] = "Statuten müssen akzeptiert werden"
		numSubmitErrors.Add("statutes-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[ipay]") != "accepted" {
		data.FieldErr["ipay"] = "Zahlungsbereitschaft ist notwendig"
		numSubmitErrors.Add("payment-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[rules]") != "accepted" {
		data.FieldErr["rules"] = "Reglement muss akzeptiert werden"
		numSubmitErrors.Add("rules-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[gt18]") != "yes" {
		data.FieldErr["gt18"] = "Man muss mindestens 18 Jahre sein, um uns beizutreten"
		numSubmitErrors.Add("not-gt18", 1)
		ok = false
	}

	if len(req.PostFormValue("mr[customFee]")) > 0 {
		fee, err = strconv.ParseFloat(req.PostFormValue("mr[customFee]"), 64)
		if err == strconv.ErrRange {
			data.FieldErr["customFee"] = "Der Betrag ist irgendwie etwas gross/klein, oder?"
			numSubmitErrors.Add("fee-out-of-range", 1)
			ok = false
		} else if err == strconv.ErrSyntax {
			data.FieldErr["customFee"] = "Der Mitgliedsbeitrag kann nicht als Zahl identifiziert werden"
			numSubmitErrors.Add("fee-not-a-number", 1)
			log.Print("Unable to parse ", req.PostFormValue("mr[customFee]"),
				" as a valid fee")
			ok = false
		} else if err != nil {
			// No idea? This shouldn't really happen.
			data.FieldErr["customFee"] = err.Error()
			log.Print("Error converting ", req.PostFormValue("mr[customFee]"),
				" to a number: ", err)
			ok = false
		}
	}
	if req.PostFormValue("mr[fee]") == "custom" {
		if fee < 50 && req.PostFormValue("mr[reduction]") != "requested" {
			data.FieldErr["customFee"] = "Für einen Betrag unter 50 CHF muss eine Ermässigung beantragt werden"
			numSubmitErrors.Add("low-fee-without-reduction", 1)
			ok = false
		} else if len(req.PostFormValue("mr[customFee]")) <= 0 {
			data.FieldErr["customFee"] = "Die Angabe eines Mitgliedsbeitrages ist notwendig"
			ok = false
		} else {
			var intfee uint64 = uint64(fee)
			data.MemberData.Fee = &intfee
		}
	} else if req.PostFormValue("mr[fee]") != "SFr. 50.--" {
		data.FieldErr["fee"] = "Unbekannter Wert für den Mitgliedsbeitrag"
		numSubmitErrors.Add("unknown-fee-value", 1)
		ok = false
	} else {
		var intfee uint64 = 50
		data.MemberData.Fee = &intfee
	}

	data.Comment = req.PostFormValue("mr[comments]")

	if ok {
		numSubmitted.Add(1)
		err = self.printTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing print template: ", err)
		}
	} else {
		err = self.applicationTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing request form template: ", err)
		}
	}
}

func init() {
	emailRe = regexp.MustCompile(`^[A-Za-z0-9-_\.]+@[A-Za-z0-9-_\.]+$`)
	phoneRe = regexp.MustCompile(`^\+?[0-9 -\.]+$`)
}
<commit_msg>Fill the City field with the city, not the Street.<commit_after>/*
 * (c) 2013, Caoimhe Chaos <caoimhechaos@protonmail.com>,
 *	     Starship Factory. All rights reserved.
 *
 * Redistribution and use in source  and binary forms, with or without
 * modification, are permitted  provided that the following conditions
 * are met:
 *
 * * Redistributions of  source code  must retain the  above copyright
 *   notice, this list of conditions and the following disclaimer.
 * * Redistributions in binary form must reproduce the above copyright
 *   notice, this  list of conditions and the  following disclaimer in
 *   the  documentation  and/or  other  materials  provided  with  the
 *   distribution.
 * * Neither  the name  of the Starship Factory  nor the  name  of its
 *   contributors may  be used to endorse or  promote products derived
 *   from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS"  AND ANY EXPRESS  OR IMPLIED WARRANTIES  OF MERCHANTABILITY
 * AND FITNESS  FOR A PARTICULAR  PURPOSE ARE DISCLAIMED. IN  NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED  TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE,  DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT  LIABILITY,  OR  TORT  (INCLUDING NEGLIGENCE  OR  OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package main

import (
	"crypto/sha1"
	"encoding/base64"
	"expvar"
	"hash"
	"html/template"
	"log"
	"net/http"
	"net/url"
	"regexp"
	"strconv"
	"strings"
)

var fmap = template.FuncMap{
	"html": template.HTMLEscaper,
	"url":  UserInputFormatter,
}

func UserInputFormatter(v ...interface{}) string {
	return template.HTMLEscapeString(url.QueryEscape(v[0].(string)))
}

// Statistics.
var numRequests *expvar.Int = expvar.NewInt("num-http-requests")
var numSubmitted *expvar.Int = expvar.NewInt("num-successful-form-submissions")
var numSubmitErrors *expvar.Map = expvar.NewMap("num-form-submission-errors")

// Regular expressions for verification of the email and phone number fields.
var emailRe *regexp.Regexp
var phoneRe *regexp.Regexp

// Data type for the HTTP handler which takes the requests. We require the
// templates and a passthrough object for static content requests, so we
// need to hold some state.
type FormInputHandler struct {
	applicationTmpl *template.Template
	printTmpl       *template.Template
	passthrough     http.Handler
}

// Data used by the HTML template. Contains not just data entered so far,
// but also some error texts in case there was a problem submitting data.
type FormInputData struct {
	MemberData *Member
	Comment    string
	CommonErr  string
	FieldErr   map[string]string
}

// Parse the form data from the membership signup form and verify that it
// can be considered acceptable. If the data looks correct, return the
// print template for the user to sign and send in.
func (self *FormInputHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var err error
	var data FormInputData
	var fee float64
	var ok bool = true
	numRequests.Add(1)

	// Pass JavaScript and CSS requests through to the passthrough handler.
	if strings.HasPrefix(req.URL.Path, "/css/") ||
		strings.HasPrefix(req.URL.Path, "/js/") ||
		req.URL.Path == "/favicon.ico" {
		self.passthrough.ServeHTTP(w, req)
		return
	}

	data.FieldErr = make(map[string]string)

	if err = req.ParseForm(); err != nil {
		data.CommonErr = err.Error()
		numSubmitErrors.Add(err.Error(), 1)
		self.applicationTmpl.Execute(w, data)
		return
	}

	// No data entered: the user is probably just going to the web site
	// for the first time, so data validation is useless.
	if len(req.PostForm) == 0 {
		self.applicationTmpl.Execute(w, data)
		return
	}

	// You might think that it would be a good idea to split the name
	// field into first and last name, which might even work for this
	// specific, localized use case, but it is bad practice, because
	// some countries don't have the concept of last names, and would
	// set a bad precedent for people reading and using this code.
	var name string = req.PostFormValue("mr[name]")
	if len(name) <= 0 {
		numSubmitErrors.Add("no-name", 1)
		data.FieldErr["name"] = "Ein Name ist erforderlich"
	} else {
		data.MemberData.Name = &name
	}

	// The same applies to the address. There just is no globally common
	// format for home addresses, not everything has a house number, and
	// we don't want to encourage people to think so.
	var address string = req.PostFormValue("mr[address]")
	if len(address) <= 0 {
		numSubmitErrors.Add("no-street", 1)
		data.FieldErr["address"] = "Eine Adresse ist erforderlich"
	} else {
		data.MemberData.Street = &address
	}

	var city string = req.PostFormValue("mr[city]")
	if len(city) <= 0 {
		numSubmitErrors.Add("no-city", 1)
		data.FieldErr["city"] = "Ein Wohnort ist erforderlich"
	} else {
		data.MemberData.City = &city
	}

	// As for zip codes, you don't even have to go to India to find out
	// why enforcing a format there doesn't work. Many people around
	// here believe that a 4-5 digit number is enough to represent a zip
	// code. But then the Netherlands have zip codes like «4201 EB»
	// (where EB is part of the zip code). If you consider adding an
	// exception for this, please note that British zip codes look like
	// «G1 1PP». The only realistic way to deal with these is to allow
	// free text for zip codes.
	var zip string = req.PostFormValue("mr[zip]")
	if len(zip) <= 0 {
		numSubmitErrors.Add("no-zip", 1)
		data.FieldErr["zip"] = "Eine Postleitzahl ist erforderlich"
	} else {
		data.MemberData.Zipcode = &zip
	}

	// The country could arguably be a list.
	var country string = req.PostFormValue("mr[country]")
	if len(country) <= 0 {
		numSubmitErrors.Add("no-country", 1)
		data.FieldErr["country"] = "Ein Wohnland ist erforderlich"
	} else {
		data.MemberData.Country = &country
	}

	var email string = req.PostFormValue("mr[email]")
	if !emailRe.MatchString(email) {
		if len(email) > 0 {
			data.FieldErr["email"] = "Mailadresse sollte im Format a@b.ch sein"
			numSubmitErrors.Add("bad-email-format", 1)
		} else {
			data.FieldErr["email"] = "Muss angegeben werden"
			numSubmitErrors.Add("no-email", 1)
		}
		ok = false
	} else {
		data.MemberData.Email = &email
	}

	var phone string = req.PostFormValue("mr[telephone]")
	if len(phone) > 0 && !phoneRe.MatchString(phone) {
		data.FieldErr["telephone"] = "Telephonnummer sollte im Format +41 79 123 45 67 sein"
		numSubmitErrors.Add("bad-phone-format", 1)
		ok = false
	} else {
		data.MemberData.Phone = &phone
	}

	// TODO(caoimhe): Verify the user name field.
	var username string = req.PostFormValue("mr[username]")
	if len(username) > 0 {
		data.MemberData.Username = &username
	}

	var pw string = req.PostFormValue("mr[password]")
	if pw != req.PostFormValue("mr[passwordConfirm]") {
		data.FieldErr["password"] = "Passworte stimmen nicht überein"
		numSubmitErrors.Add("password-mismatch", 1)
		ok = false
	} else {
		var h hash.Hash = sha1.New()
		h.Write([]byte(pw))
		pw = "{SHA}" + base64.StdEncoding.EncodeToString(h.Sum([]byte{}))
		data.MemberData.Pwhash = &pw
	}

	if req.PostFormValue("mr[statutes]") != "accepted" {
		data.FieldErr["statutes"] = "Statuten müssen akzeptiert werden"
		numSubmitErrors.Add("statutes-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[ipay]") != "accepted" {
		data.FieldErr["ipay"] = "Zahlungsbereitschaft ist notwendig"
		numSubmitErrors.Add("payment-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[rules]") != "accepted" {
		data.FieldErr["rules"] = "Reglement muss akzeptiert werden"
		numSubmitErrors.Add("rules-not-accepted", 1)
		ok = false
	}

	if req.PostFormValue("mr[gt18]") != "yes" {
		data.FieldErr["gt18"] = "Man muss mindestens 18 Jahre sein, um uns beizutreten"
		numSubmitErrors.Add("not-gt18", 1)
		ok = false
	}

	if len(req.PostFormValue("mr[customFee]")) > 0 {
		fee, err = strconv.ParseFloat(req.PostFormValue("mr[customFee]"), 64)
		if err == strconv.ErrRange {
			data.FieldErr["customFee"] = "Der Betrag ist irgendwie etwas gross/klein, oder?"
			numSubmitErrors.Add("fee-out-of-range", 1)
			ok = false
		} else if err == strconv.ErrSyntax {
			data.FieldErr["customFee"] = "Der Mitgliedsbeitrag kann nicht als Zahl identifiziert werden"
			numSubmitErrors.Add("fee-not-a-number", 1)
			log.Print("Unable to parse ", req.PostFormValue("mr[customFee]"),
				" as a valid fee")
			ok = false
		} else if err != nil {
			// No idea? This shouldn't really happen.
			data.FieldErr["customFee"] = err.Error()
			log.Print("Error converting ", req.PostFormValue("mr[customFee]"),
				" to a number: ", err)
			ok = false
		}
	}
	if req.PostFormValue("mr[fee]") == "custom" {
		if fee < 50 && req.PostFormValue("mr[reduction]") != "requested" {
			data.FieldErr["customFee"] = "Für einen Betrag unter 50 CHF muss eine Ermässigung beantragt werden"
			numSubmitErrors.Add("low-fee-without-reduction", 1)
			ok = false
		} else if len(req.PostFormValue("mr[customFee]")) <= 0 {
			data.FieldErr["customFee"] = "Die Angabe eines Mitgliedsbeitrages ist notwendig"
			ok = false
		} else {
			var intfee uint64 = uint64(fee)
			data.MemberData.Fee = &intfee
		}
	} else if req.PostFormValue("mr[fee]") != "SFr. 50.--" {
		data.FieldErr["fee"] = "Unbekannter Wert für den Mitgliedsbeitrag"
		numSubmitErrors.Add("unknown-fee-value", 1)
		ok = false
	} else {
		var intfee uint64 = 50
		data.MemberData.Fee = &intfee
	}

	data.Comment = req.PostFormValue("mr[comments]")

	if ok {
		numSubmitted.Add(1)
		err = self.printTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing print template: ", err)
		}
	} else {
		err = self.applicationTmpl.Execute(w, data)
		if err != nil {
			log.Print("Error executing request form template: ", err)
		}
	}
}

func init() {
	emailRe = regexp.MustCompile(`^[A-Za-z0-9-_\.]+@[A-Za-z0-9-_\.]+$`)
	phoneRe = regexp.MustCompile(`^\+?[0-9 -\.]+$`)
}
<|endoftext|>


<commit_before>package httpcheck

import (
	"errors"
	"gopkg.in/yaml.v1"
	"regexp"
)

// Manifest contains one or more Servers, each having some Scenarios.
type Manifest []Server

// SetBaseURL walks through all tests and prefixes any relative URLs (e.g. '/') with a base URL.
func (m *Manifest) SetBaseURL(baseurl string) {
	scheme := regexp.MustCompile("^https?:")
	for i := range *m {
		for j := range (*m)[i].Scenarios {
			for k := range (*m)[i].Scenarios[j].Tests {
				match := scheme.MatchString((*m)[i].Scenarios[j].Tests[k].Url)
				if !match {
					//	This is a relative URL, prefix it with baseurl.
					//	When glueing the URLs together, remove a double '/' to make prettier URLs.
					//	To have a test that starts with '//', add it to the test URL explicitly.
					var url string
					if baseurl[len(baseurl)-1] == '/' && len((*m)[i].Scenarios[j].Tests[k].Url) > 0 &&
						(*m)[i].Scenarios[j].Tests[k].Url[0] == '/' {
						url = baseurl[0:len(baseurl)-1] + (*m)[i].Scenarios[j].Tests[k].Url
					} else {
						url = baseurl + (*m)[i].Scenarios[j].Tests[k].Url
					}
					(*m)[i].Scenarios[j].Tests[k].Url = url
				}
			}
		}
	}
}

// Test runs tests on all servers in the manifest.
// It returns an error if one or more server has errors, or nil otherwise.
// In case there are multiple errors, the error contains the concatenated messages.
func (m Manifest) Test() error {
	if len(m) == 0 {
		return errors.New("Manifest is empty")
	}

	// Start goroutine for every server.Test() call
	// The result of server.Test() is an error or nil. This is passed through a channel.
	var testresults []chan error
	for _, server := range m {
		c := make(chan error)
		defer close(c)
		testresults = append(testresults, c)
		go func(server Server) {
			c <- server.Test()
		}(server)
	}

	// Read from every channel to collect all errors returned.
	// This blocks until every channel has something to receive so all tests are done.
	errorcount := 0
	errorstr := ""
	for _, c := range testresults {
		err := <-c
		if err != nil {
			if errorcount > 0 {
				errorstr += "\n"
			}
			errorstr += err.Error()
			errorcount++
		}
	}

	if errorcount > 0 {
		return errors.New(errorstr)
	} else {
		return nil
	}
}

// FromYAML parses YAML input and returns a manifest.
func FromYAML(y []byte) (Manifest, error) {
	var manifest Manifest
	err := yaml.Unmarshal(y, &manifest)
	if err != nil {
		return nil, err
	}
	return manifest, nil
}
<commit_msg>manifest.SetBaseURL() fix intolerance for upper case scheme e.g. "HtTp://"<commit_after>package httpcheck

import (
	"errors"
	"gopkg.in/yaml.v1"
	"regexp"
)

// Manifest contains one or more Servers, each having some Scenarios.
type Manifest []Server

// SetBaseURL walks through all tests and prefixes any relative URLs (e.g. '/') with a base URL.
func (m *Manifest) SetBaseURL(baseurl string) {
	scheme := regexp.MustCompile("(?i)^https?:")
	for i := range *m {
		for j := range (*m)[i].Scenarios {
			for k := range (*m)[i].Scenarios[j].Tests {
				match := scheme.MatchString((*m)[i].Scenarios[j].Tests[k].Url)
				if !match {
					//	This is a relative URL, prefix it with baseurl.
					//	When glueing the URLs together, remove a double '/' to make prettier URLs.
					//	To have a test that starts with '//', add it to the test URL explicitly.
					var url string
					if baseurl[len(baseurl)-1] == '/' && len((*m)[i].Scenarios[j].Tests[k].Url) > 0 &&
						(*m)[i].Scenarios[j].Tests[k].Url[0] == '/' {
						url = baseurl[0:len(baseurl)-1] + (*m)[i].Scenarios[j].Tests[k].Url
					} else {
						url = baseurl + (*m)[i].Scenarios[j].Tests[k].Url
					}
					(*m)[i].Scenarios[j].Tests[k].Url = url
				}
			}
		}
	}
}

// Test runs tests on all servers in the manifest.
// It returns an error if one or more server has errors, or nil otherwise.
// In case there are multiple errors, the error contains the concatenated messages.
func (m Manifest) Test() error {
	if len(m) == 0 {
		return errors.New("Manifest is empty")
	}

	// Start goroutine for every server.Test() call
	// The result of server.Test() is an error or nil. This is passed through a channel.
	var testresults []chan error
	for _, server := range m {
		c := make(chan error)
		defer close(c)
		testresults = append(testresults, c)
		go func(server Server) {
			c <- server.Test()
		}(server)
	}

	// Read from every channel to collect all errors returned.
	// This blocks until every channel has something to receive so all tests are done.
	errorcount := 0
	errorstr := ""
	for _, c := range testresults {
		err := <-c
		if err != nil {
			if errorcount > 0 {
				errorstr += "\n"
			}
			errorstr += err.Error()
			errorcount++
		}
	}

	if errorcount > 0 {
		return errors.New(errorstr)
	} else {
		return nil
	}
}

// FromYAML parses YAML input and returns a manifest.
func FromYAML(y []byte) (Manifest, error) {
	var manifest Manifest
	err := yaml.Unmarshal(y, &manifest)
	if err != nil {
		return nil, err
	}
	return manifest, nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"errors"
	"flag"
	"fmt"
	"go/ast"
	"go/printer"
	"html/template"
	"os"
	"os/exec"
	"strings"

	"golang.org/x/tools/go/loader"

	"github.com/kisielk/gotool"
)

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) != 1 {
		fmt.Fprintln(os.Stderr, "Func name missing: goreduce funcName")
		os.Exit(1)
	}
	if err := reduce(args[0]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

const (
	testFile = "reduce_test.go"
	testName = "TestReduce"
)

var testTmpl = template.Must(template.New("test").Parse(`` +
	`package {{ .Pkg }}

import "testing"

func {{ .TestName }}(t *testing.T) {
	if {{ .Expr }}() {
		t.Fail()
	}
}
`))

func emptyFile(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return err
	}
	_, err := f.Seek(0, 0)
	return err
}

func writeTest(f *os.File, pkgName, expr string) error {
	if err := emptyFile(f); err != nil {
		return err
	}
	return testTmpl.Execute(f, struct {
		Pkg, TestName, Expr string
	}{
		Pkg:      pkgName,
		TestName: testName,
		Expr:     expr,
	})
}

func reduce(funcName string) error {
	paths := gotool.ImportPaths([]string{"."})
	conf := loader.Config{}
	if _, err := conf.FromArgs(paths, false); err != nil {
		return err
	}
	prog, err := conf.Load()
	if err != nil {
		return err
	}
	pkgInfos := prog.InitialPackages()
	if len(pkgInfos) != 1 {
		return fmt.Errorf("expected 1 package, got %d", len(pkgInfos))
	}
	pkgInfo := pkgInfos[0]
	pkg := pkgInfo.Pkg
	if pkg.Scope().Lookup(funcName) == nil {
		return fmt.Errorf("top-level func %s does not exist", funcName)
	}
	tf, err := os.Create(testFile)
	if err != nil {
		return err
	}
	defer func() {
		tf.Close()
		os.Remove(testFile)
	}()
	// Check that it compiles and the func returns true, meaning
	// that it's still reproducing the issue.
	if err := writeTest(tf, pkg.Name(), funcName); err != nil {
		return err
	}
	if err := runTest(); err == nil {
		return fmt.Errorf("expected test to fail")
	}
	if err := writeTest(tf, pkg.Name(), "!"+funcName); err != nil {
		return err
	}
	if err := runTest(); err != nil {
		return err
	}
	file, fd := findFunc(pkgInfo.Files, funcName)
	fname := conf.Fset.Position(file.Pos()).Filename
	f, err := os.Create(fname)
	if err != nil {
		return err
	}
	defer f.Close()
	block := fd.Body
	for _, b := range removeStmt(block) {
		fd.Body = b
		if err := emptyFile(f); err != nil {
			return err
		}
		if err := printer.Fprint(f, conf.Fset, file); err != nil {
			return err
		}
		if err := runTest(); err != nil {
			fmt.Println(err)
			continue
		}
		return nil
	}
	fd.Body = block
	printer.Fprint(f, conf.Fset, file)
	return nil
}

func removeStmt(orig *ast.BlockStmt) []*ast.BlockStmt {
	bs := make([]*ast.BlockStmt, len(orig.List))
	for i := range orig.List {
		b := &ast.BlockStmt{}
		*b = *orig
		b.List = make([]ast.Stmt, len(orig.List)-1)
		copy(b.List, orig.List[:i])
		copy(b.List[i:], orig.List[i+1:])
		bs[i] = b
	}
	return bs
}

func findFunc(files []*ast.File, name string) (*ast.File, *ast.FuncDecl) {
	for _, file := range files {
		for _, decl := range file.Decls {
			fd, ok := decl.(*ast.FuncDecl)
			if ok && fd.Name.Name == name {
				return file, fd
			}
		}
	}
	return nil, nil
}

func runTest() error {
	cmd := exec.Command("go", "test", "-run", "^"+testName+"$")
	out, err := cmd.CombinedOutput()
	if err == nil {
		return nil
	}
	if strings.HasPrefix(err.Error(), "exit status") {
		s := strings.TrimSpace(string(out))
		return errors.New(s)
	}
	return err
}
<commit_msg>Simplify code a bit<commit_after>// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"errors"
	"flag"
	"fmt"
	"go/ast"
	"go/printer"
	"html/template"
	"os"
	"os/exec"
	"strings"

	"golang.org/x/tools/go/loader"

	"github.com/kisielk/gotool"
)

func main() {
	flag.Parse()
	args := flag.Args()
	if len(args) != 1 {
		fmt.Fprintln(os.Stderr, "Func name missing: goreduce funcName")
		os.Exit(1)
	}
	if err := reduce(args[0]); err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

const (
	testFile = "reduce_test.go"
	testName = "TestReduce"
)

var testTmpl = template.Must(template.New("test").Parse(`` +
	`package {{ .Pkg }}

import "testing"

func {{ .TestName }}(t *testing.T) {
	if {{ .Expr }}() {
		t.Fail()
	}
}
`))

func emptyFile(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return err
	}
	_, err := f.Seek(0, 0)
	return err
}

func writeTest(f *os.File, pkgName, expr string) error {
	if err := emptyFile(f); err != nil {
		return err
	}
	return testTmpl.Execute(f, struct {
		Pkg, TestName, Expr string
	}{
		Pkg:      pkgName,
		TestName: testName,
		Expr:     expr,
	})
}

func reduce(funcName string) error {
	paths := gotool.ImportPaths([]string{"."})
	conf := loader.Config{}
	if _, err := conf.FromArgs(paths, false); err != nil {
		return err
	}
	prog, err := conf.Load()
	if err != nil {
		return err
	}
	pkgInfos := prog.InitialPackages()
	if len(pkgInfos) != 1 {
		return fmt.Errorf("expected 1 package, got %d", len(pkgInfos))
	}
	pkgInfo := pkgInfos[0]
	pkg := pkgInfo.Pkg
	if pkg.Scope().Lookup(funcName) == nil {
		return fmt.Errorf("top-level func %s does not exist", funcName)
	}
	tf, err := os.Create(testFile)
	if err != nil {
		return err
	}
	defer func() {
		tf.Close()
		os.Remove(testFile)
	}()
	// Check that it compiles and the func returns true, meaning
	// that it's still reproducing the issue.
	if err := writeTest(tf, pkg.Name(), funcName); err != nil {
		return err
	}
	if err := runTest(); err == nil {
		return fmt.Errorf("expected test to fail")
	}
	if err := writeTest(tf, pkg.Name(), "!"+funcName); err != nil {
		return err
	}
	if err := runTest(); err != nil {
		return err
	}
	file, fd := findFunc(pkgInfo.Files, funcName)
	fname := conf.Fset.Position(file.Pos()).Filename
	f, err := os.Create(fname)
	if err != nil {
		return err
	}
	defer f.Close()
	block := fd.Body
	for _, b := range removeStmt(block) {
		fd.Body = b
		if err := emptyFile(f); err != nil {
			return err
		}
		if err := printer.Fprint(f, conf.Fset, file); err != nil {
			return err
		}
		if err := runTest(); err == nil {
			// Reduction worked, exit
			return nil
		}
	}
	// Nothing worked, return to original state
	fd.Body = block
	printer.Fprint(f, conf.Fset, file)
	return nil
}

func removeStmt(orig *ast.BlockStmt) []*ast.BlockStmt {
	bs := make([]*ast.BlockStmt, len(orig.List))
	for i := range orig.List {
		b := &ast.BlockStmt{}
		bs[i], *b = b, *orig
		b.List = make([]ast.Stmt, len(orig.List)-1)
		copy(b.List, orig.List[:i])
		copy(b.List[i:], orig.List[i+1:])
	}
	return bs
}

func findFunc(files []*ast.File, name string) (*ast.File, *ast.FuncDecl) {
	for _, file := range files {
		for _, decl := range file.Decls {
			fd, ok := decl.(*ast.FuncDecl)
			if ok && fd.Name.Name == name {
				return file, fd
			}
		}
	}
	return nil, nil
}

func runTest() error {
	cmd := exec.Command("go", "test", "-run", "^"+testName+"$")
	out, err := cmd.CombinedOutput()
	if err == nil {
		return nil
	}
	if strings.HasPrefix(err.Error(), "exit status") {
		return errors.New(strings.TrimSpace(string(out)))
	}
	return err
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/printer"
	"go/token"
	"go/types"
	"html/template"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
)

const mainFile = "goreduce_main.go"

var mainTmpl = template.Must(template.New("test").Parse(`` +
	`package main

func main() {
	{{ .Func }}()
}
`))

func emptyFile(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return err
	}
	_, err := f.Seek(0, 0)
	return err
}

type reducer struct {
	dir     string
	matchRe *regexp.Regexp

	fset     *token.FileSet
	pkg      *ast.Package
	files    []*ast.File
	file     *ast.File
	funcDecl *ast.FuncDecl

	tinfo types.Config

	tdir    string
	outBin  string
	goArgs  []string
	dstFile *os.File

	didChange bool
	stmt      *ast.Stmt
	expr      *ast.Expr
}

func reduce(dir, funcName, matchStr string, bflags ...string) error {
	r := &reducer{dir: dir}
	var err error
	if r.tdir, err = ioutil.TempDir("", "goreduce"); err != nil {
		return err
	}
	defer os.RemoveAll(r.tdir)
	r.tinfo.Importer = importer.Default()
	if r.matchRe, err = regexp.Compile(matchStr); err != nil {
		return err
	}
	r.fset = token.NewFileSet()
	pkgs, err := parser.ParseDir(r.fset, r.dir, nil, 0)
	if err != nil {
		return err
	}
	if len(pkgs) != 1 {
		return fmt.Errorf("expected 1 package, got %d", len(pkgs))
	}
	for _, pkg := range pkgs {
		r.pkg = pkg
	}
	for _, file := range r.pkg.Files {
		r.files = append(r.files, file)
	}
	r.file, r.funcDecl = findFunc(r.files, funcName)
	if r.file == nil {
		return fmt.Errorf("top-level func %s does not exist", funcName)
	}
	tfnames := make([]string, 0, len(r.files)+1)
	for _, file := range r.files {
		fname := r.fset.Position(file.Pos()).Filename
		tfname := filepath.Join(r.tdir, filepath.Base(fname))
		dst, err := os.Create(tfname)
		if err != nil {
			return nil
		}
		file.Name.Name = "main"
		if err := printer.Fprint(dst, r.fset, file); err != nil {
			return err
		}
		if file == r.file {
			r.dstFile = dst
		} else if err := dst.Close(); err != nil {
			return err
		}
		tfnames = append(tfnames, tfname)
	}
	mfname := filepath.Join(r.tdir, mainFile)
	mf, err := os.Create(mfname)
	if err != nil {
		return err
	}
	// Check that it compiles and the output matches before we apply
	// any changes
	if err := mainTmpl.Execute(mf, struct {
		Func string
	}{
		Func: funcName,
	}); err != nil {
		return err
	}
	if err := mf.Close(); err != nil {
		return err
	}
	tfnames = append(tfnames, mfname)
	r.outBin = filepath.Join(r.tdir, "bin")
	r.goArgs = append([]string{"build", "-o", r.outBin,
		"-ldflags", "-w -s",
	}, tfnames...)
	if err := r.checkRun(); err != nil {
		return err
	}
	anyChanges := false
	for err == nil {
		if err = r.step(); err == errNoChange {
			err = nil
			break // we're done
		}
		anyChanges = true
	}
	if anyChanges {
		fname := r.fset.Position(r.file.Pos()).Filename
		f, err := os.Create(fname)
		if err != nil {
			return err
		}
		r.file.Name.Name = r.pkg.Name
		if err := printer.Fprint(f, r.fset, r.file); err != nil {
			return err
		}
		if err := f.Close(); err != nil {
			return err
		}
	}
	if err2 := r.dstFile.Close(); err == nil && err2 != nil {
		return err2
	}
	return err
}

func (r *reducer) logChange(node ast.Node, format string, a ...interface{}) {
	if *verbose {
		pos := r.fset.Position(node.Pos())
		rpath, err := filepath.Rel(r.tdir, pos.Filename)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stderr, "%s:%d: %s\n", rpath, pos.Line,
			fmt.Sprintf(format, a...))
	}
}

func (r *reducer) checkRun() error {
	err := r.buildAndRun()
	if err == nil {
		return fmt.Errorf("expected an error to occur")
	}
	if s := err.Error(); !r.matchRe.MatchString(s) {
		return fmt.Errorf("error does not match:\n%s", s)
	}
	return nil
}

var errNoChange = fmt.Errorf("no reduction to apply")

func (r *reducer) writeSource() error {
	if err := emptyFile(r.dstFile); err != nil {
		return err
	}
	return printer.Fprint(r.dstFile, r.fset, r.file)
}

func (r *reducer) okChange() bool {
	if r.didChange {
		return false
	}
	// go/types catches most compile errors before writing
	// to disk and running the go tool. Since quite a lot of
	// changes are nonsensical, this is often a big win.
	if _, err := r.tinfo.Check(r.dir, r.fset, r.files, nil); err != nil {
		terr, ok := err.(types.Error)
		if ok && terr.Soft && r.shouldRetry(terr) {
			return r.okChange()
		}
		return false
	}
	if err := r.writeSource(); err != nil {
		return false
	}
	if err := r.checkRun(); err != nil {
		return false
	}
	// Reduction worked
	r.didChange = true
	return true
}

var (
	importNotUsed = regexp.MustCompile(`"(.*)" imported but not used`)
)

func (r *reducer) shouldRetry(terr types.Error) bool {
	if sm := importNotUsed.FindStringSubmatch(terr.Msg); sm != nil {
		name, path := "", sm[1]
		for _, imp := range r.file.Imports {
			if imp.Name != nil && strings.Trim(imp.Path.Value, `"`) == path {
				name = imp.Name.Name
				break
			}
		}
		return astutil.DeleteNamedImport(r.fset, r.file, name, path)
	}
	return false
}

func (r *reducer) step() error {
	r.didChange = false
	r.walk(r.file, func(v interface{}) bool {
		if r.didChange {
			return false
		}
		return r.reduceNode(v)
	})
	if r.didChange {
		return nil
	}
	return errNoChange
}

func findFunc(files []*ast.File, name string) (*ast.File, *ast.FuncDecl) {
	for _, file := range files {
		for _, decl := range file.Decls {
			funcDecl, ok := decl.(*ast.FuncDecl)
			if ok && funcDecl.Name.Name == name {
				return file, funcDecl
			}
		}
	}
	return nil, nil
}

func (r *reducer) buildAndRun() error {
	cmd := exec.Command("go", r.goArgs...)
	if out, err := cmd.CombinedOutput(); err != nil {
		if strings.HasPrefix(err.Error(), "exit status") {
			return errors.New(string(out))
		}
		return err
	}
	if out, err := exec.Command(r.outBin).CombinedOutput(); err != nil {
		if strings.HasPrefix(err.Error(), "exit status") {
			return errors.New(string(out))
		}
		return err
	}
	return nil
}
<commit_msg>Inline r.writeSource<commit_after>// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/importer"
	"go/parser"
	"go/printer"
	"go/token"
	"go/types"
	"html/template"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
)

const mainFile = "goreduce_main.go"

var mainTmpl = template.Must(template.New("test").Parse(`` +
	`package main

func main() {
	{{ .Func }}()
}
`))

func emptyFile(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return err
	}
	_, err := f.Seek(0, 0)
	return err
}

type reducer struct {
	dir     string
	matchRe *regexp.Regexp

	fset     *token.FileSet
	pkg      *ast.Package
	files    []*ast.File
	file     *ast.File
	funcDecl *ast.FuncDecl

	tinfo types.Config

	tdir    string
	outBin  string
	goArgs  []string
	dstFile *os.File

	didChange bool
	stmt      *ast.Stmt
	expr      *ast.Expr
}

func reduce(dir, funcName, matchStr string, bflags ...string) error {
	r := &reducer{dir: dir}
	var err error
	if r.tdir, err = ioutil.TempDir("", "goreduce"); err != nil {
		return err
	}
	defer os.RemoveAll(r.tdir)
	r.tinfo.Importer = importer.Default()
	if r.matchRe, err = regexp.Compile(matchStr); err != nil {
		return err
	}
	r.fset = token.NewFileSet()
	pkgs, err := parser.ParseDir(r.fset, r.dir, nil, 0)
	if err != nil {
		return err
	}
	if len(pkgs) != 1 {
		return fmt.Errorf("expected 1 package, got %d", len(pkgs))
	}
	for _, pkg := range pkgs {
		r.pkg = pkg
	}
	for _, file := range r.pkg.Files {
		r.files = append(r.files, file)
	}
	r.file, r.funcDecl = findFunc(r.files, funcName)
	if r.file == nil {
		return fmt.Errorf("top-level func %s does not exist", funcName)
	}
	tfnames := make([]string, 0, len(r.files)+1)
	for _, file := range r.files {
		fname := r.fset.Position(file.Pos()).Filename
		tfname := filepath.Join(r.tdir, filepath.Base(fname))
		dst, err := os.Create(tfname)
		if err != nil {
			return nil
		}
		file.Name.Name = "main"
		if err := printer.Fprint(dst, r.fset, file); err != nil {
			return err
		}
		if file == r.file {
			r.dstFile = dst
		} else if err := dst.Close(); err != nil {
			return err
		}
		tfnames = append(tfnames, tfname)
	}
	mfname := filepath.Join(r.tdir, mainFile)
	mf, err := os.Create(mfname)
	if err != nil {
		return err
	}
	// Check that it compiles and the output matches before we apply
	// any changes
	if err := mainTmpl.Execute(mf, struct {
		Func string
	}{
		Func: funcName,
	}); err != nil {
		return err
	}
	if err := mf.Close(); err != nil {
		return err
	}
	tfnames = append(tfnames, mfname)
	r.outBin = filepath.Join(r.tdir, "bin")
	r.goArgs = append([]string{"build", "-o", r.outBin,
		"-ldflags", "-w -s",
	}, tfnames...)
	if err := r.checkRun(); err != nil {
		return err
	}
	anyChanges := false
	for err == nil {
		if err = r.step(); err == errNoChange {
			err = nil
			break // we're done
		}
		anyChanges = true
	}
	if anyChanges {
		fname := r.fset.Position(r.file.Pos()).Filename
		f, err := os.Create(fname)
		if err != nil {
			return err
		}
		r.file.Name.Name = r.pkg.Name
		if err := printer.Fprint(f, r.fset, r.file); err != nil {
			return err
		}
		if err := f.Close(); err != nil {
			return err
		}
	}
	if err2 := r.dstFile.Close(); err == nil && err2 != nil {
		return err2
	}
	return err
}

func (r *reducer) logChange(node ast.Node, format string, a ...interface{}) {
	if *verbose {
		pos := r.fset.Position(node.Pos())
		rpath, err := filepath.Rel(r.tdir, pos.Filename)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stderr, "%s:%d: %s\n", rpath, pos.Line,
			fmt.Sprintf(format, a...))
	}
}

func (r *reducer) checkRun() error {
	err := r.buildAndRun()
	if err == nil {
		return fmt.Errorf("expected an error to occur")
	}
	if s := err.Error(); !r.matchRe.MatchString(s) {
		return fmt.Errorf("error does not match:\n%s", s)
	}
	return nil
}

var errNoChange = fmt.Errorf("no reduction to apply")

func (r *reducer) okChange() bool {
	if r.didChange {
		return false
	}
	// go/types catches most compile errors before writing
	// to disk and running the go tool. Since quite a lot of
	// changes are nonsensical, this is often a big win.
	if _, err := r.tinfo.Check(r.dir, r.fset, r.files, nil); err != nil {
		terr, ok := err.(types.Error)
		if ok && terr.Soft && r.shouldRetry(terr) {
			return r.okChange()
		}
		return false
	}
	if err := emptyFile(r.dstFile); err != nil {
		return false
	}
	if err := printer.Fprint(r.dstFile, r.fset, r.file); err != nil {
		return false
	}
	if err := r.checkRun(); err != nil {
		return false
	}
	// Reduction worked
	r.didChange = true
	return true
}

var (
	importNotUsed = regexp.MustCompile(`"(.*)" imported but not used`)
)

func (r *reducer) shouldRetry(terr types.Error) bool {
	if sm := importNotUsed.FindStringSubmatch(terr.Msg); sm != nil {
		name, path := "", sm[1]
		for _, imp := range r.file.Imports {
			if imp.Name != nil && strings.Trim(imp.Path.Value, `"`) == path {
				name = imp.Name.Name
				break
			}
		}
		return astutil.DeleteNamedImport(r.fset, r.file, name, path)
	}
	return false
}

func (r *reducer) step() error {
	r.didChange = false
	r.walk(r.file, func(v interface{}) bool {
		if r.didChange {
			return false
		}
		return r.reduceNode(v)
	})
	if r.didChange {
		return nil
	}
	return errNoChange
}

func findFunc(files []*ast.File, name string) (*ast.File, *ast.FuncDecl) {
	for _, file := range files {
		for _, decl := range file.Decls {
			funcDecl, ok := decl.(*ast.FuncDecl)
			if ok && funcDecl.Name.Name == name {
				return file, funcDecl
			}
		}
	}
	return nil, nil
}

func (r *reducer) buildAndRun() error {
	cmd := exec.Command("go", r.goArgs...)
	if out, err := cmd.CombinedOutput(); err != nil {
		if strings.HasPrefix(err.Error(), "exit status") {
			return errors.New(string(out))
		}
		return err
	}
	if out, err := exec.Command(r.outBin).CombinedOutput(); err != nil {
		if strings.HasPrefix(err.Error(), "exit status") {
			return errors.New(string(out))
		}
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/importer"
	"go/printer"
	"go/types"
	"html/template"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/loader"
)

const (
	testFile = "goreduce_test.go"
	testName = "TestReduce"
)

var testTmpl = template.Must(template.New("test").Parse(`` +
	`package {{ .Pkg }}

import "testing"

func {{ .TestName }}(t *testing.T) {
	{{ .Func }}()
}
`))

func emptyFile(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return err
	}
	_, err := f.Seek(0, 0)
	return err
}

type reducer struct {
	loader.Config
	*loader.PackageInfo

	impPath  string
	matchRe  *regexp.Regexp
	file     *ast.File
	funcDecl *ast.FuncDecl
	srcFile  *os.File

	*types.Info

	wd        string
	didChange bool
	stmt      *ast.Stmt
	expr      *ast.Expr
}

func reduce(impPath, funcName, matchStr string) error {
	r := &reducer{impPath: impPath}
	var err error
	if r.wd, err = os.Getwd(); err != nil {
		return err
	}
	// otherwise go/types prints errors to stderr
	// need to panic to stop typechecking
	r.TypeChecker.Error = func(error) { panic(nil) }
	r.TypeChecker.Importer = importer.Default()
	r.Info = new(types.Info)
	r.emptyInfo()
	if r.matchRe, err = regexp.Compile(matchStr); err != nil {
		return err
	}
	if _, err := r.FromArgs([]string{impPath}, false); err != nil {
		return err
	}
	prog, err := r.Load()
	if err != nil {
		return err
	}
	pkgInfos := prog.InitialPackages()
	if len(pkgInfos) != 1 {
		return fmt.Errorf("expected 1 package, got %d", len(pkgInfos))
	}
	r.PackageInfo = pkgInfos[0]
	r.Info = &r.PackageInfo.Info
	pkg := r.PackageInfo.Pkg
	if pkg.Scope().Lookup(funcName) == nil {
		return fmt.Errorf("top-level func %s does not exist", funcName)
	}
	r.file, r.funcDecl = findFunc(r.PackageInfo.Files, funcName)
	fname := r.Fset.Position(r.file.Pos()).Filename
	testFilePath := filepath.Join(filepath.Dir(fname), testFile)
	tf, err := os.Create(testFilePath)
	if err != nil {
		return err
	}
	defer func() {
		tf.Close()
		os.Remove(testFilePath)
	}()
	// Check that it compiles and the output matches before we apply
	// any changes
	if err := testTmpl.Execute(tf, struct {
		Pkg, TestName, Func string
	}{
		Pkg:      pkg.Name(),
		TestName: testName,
		Func:     funcName,
	}); err != nil {
		return err
	}
	if err := r.checkTest(); err != nil {
		return err
	}
	if r.srcFile, err = os.Create(fname); err != nil {
		return err
	}
	for err == nil {
		if err = r.step(); err == errNoChange {
			err = nil
			break // we're done
		}
	}
	if err2 := r.srcFile.Close(); err == nil && err2 != nil {
		return err2
	}
	return err
}

func (r *reducer) logChange(node ast.Node, format string, a ...interface{}) {
	if *verbose {
		pos := r.Fset.Position(node.Pos())
		rpath, err := filepath.Rel(r.wd, pos.Filename)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stderr, "%s:%d: %s\n", rpath, pos.Line,
			fmt.Sprintf(format, a...))
	}
}

func (r *reducer) checkTest() error {
	err := runTest(r.impPath)
	if err == nil {
		return fmt.Errorf("expected an error to occur")
	}
	if s := err.Error(); !r.matchRe.MatchString(s) {
		return fmt.Errorf("error does not match:\n%s", s)
	}
	return nil
}

var errNoChange = fmt.Errorf("no reduction to apply")

func (r *reducer) writeSource() error {
	if err := emptyFile(r.srcFile); err != nil {
		return err
	}
	return printer.Fprint(r.srcFile, r.Fset, r.file)
}

func (r *reducer) emptyInfo() {
	r.Info.Types = make(map[ast.Expr]types.TypeAndValue)
	r.Info.Defs = make(map[*ast.Ident]types.Object)
	r.Info.Uses = make(map[*ast.Ident]types.Object)
}

func (r *reducer) okChange() bool {
	if r.didChange {
		return false
	}
	// go/types catches most compile errors before writing
	// to disk and running the go tool. Since quite a lot of
	// changes are nonsensical, this is often a big win.
	if _, err := r.TypeChecker.Check(r.impPath, r.Fset, r.Files, r.Info); err != nil {
		terr, ok := err.(types.Error)
		if ok && terr.Soft && r.shouldRetry(terr) {
			if err := r.writeSource(); err != nil {
				return false
			}
			return r.okChange()
		}
		return false
	}
	if err := r.writeSource(); err != nil {
		return false
	}
	if err := r.checkTest(); err != nil {
		return false
	}
	// Reduction worked
	r.didChange = true
	return true
}

var (
	importNotUsed = regexp.MustCompile(`"(.*)" imported but not used`)
)

func (r *reducer) shouldRetry(terr types.Error) bool {
	if sm := importNotUsed.FindStringSubmatch(terr.Msg); sm != nil {
		name, path := "", sm[1]
		for _, imp := range r.file.Imports {
			if imp.Name != nil && strings.Trim(imp.Path.Value, `"`) == path {
				name = imp.Name.Name
				break
			}
		}
		return astutil.DeleteNamedImport(r.Fset, r.file, name, path)
	}
	return false
}

func (r *reducer) step() error {
	r.didChange = false
	r.walk(r.file, func(v interface{}) bool {
		if r.didChange {
			return false
		}
		return r.reduceNode(v)
	})
	if r.didChange {
		return nil
	}
	if err := r.writeSource(); err != nil {
		return err
	}
	return errNoChange
}

func findFunc(files []*ast.File, name string) (*ast.File, *ast.FuncDecl) {
	for _, file := range files {
		for _, decl := range file.Decls {
			funcDecl, ok := decl.(*ast.FuncDecl)
			if ok && funcDecl.Name.Name == name {
				return file, funcDecl
			}
		}
	}
	return nil, nil
}

func runTest(impPath string) error {
	cmd := exec.Command("go", "test", impPath, "-run", "^"+testName+"$")
	out, err := cmd.CombinedOutput()
	if err == nil {
		return nil
	}
	if strings.HasPrefix(err.Error(), "exit status") {
		return errors.New(string(out))
	}
	return err
}
<commit_msg>Don't set r.Info twice<commit_after>// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/importer"
	"go/printer"
	"go/types"
	"html/template"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/loader"
)

const (
	testFile = "goreduce_test.go"
	testName = "TestReduce"
)

var testTmpl = template.Must(template.New("test").Parse(`` +
	`package {{ .Pkg }}

import "testing"

func {{ .TestName }}(t *testing.T) {
	{{ .Func }}()
}
`))

func emptyFile(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return err
	}
	_, err := f.Seek(0, 0)
	return err
}

type reducer struct {
	loader.Config
	*loader.PackageInfo

	impPath  string
	matchRe  *regexp.Regexp
	file     *ast.File
	funcDecl *ast.FuncDecl
	srcFile  *os.File

	*types.Info

	wd        string
	didChange bool
	stmt      *ast.Stmt
	expr      *ast.Expr
}

func reduce(impPath, funcName, matchStr string) error {
	r := &reducer{impPath: impPath}
	var err error
	if r.wd, err = os.Getwd(); err != nil {
		return err
	}
	// otherwise go/types prints errors to stderr
	// need to panic to stop typechecking
	r.TypeChecker.Error = func(error) { panic(nil) }
	r.TypeChecker.Importer = importer.Default()
	if r.matchRe, err = regexp.Compile(matchStr); err != nil {
		return err
	}
	if _, err := r.FromArgs([]string{impPath}, false); err != nil {
		return err
	}
	prog, err := r.Load()
	if err != nil {
		return err
	}
	pkgInfos := prog.InitialPackages()
	if len(pkgInfos) != 1 {
		return fmt.Errorf("expected 1 package, got %d", len(pkgInfos))
	}
	r.PackageInfo = pkgInfos[0]
	r.Info = &r.PackageInfo.Info
	pkg := r.PackageInfo.Pkg
	if pkg.Scope().Lookup(funcName) == nil {
		return fmt.Errorf("top-level func %s does not exist", funcName)
	}
	r.file, r.funcDecl = findFunc(r.PackageInfo.Files, funcName)
	fname := r.Fset.Position(r.file.Pos()).Filename
	testFilePath := filepath.Join(filepath.Dir(fname), testFile)
	tf, err := os.Create(testFilePath)
	if err != nil {
		return err
	}
	defer func() {
		tf.Close()
		os.Remove(testFilePath)
	}()
	// Check that it compiles and the output matches before we apply
	// any changes
	if err := testTmpl.Execute(tf, struct {
		Pkg, TestName, Func string
	}{
		Pkg:      pkg.Name(),
		TestName: testName,
		Func:     funcName,
	}); err != nil {
		return err
	}
	if err := r.checkTest(); err != nil {
		return err
	}
	if r.srcFile, err = os.Create(fname); err != nil {
		return err
	}
	for err == nil {
		if err = r.step(); err == errNoChange {
			err = nil
			break // we're done
		}
	}
	if err2 := r.srcFile.Close(); err == nil && err2 != nil {
		return err2
	}
	return err
}

func (r *reducer) logChange(node ast.Node, format string, a ...interface{}) {
	if *verbose {
		pos := r.Fset.Position(node.Pos())
		rpath, err := filepath.Rel(r.wd, pos.Filename)
		if err != nil {
			panic(err)
		}
		fmt.Fprintf(os.Stderr, "%s:%d: %s\n", rpath, pos.Line,
			fmt.Sprintf(format, a...))
	}
}

func (r *reducer) checkTest() error {
	err := runTest(r.impPath)
	if err == nil {
		return fmt.Errorf("expected an error to occur")
	}
	if s := err.Error(); !r.matchRe.MatchString(s) {
		return fmt.Errorf("error does not match:\n%s", s)
	}
	return nil
}

var errNoChange = fmt.Errorf("no reduction to apply")

func (r *reducer) writeSource() error {
	if err := emptyFile(r.srcFile); err != nil {
		return err
	}
	return printer.Fprint(r.srcFile, r.Fset, r.file)
}

func (r *reducer) okChange() bool {
	if r.didChange {
		return false
	}
	// go/types catches most compile errors before writing
	// to disk and running the go tool. Since quite a lot of
	// changes are nonsensical, this is often a big win.
	if _, err := r.TypeChecker.Check(r.impPath, r.Fset, r.Files, r.Info); err != nil {
		terr, ok := err.(types.Error)
		if ok && terr.Soft && r.shouldRetry(terr) {
			if err := r.writeSource(); err != nil {
				return false
			}
			return r.okChange()
		}
		return false
	}
	if err := r.writeSource(); err != nil {
		return false
	}
	if err := r.checkTest(); err != nil {
		return false
	}
	// Reduction worked
	r.didChange = true
	return true
}

var (
	importNotUsed = regexp.MustCompile(`"(.*)" imported but not used`)
)

func (r *reducer) shouldRetry(terr types.Error) bool {
	if sm := importNotUsed.FindStringSubmatch(terr.Msg); sm != nil {
		name, path := "", sm[1]
		for _, imp := range r.file.Imports {
			if imp.Name != nil && strings.Trim(imp.Path.Value, `"`) == path {
				name = imp.Name.Name
				break
			}
		}
		return astutil.DeleteNamedImport(r.Fset, r.file, name, path)
	}
	return false
}

func (r *reducer) step() error {
	r.didChange = false
	r.walk(r.file, func(v interface{}) bool {
		if r.didChange {
			return false
		}
		return r.reduceNode(v)
	})
	if r.didChange {
		return nil
	}
	if err := r.writeSource(); err != nil {
		return err
	}
	return errNoChange
}

func findFunc(files []*ast.File, name string) (*ast.File, *ast.FuncDecl) {
	for _, file := range files {
		for _, decl := range file.Decls {
			funcDecl, ok := decl.(*ast.FuncDecl)
			if ok && funcDecl.Name.Name == name {
				return file, funcDecl
			}
		}
	}
	return nil, nil
}

func runTest(impPath string) error {
	cmd := exec.Command("go", "test", impPath, "-run", "^"+testName+"$")
	out, err := cmd.CombinedOutput()
	if err == nil {
		return nil
	}
	if strings.HasPrefix(err.Error(), "exit status") {
		return errors.New(string(out))
	}
	return err
}
<|endoftext|>


<commit_before>// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/importer"
	"go/printer"
	"go/types"
	"html/template"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/loader"

	"github.com/kisielk/gotool"
)

const (
	testFile = "goreduce_test.go"
	testName = "TestReduce"
)

var testTmpl = template.Must(template.New("test").Parse(`` +
	`package {{ .Pkg }}

import "testing"

func {{ .TestName }}(t *testing.T) {
	{{ .Func }}()
}
`))

func emptyFile(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return err
	}
	_, err := f.Seek(0, 0)
	return err
}

type reducer struct {
	loader.Config
	*loader.PackageInfo

	impPath  string
	matchRe  *regexp.Regexp
	file     *ast.File
	funcDecl *ast.FuncDecl
	srcFile  *os.File

	*types.Info

	didChange bool
	stmt      *ast.Stmt
	expr      *ast.Expr
}

func reduce(impPath, funcName, matchStr string) error {
	r := &reducer{impPath: impPath}
	// otherwise go/types prints errors to stderr
	// need to panic to stop typechecking
	r.TypeChecker.Error = func(error) { panic(nil) }
	r.TypeChecker.Importer = importer.Default()
	var err error
	if r.matchRe, err = regexp.Compile(matchStr); err != nil {
		return err
	}
	paths := gotool.ImportPaths([]string{impPath})
	if _, err := r.FromArgs(paths, false); err != nil {
		return err
	}
	prog, err := r.Load()
	if err != nil {
		return err
	}
	pkgInfos := prog.InitialPackages()
	if len(pkgInfos) != 1 {
		return fmt.Errorf("expected 1 package, got %d", len(pkgInfos))
	}
	r.PackageInfo = pkgInfos[0]
	r.Info = &r.PackageInfo.Info
	pkg := r.PackageInfo.Pkg
	if pkg.Scope().Lookup(funcName) == nil {
		return fmt.Errorf("top-level func %s does not exist", funcName)
	}
	r.file, r.funcDecl = findFunc(r.PackageInfo.Files, funcName)
	fname := r.Fset.Position(r.file.Pos()).Filename
	testFilePath := filepath.Join(filepath.Dir(fname), testFile)
	tf, err := os.Create(testFilePath)
	if err != nil {
		return err
	}
	defer func() {
		tf.Close()
		os.Remove(testFilePath)
	}()
	// Check that it compiles and the output matches before we apply
	// any changes
	if err := testTmpl.Execute(tf, struct {
		Pkg, TestName, Func string
	}{
		Pkg:      pkg.Name(),
		TestName: testName,
		Func:     funcName,
	}); err != nil {
		return err
	}
	if err := r.checkTest(); err != nil {
		return err
	}
	if r.srcFile, err = os.Create(fname); err != nil {
		return err
	}
	for err == nil {
		if err = r.step(); err == errNoChange {
			err = nil
			break // we're done
		}
	}
	if err2 := r.srcFile.Close(); err == nil && err2 != nil {
		return err2
	}
	return err
}

func (r *reducer) checkTest() error {
	err := runTest(r.impPath)
	if err == nil {
		return fmt.Errorf("expected an error to occur")
	}
	if s := err.Error(); !r.matchRe.MatchString(s) {
		return fmt.Errorf("error does not match:\n%s", s)
	}
	return nil
}

var errNoChange = fmt.Errorf("no reduction to apply")

func (r *reducer) writeSource() error {
	if err := emptyFile(r.srcFile); err != nil {
		return err
	}
	return printer.Fprint(r.srcFile, r.Fset, r.file)
}

func (r *reducer) okChange() bool {
	if r.didChange {
		return false
	}
	// go/types catches most compile errors before writing
	// to disk and running the go tool. Since quite a lot of
	// changes are nonsensical, this is often a big win.
	*r.Info = types.Info{
		Types: make(map[ast.Expr]types.TypeAndValue),
		Defs:  make(map[*ast.Ident]types.Object),
		Uses:  make(map[*ast.Ident]types.Object),
	}
	if _, err := r.TypeChecker.Check(r.impPath, r.Fset, r.Files, r.Info); err != nil {
		terr, ok := err.(types.Error)
		if ok && terr.Soft && r.shouldRetry(terr) {
			if err := r.writeSource(); err != nil {
				return false
			}
			return r.okChange()
		}
		return false
	}
	if err := r.writeSource(); err != nil {
		return false
	}
	if err := r.checkTest(); err != nil {
		return false
	}
	// Reduction worked
	r.didChange = true
	return true
}

var (
	importNotUsed = regexp.MustCompile(`"(.*)" imported but not used`)
)

func (r *reducer) shouldRetry(terr types.Error) bool {
	if sm := importNotUsed.FindStringSubmatch(terr.Msg); sm != nil {
		name, path := "", sm[1]
		for _, imp := range r.file.Imports {
			if imp.Name != nil && strings.Trim(imp.Path.Value, `"`) == path {
				name = imp.Name.Name
				break
			}
		}
		return astutil.DeleteNamedImport(r.Fset, r.file, name, path)
	}
	return false
}

func (r *reducer) step() error {
	r.didChange = false
	r.walk(r.file, func(v interface{}) bool {
		if r.didChange {
			return false
		}
		r.reduceNode(v)
		return true
	})
	if r.didChange {
		return nil
	}
	if err := r.writeSource(); err != nil {
		return err
	}
	return errNoChange
}

func findFunc(files []*ast.File, name string) (*ast.File, *ast.FuncDecl) {
	for _, file := range files {
		for _, decl := range file.Decls {
			funcDecl, ok := decl.(*ast.FuncDecl)
			if ok && funcDecl.Name.Name == name {
				return file, funcDecl
			}
		}
	}
	return nil, nil
}

func runTest(impPath string) error {
	cmd := exec.Command("go", "test", impPath, "-run", "^"+testName+"$")
	out, err := cmd.CombinedOutput()
	if err == nil {
		return nil
	}
	if strings.HasPrefix(err.Error(), "exit status") {
		return errors.New(strings.TrimSpace(string(out)))
	}
	return err
}
<commit_msg>Always use the same types.Info maps<commit_after>// Copyright (c) 2017, Daniel Martí <mvdan@mvdan.cc>
// See LICENSE for licensing information

package main

import (
	"errors"
	"fmt"
	"go/ast"
	"go/importer"
	"go/printer"
	"go/types"
	"html/template"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"golang.org/x/tools/go/ast/astutil"
	"golang.org/x/tools/go/loader"

	"github.com/kisielk/gotool"
)

const (
	testFile = "goreduce_test.go"
	testName = "TestReduce"
)

var testTmpl = template.Must(template.New("test").Parse(`` +
	`package {{ .Pkg }}

import "testing"

func {{ .TestName }}(t *testing.T) {
	{{ .Func }}()
}
`))

func emptyFile(f *os.File) error {
	if err := f.Truncate(0); err != nil {
		return err
	}
	_, err := f.Seek(0, 0)
	return err
}

type reducer struct {
	loader.Config
	*loader.PackageInfo

	impPath  string
	matchRe  *regexp.Regexp
	file     *ast.File
	funcDecl *ast.FuncDecl
	srcFile  *os.File

	*types.Info

	didChange bool
	stmt      *ast.Stmt
	expr      *ast.Expr
}

func reduce(impPath, funcName, matchStr string) error {
	r := &reducer{impPath: impPath}
	// otherwise go/types prints errors to stderr
	// need to panic to stop typechecking
	r.TypeChecker.Error = func(error) { panic(nil) }
	r.TypeChecker.Importer = importer.Default()
	r.Info = new(types.Info)
	r.emptyInfo()
	var err error
	if r.matchRe, err = regexp.Compile(matchStr); err != nil {
		return err
	}
	paths := gotool.ImportPaths([]string{impPath})
	if _, err := r.FromArgs(paths, false); err != nil {
		return err
	}
	prog, err := r.Load()
	if err != nil {
		return err
	}
	pkgInfos := prog.InitialPackages()
	if len(pkgInfos) != 1 {
		return fmt.Errorf("expected 1 package, got %d", len(pkgInfos))
	}
	r.PackageInfo = pkgInfos[0]
	r.Info = &r.PackageInfo.Info
	pkg := r.PackageInfo.Pkg
	if pkg.Scope().Lookup(funcName) == nil {
		return fmt.Errorf("top-level func %s does not exist", funcName)
	}
	r.file, r.funcDecl = findFunc(r.PackageInfo.Files, funcName)
	fname := r.Fset.Position(r.file.Pos()).Filename
	testFilePath := filepath.Join(filepath.Dir(fname), testFile)
	tf, err := os.Create(testFilePath)
	if err != nil {
		return err
	}
	defer func() {
		tf.Close()
		os.Remove(testFilePath)
	}()
	// Check that it compiles and the output matches before we apply
	// any changes
	if err := testTmpl.Execute(tf, struct {
		Pkg, TestName, Func string
	}{
		Pkg:      pkg.Name(),
		TestName: testName,
		Func:     funcName,
	}); err != nil {
		return err
	}
	if err := r.checkTest(); err != nil {
		return err
	}
	if r.srcFile, err = os.Create(fname); err != nil {
		return err
	}
	for err == nil {
		if err = r.step(); err == errNoChange {
			err = nil
			break // we're done
		}
	}
	if err2 := r.srcFile.Close(); err == nil && err2 != nil {
		return err2
	}
	return err
}

func (r *reducer) checkTest() error {
	err := runTest(r.impPath)
	if err == nil {
		return fmt.Errorf("expected an error to occur")
	}
	if s := err.Error(); !r.matchRe.MatchString(s) {
		return fmt.Errorf("error does not match:\n%s", s)
	}
	return nil
}

var errNoChange = fmt.Errorf("no reduction to apply")

func (r *reducer) writeSource() error {
	if err := emptyFile(r.srcFile); err != nil {
		return err
	}
	return printer.Fprint(r.srcFile, r.Fset, r.file)
}

func (r *reducer) emptyInfo() {
	r.Info.Types = make(map[ast.Expr]types.TypeAndValue)
	r.Info.Defs = make(map[*ast.Ident]types.Object)
	r.Info.Uses = make(map[*ast.Ident]types.Object)
}

func (r *reducer) okChange() bool {
	if r.didChange {
		return false
	}
	// go/types catches most compile errors before writing
	// to disk and running the go tool. Since quite a lot of
	// changes are nonsensical, this is often a big win.
	if _, err := r.TypeChecker.Check(r.impPath, r.Fset, r.Files, r.Info); err != nil {
		terr, ok := err.(types.Error)
		if ok && terr.Soft && r.shouldRetry(terr) {
			if err := r.writeSource(); err != nil {
				return false
			}
			return r.okChange()
		}
		return false
	}
	if err := r.writeSource(); err != nil {
		return false
	}
	if err := r.checkTest(); err != nil {
		return false
	}
	// Reduction worked
	r.didChange = true
	return true
}

var (
	importNotUsed = regexp.MustCompile(`"(.*)" imported but not used`)
)

func (r *reducer) shouldRetry(terr types.Error) bool {
	if sm := importNotUsed.FindStringSubmatch(terr.Msg); sm != nil {
		name, path := "", sm[1]
		for _, imp := range r.file.Imports {
			if imp.Name != nil && strings.Trim(imp.Path.Value, `"`) == path {
				name = imp.Name.Name
				break
			}
		}
		return astutil.DeleteNamedImport(r.Fset, r.file, name, path)
	}
	return false
}

func (r *reducer) step() error {
	r.didChange = false
	r.walk(r.file, func(v interface{}) bool {
		if r.didChange {
			return false
		}
		r.reduceNode(v)
		return true
	})
	if r.didChange {
		return nil
	}
	if err := r.writeSource(); err != nil {
		return err
	}
	return errNoChange
}

func findFunc(files []*ast.File, name string) (*ast.File, *ast.FuncDecl) {
	for _, file := range files {
		for _, decl := range file.Decls {
			funcDecl, ok := decl.(*ast.FuncDecl)
			if ok && funcDecl.Name.Name == name {
				return file, funcDecl
			}
		}
	}
	return nil, nil
}

func runTest(impPath string) error {
	cmd := exec.Command("go", "test", impPath, "-run", "^"+testName+"$")
	out, err := cmd.CombinedOutput()
	if err == nil {
		return nil
	}
	if strings.HasPrefix(err.Error(), "exit status") {
		return errors.New(strings.TrimSpace(string(out)))
	}
	return err
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"io"

	"golang.org/x/net/html"
)

type renderer struct {
	elStack []string
	t       *html.Tokenizer
	b       bytes.Buffer
}

func render(r io.Reader) (bytes.Buffer, error) {
	t := html.NewTokenizer(r)
	re := renderer{t: t}
	err := re.parse(r)
	if err != nil {
		return re.b, err
	}
	return re.b, nil
}

func (re *renderer) parse(io.Reader) (err error) {
	for {
		switch re.t.Next() {
		case html.ErrorToken:
			err = re.t.Err()
		case html.StartTagToken:
			re.elStack = append(re.elStack, re.t.Token().Data) // push element
			fallthrough
		case html.SelfClosingTagToken:
			err = re.handleTags()
		case html.TextToken:
			err = re.handleText()
		case html.EndTagToken:
			re.elStack = re.elStack[:len(re.elStack)-1] // pop element
		}
		if err == io.EOF {
			return nil
		} else if err != nil {
			return err
		}
	}
}

func (re *renderer) handleText() error {
	// Skip style tags
	if len(re.elStack) > 0 && re.elStack[len(re.elStack)-1] == "style" {
		return nil
	}
	_, err := re.b.Write(re.t.Text())
	return err
}

func (re *renderer) handleTags() error {
	token := re.t.Token()
	// Display alt text in place of image
	if token.Data == "img" {
		for _, a := range token.Attr {
			if a.Key == "alt" {
				_, err := re.b.Write([]byte(fmt.Sprintf("\nImage alt text: %s\n", a.Val)))
				if err != nil {
					return err
				}
			}
		}
	}

	return nil
}
<commit_msg>Add comments to render.go<commit_after>package main

import (
	"bytes"
	"fmt"
	"io"

	"golang.org/x/net/html"
)

type renderer struct {
	elStack []string
	t       *html.Tokenizer
	b       bytes.Buffer
}

// render takes in html content via an io.Reader and returns a buffer
// containing only plain text.
func render(r io.Reader) (bytes.Buffer, error) {
	t := html.NewTokenizer(r)
	re := renderer{t: t}
	err := re.parse(r)
	if err != nil {
		return re.b, err
	}
	return re.b, nil
}

// parse walks an html document and appends text elements to a buffer.
func (re *renderer) parse(io.Reader) (err error) {
	for {
		switch re.t.Next() {
		case html.ErrorToken:
			err = re.t.Err()
		case html.StartTagToken:
			re.elStack = append(re.elStack, re.t.Token().Data) // push element
			fallthrough
		case html.SelfClosingTagToken:
			err = re.handleTags()
		case html.TextToken:
			err = re.handleText()
		case html.EndTagToken:
			re.elStack = re.elStack[:len(re.elStack)-1] // pop element
		}
		if err == io.EOF {
			return nil
		} else if err != nil {
			return err
		}
	}
}

// handleText appends text elements to the renderer buffer. It filters elements
// that should not be displayed as text (e.g. style blocks).
func (re *renderer) handleText() error {
	// Skip style tags
	if len(re.elStack) > 0 && re.elStack[len(re.elStack)-1] == "style" {
		return nil
	}
	_, err := re.b.Write(re.t.Text())
	return err
}

// handleTags appends text representations of non-text elements (e.g. image alt
// tags) to the renderer buffer.
func (re *renderer) handleTags() error {
	token := re.t.Token()
	// Display alt text in place of image
	if token.Data == "img" {
		for _, a := range token.Attr {
			if a.Key == "alt" {
				_, err := re.b.Write([]byte(fmt.Sprintf("\nImage alt text: %s\n", a.Val)))
				if err != nil {
					return err
				}
			}
		}
	}

	return nil
}
<|endoftext|>


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

package macaron

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Unknwon/macaron/bpool"
)

const (
	ContentType    = "Content-Type"
	ContentLength  = "Content-Length"
	ContentBinary  = "application/octet-stream"
	ContentJSON    = "application/json"
	ContentHTML    = "text/html"
	ContentXHTML   = "application/xhtml+xml"
	ContentXML     = "text/xml"
	defaultCharset = "UTF-8"
)

var (
	// Provides a temporary buffer to execute templates into and catch errors.
	bufpool *bpool.BufferPool

	// Included helper functions for use when rendering html
	helperFuncs = template.FuncMap{
		"yield": func() (string, error) {
			return "", fmt.Errorf("yield called with no layout defined")
		},
		"current": func() (string, error) {
			return "", nil
		},
	}
)

type (
	// Delims represents a set of Left and Right delimiters for HTML template rendering
	Delims struct {
		// Left delimiter, defaults to {{
		Left string
		// Right delimiter, defaults to }}
		Right string
	}

	// RenderOptions represents a struct for specifying configuration options for the Render middleware.
	RenderOptions struct {
		// Directory to load templates. Default is "templates".
		Directory string
		// Layout template name. Will not render a layout if "". Default is to "".
		Layout string
		// Extensions to parse template files from. Defaults are [".tmpl", ".html"].
		Extensions []string
		// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
		Funcs []template.FuncMap
		// Delims sets the action delimiters to the specified strings in the Delims struct.
		Delims Delims
		// Appends the given charset to the Content-Type header. Default is "UTF-8".
		Charset string
		// Outputs human readable JSON.
		IndentJSON bool
		// Outputs human readable XML.
		IndentXML bool
		// Prefixes the JSON output with the given bytes.
		PrefixJSON []byte
		// Prefixes the XML output with the given bytes.
		PrefixXML []byte
		// Allows changing of output to XHTML instead of HTML. Default is "text/html"
		HTMLContentType string
	}

	// HTMLOptions is a struct for overriding some rendering Options for specific HTML call
	HTMLOptions struct {
		// Layout template name. Overrides Options.Layout.
		Layout string
	}
)

type Render interface {
	http.ResponseWriter
	RW() http.ResponseWriter

	JSON(int, interface{})
	JSONString(interface{}) (string, error)
	RawData(int, []byte)
	RenderData(int, []byte)
	HTML(int, string, interface{}, ...HTMLOptions)
	HTMLSet(int, string, string, interface{}, ...HTMLOptions)
	HTMLString(string, interface{}, ...HTMLOptions) (string, error)
	HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
	XML(int, interface{})
	Error(int, ...string)
	Status(int)
	Redirect(string, ...int)
}

func prepareOptions(options []RenderOptions) RenderOptions {
	var opt RenderOptions
	if len(options) > 0 {
		opt = options[0]
	}

	// Defaults.
	if len(opt.Directory) == 0 {
		opt.Directory = "templates"
	}
	if len(opt.Extensions) == 0 {
		opt.Extensions = []string{".tmpl", ".html"}
	}
	if len(opt.HTMLContentType) == 0 {
		opt.HTMLContentType = ContentHTML
	}

	return opt
}

func prepareCharset(charset string) string {
	if len(charset) != 0 {
		return "; charset=" + charset
	}

	return "; charset=" + defaultCharset
}

func getExt(s string) string {
	if strings.Index(s, ".") == -1 {
		return ""
	}
	return "." + strings.Join(strings.Split(s, ".")[1:], ".")
}

func compile(options RenderOptions) *template.Template {
	dir := options.Directory
	t := template.New(dir)
	t.Delims(options.Delims.Left, options.Delims.Right)
	// Parse an initial template in case we don't have any.
	template.Must(t.Parse("Macaron"))

	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		r, err := filepath.Rel(dir, path)
		if err != nil {
			return err
		}

		ext := getExt(r)

		for _, extension := range options.Extensions {
			if ext == extension {

				buf, err := ioutil.ReadFile(path)
				if err != nil {
					panic(err)
				}

				name := (r[0 : len(r)-len(ext)])
				tmpl := t.New(filepath.ToSlash(name))

				// add our funcmaps
				for _, funcs := range options.Funcs {
					tmpl.Funcs(funcs)
				}

				// Bomb out if parse fails. We don't want any silent server starts.
				template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
				break
			}
		}

		return nil
	})

	return t
}

// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
// An single variadic macaron.RenderOptions struct can be optionally provided to configure
// HTML rendering. The default directory for templates is "templates" and the default
// file extension is ".tmpl" and ".html".
//
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
// MACARON_ENV environment variable to "production".
func Renderer(options ...RenderOptions) Handler {
	opt := prepareOptions(options)
	cs := prepareCharset(opt.Charset)
	t := compile(opt)
	bufpool = bpool.NewBufferPool(64)
	return func(ctx *Context, rw http.ResponseWriter, req *http.Request) {
		var tc *template.Template
		if Env == DEV {
			// recompile for easy development
			tc = compile(opt)
		} else {
			// use a clone of the initial template
			tc, _ = t.Clone()
		}
		r := &TplRender{
			ResponseWriter:  rw,
			Req:             req,
			t:               tc,
			Opt:             &opt,
			CompiledCharset: cs,
		}
		ctx.Data["TmplLoadTimes"] = func() string {
			if r.startTime.IsZero() {
				return ""
			}
			return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
		}

		ctx.Render = r
		ctx.MapTo(r, (*Render)(nil))
	}
}

type TplRender struct {
	http.ResponseWriter
	Req             *http.Request
	t               *template.Template
	Opt             *RenderOptions
	CompiledCharset string

	startTime time.Time
}

func (r *TplRender) RW() http.ResponseWriter {
	return r.ResponseWriter
}

func (r *TplRender) JSON(status int, v interface{}) {
	var result []byte
	var err error
	if r.Opt.IndentJSON {
		result, err = json.MarshalIndent(v, "", "  ")
	} else {
		result, err = json.Marshal(v)
	}
	if err != nil {
		http.Error(r, err.Error(), 500)
		return
	}

	// json rendered fine, write out the result
	r.Header().Set(ContentType, ContentJSON+r.CompiledCharset)
	r.WriteHeader(status)
	if len(r.Opt.PrefixJSON) > 0 {
		r.Write(r.Opt.PrefixJSON)
	}
	r.Write(result)
}

func (r *TplRender) JSONString(v interface{}) (string, error) {
	var result []byte
	var err error
	if r.Opt.IndentJSON {
		result, err = json.MarshalIndent(v, "", "  ")
	} else {
		result, err = json.Marshal(v)
	}
	if err != nil {
		return "", err
	}
	return string(result), nil
}

func (r *TplRender) XML(status int, v interface{}) {
	var result []byte
	var err error
	if r.Opt.IndentXML {
		result, err = xml.MarshalIndent(v, "", "  ")
	} else {
		result, err = xml.Marshal(v)
	}
	if err != nil {
		http.Error(r, err.Error(), 500)
		return
	}

	// XML rendered fine, write out the result
	r.Header().Set(ContentType, ContentXML+r.CompiledCharset)
	r.WriteHeader(status)
	if len(r.Opt.PrefixXML) > 0 {
		r.Write(r.Opt.PrefixXML)
	}
	r.Write(result)
}

func (r *TplRender) data(status int, contentType string, v []byte) {
	if r.Header().Get(ContentType) == "" {
		r.Header().Set(ContentType, contentType)
	}
	r.WriteHeader(status)
	r.Write(v)
}

func (r *TplRender) RawData(status int, v []byte) {
	r.data(status, ContentBinary, v)
}

func (r *TplRender) RenderData(status int, v []byte) {
	r.data(status, ContentHTML, v)
}

func (r *TplRender) renderBytes(name string, binding interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
	opt := r.prepareHTMLOptions(htmlOpt)

	if len(opt.Layout) > 0 {
		r.addYield(name, binding)
		name = opt.Layout
	}

	out, err := r.execute(name, binding)
	if err != nil {
		return nil, err
	}

	return out, nil
}

func (r *TplRender) HTML(status int, name string, binding interface{}, htmlOpt ...HTMLOptions) {
	r.startTime = time.Now()

	out, err := r.renderBytes(name, binding, htmlOpt...)
	if err != nil {
		http.Error(r, err.Error(), http.StatusInternalServerError)
		return
	}

	r.Header().Set(ContentType, r.Opt.HTMLContentType+r.CompiledCharset)
	r.WriteHeader(status)
	io.Copy(r, out)
	bufpool.Put(out)
}

func (r *TplRender) HTMLSet(status int, setName, name string, binding interface{}, htmlOpt ...HTMLOptions) {
	r.startTime = time.Now()

	out, err := r.renderBytes(name, binding, htmlOpt...)
	if err != nil {
		http.Error(r, err.Error(), http.StatusInternalServerError)
		return
	}

	r.Header().Set(ContentType, r.Opt.HTMLContentType+r.CompiledCharset)
	r.WriteHeader(status)
	io.Copy(r, out)
	bufpool.Put(out)
}

func (r *TplRender) HTMLString(name string, binding interface{}, htmlOpt ...HTMLOptions) (string, error) {
	if out, err := r.renderBytes(name, binding, htmlOpt...); err != nil {
		return "", err
	} else {
		return out.String(), nil
	}
}

func (r *TplRender) HTMLSetString(setName, name string, binding interface{}, htmlOpt ...HTMLOptions) (string, error) {
	if out, err := r.renderBytes(name, binding, htmlOpt...); err != nil {
		return "", err
	} else {
		return out.String(), nil
	}
}

// Error writes the given HTTP status to the current ResponseWriter
func (r *TplRender) Error(status int, message ...string) {
	r.WriteHeader(status)
	if len(message) > 0 {
		r.Write([]byte(message[0]))
	}
}

func (r *TplRender) Status(status int) {
	r.WriteHeader(status)
}

func (r *TplRender) Redirect(location string, status ...int) {
	code := http.StatusFound
	if len(status) == 1 {
		code = status[0]
	}

	http.Redirect(r, r.Req, location, code)
}

func (r *TplRender) execute(name string, binding interface{}) (*bytes.Buffer, error) {
	buf := bufpool.Get()
	return buf, r.t.ExecuteTemplate(buf, name, binding)
}

func (r *TplRender) addYield(name string, binding interface{}) {
	funcs := template.FuncMap{
		"yield": func() (template.HTML, error) {
			buf, err := r.execute(name, binding)
			// return safe html here since we are rendering our own template
			return template.HTML(buf.String()), err
		},
		"current": func() (string, error) {
			return name, nil
		},
	}
	r.t.Funcs(funcs)
}

func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
	if len(htmlOpt) > 0 {
		return htmlOpt[0]
	}

	return HTMLOptions{
		Layout: r.Opt.Layout,
	}
}
<commit_msg>bring back API<commit_after>// Copyright 2013 Martini Authors
// Copyright 2014 Unknwon
//
// Licensed under the Apache License, Version 2.0 (the "License"): you may
// not use this file except in compliance with the License. You may obtain
// a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.

package macaron

import (
	"bytes"
	"encoding/json"
	"encoding/xml"
	"fmt"
	"html/template"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"github.com/Unknwon/macaron/bpool"
)

const (
	ContentType    = "Content-Type"
	ContentLength  = "Content-Length"
	ContentBinary  = "application/octet-stream"
	ContentJSON    = "application/json"
	ContentHTML    = "text/html"
	ContentXHTML   = "application/xhtml+xml"
	ContentXML     = "text/xml"
	defaultCharset = "UTF-8"
)

var (
	// Provides a temporary buffer to execute templates into and catch errors.
	bufpool *bpool.BufferPool

	// Included helper functions for use when rendering html
	helperFuncs = template.FuncMap{
		"yield": func() (string, error) {
			return "", fmt.Errorf("yield called with no layout defined")
		},
		"current": func() (string, error) {
			return "", nil
		},
	}
)

type (
	// Delims represents a set of Left and Right delimiters for HTML template rendering
	Delims struct {
		// Left delimiter, defaults to {{
		Left string
		// Right delimiter, defaults to }}
		Right string
	}

	// RenderOptions represents a struct for specifying configuration options for the Render middleware.
	RenderOptions struct {
		// Directory to load templates. Default is "templates".
		Directory string
		// Layout template name. Will not render a layout if "". Default is to "".
		Layout string
		// Extensions to parse template files from. Defaults are [".tmpl", ".html"].
		Extensions []string
		// Funcs is a slice of FuncMaps to apply to the template upon compilation. This is useful for helper functions. Default is [].
		Funcs []template.FuncMap
		// Delims sets the action delimiters to the specified strings in the Delims struct.
		Delims Delims
		// Appends the given charset to the Content-Type header. Default is "UTF-8".
		Charset string
		// Outputs human readable JSON.
		IndentJSON bool
		// Outputs human readable XML.
		IndentXML bool
		// Prefixes the JSON output with the given bytes.
		PrefixJSON []byte
		// Prefixes the XML output with the given bytes.
		PrefixXML []byte
		// Allows changing of output to XHTML instead of HTML. Default is "text/html"
		HTMLContentType string
	}

	// HTMLOptions is a struct for overriding some rendering Options for specific HTML call
	HTMLOptions struct {
		// Layout template name. Overrides Options.Layout.
		Layout string
	}
)

type Render interface {
	http.ResponseWriter
	RW() http.ResponseWriter

	JSON(int, interface{})
	JSONString(interface{}) (string, error)
	RawData(int, []byte)
	RenderData(int, []byte)
	HTML(int, string, interface{}, ...HTMLOptions)
	HTMLSet(int, string, string, interface{}, ...HTMLOptions)
	HTMLString(string, interface{}, ...HTMLOptions) (string, error)
	HTMLSetString(string, string, interface{}, ...HTMLOptions) (string, error)
	XML(int, interface{})
	Error(int, ...string)
	Status(int)
	Redirect(string, ...int)
	SetTemplatePath(string, string)
}

func prepareOptions(options []RenderOptions) RenderOptions {
	var opt RenderOptions
	if len(options) > 0 {
		opt = options[0]
	}

	// Defaults.
	if len(opt.Directory) == 0 {
		opt.Directory = "templates"
	}
	if len(opt.Extensions) == 0 {
		opt.Extensions = []string{".tmpl", ".html"}
	}
	if len(opt.HTMLContentType) == 0 {
		opt.HTMLContentType = ContentHTML
	}

	return opt
}

func prepareCharset(charset string) string {
	if len(charset) != 0 {
		return "; charset=" + charset
	}

	return "; charset=" + defaultCharset
}

func getExt(s string) string {
	if strings.Index(s, ".") == -1 {
		return ""
	}
	return "." + strings.Join(strings.Split(s, ".")[1:], ".")
}

func compile(options RenderOptions) *template.Template {
	dir := options.Directory
	t := template.New(dir)
	t.Delims(options.Delims.Left, options.Delims.Right)
	// Parse an initial template in case we don't have any.
	template.Must(t.Parse("Macaron"))

	filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		r, err := filepath.Rel(dir, path)
		if err != nil {
			return err
		}

		ext := getExt(r)

		for _, extension := range options.Extensions {
			if ext == extension {

				buf, err := ioutil.ReadFile(path)
				if err != nil {
					panic(err)
				}

				name := (r[0 : len(r)-len(ext)])
				tmpl := t.New(filepath.ToSlash(name))

				// add our funcmaps
				for _, funcs := range options.Funcs {
					tmpl.Funcs(funcs)
				}

				// Bomb out if parse fails. We don't want any silent server starts.
				template.Must(tmpl.Funcs(helperFuncs).Parse(string(buf)))
				break
			}
		}

		return nil
	})

	return t
}

// Renderer is a Middleware that maps a macaron.Render service into the Macaron handler chain.
// An single variadic macaron.RenderOptions struct can be optionally provided to configure
// HTML rendering. The default directory for templates is "templates" and the default
// file extension is ".tmpl" and ".html".
//
// If MACARON_ENV is set to "" or "development" then templates will be recompiled on every request. For more performance, set the
// MACARON_ENV environment variable to "production".
func Renderer(options ...RenderOptions) Handler {
	opt := prepareOptions(options)
	cs := prepareCharset(opt.Charset)
	t := compile(opt)
	bufpool = bpool.NewBufferPool(64)
	return func(ctx *Context, rw http.ResponseWriter, req *http.Request) {
		var tc *template.Template
		if Env == DEV {
			// recompile for easy development
			tc = compile(opt)
		} else {
			// use a clone of the initial template
			tc, _ = t.Clone()
		}
		r := &TplRender{
			ResponseWriter:  rw,
			Req:             req,
			t:               tc,
			Opt:             &opt,
			CompiledCharset: cs,
		}
		ctx.Data["TmplLoadTimes"] = func() string {
			if r.startTime.IsZero() {
				return ""
			}
			return fmt.Sprint(time.Since(r.startTime).Nanoseconds()/1e6) + "ms"
		}

		ctx.Render = r
		ctx.MapTo(r, (*Render)(nil))
	}
}

type TplRender struct {
	http.ResponseWriter
	Req             *http.Request
	t               *template.Template
	Opt             *RenderOptions
	CompiledCharset string

	startTime time.Time
}

func (r *TplRender) RW() http.ResponseWriter {
	return r.ResponseWriter
}

func (r *TplRender) JSON(status int, v interface{}) {
	var result []byte
	var err error
	if r.Opt.IndentJSON {
		result, err = json.MarshalIndent(v, "", "  ")
	} else {
		result, err = json.Marshal(v)
	}
	if err != nil {
		http.Error(r, err.Error(), 500)
		return
	}

	// json rendered fine, write out the result
	r.Header().Set(ContentType, ContentJSON+r.CompiledCharset)
	r.WriteHeader(status)
	if len(r.Opt.PrefixJSON) > 0 {
		r.Write(r.Opt.PrefixJSON)
	}
	r.Write(result)
}

func (r *TplRender) JSONString(v interface{}) (string, error) {
	var result []byte
	var err error
	if r.Opt.IndentJSON {
		result, err = json.MarshalIndent(v, "", "  ")
	} else {
		result, err = json.Marshal(v)
	}
	if err != nil {
		return "", err
	}
	return string(result), nil
}

func (r *TplRender) XML(status int, v interface{}) {
	var result []byte
	var err error
	if r.Opt.IndentXML {
		result, err = xml.MarshalIndent(v, "", "  ")
	} else {
		result, err = xml.Marshal(v)
	}
	if err != nil {
		http.Error(r, err.Error(), 500)
		return
	}

	// XML rendered fine, write out the result
	r.Header().Set(ContentType, ContentXML+r.CompiledCharset)
	r.WriteHeader(status)
	if len(r.Opt.PrefixXML) > 0 {
		r.Write(r.Opt.PrefixXML)
	}
	r.Write(result)
}

func (r *TplRender) data(status int, contentType string, v []byte) {
	if r.Header().Get(ContentType) == "" {
		r.Header().Set(ContentType, contentType)
	}
	r.WriteHeader(status)
	r.Write(v)
}

func (r *TplRender) RawData(status int, v []byte) {
	r.data(status, ContentBinary, v)
}

func (r *TplRender) RenderData(status int, v []byte) {
	r.data(status, ContentHTML, v)
}

func (r *TplRender) renderBytes(name string, binding interface{}, htmlOpt ...HTMLOptions) (*bytes.Buffer, error) {
	opt := r.prepareHTMLOptions(htmlOpt)

	if len(opt.Layout) > 0 {
		r.addYield(name, binding)
		name = opt.Layout
	}

	out, err := r.execute(name, binding)
	if err != nil {
		return nil, err
	}

	return out, nil
}

func (r *TplRender) HTML(status int, name string, binding interface{}, htmlOpt ...HTMLOptions) {
	r.startTime = time.Now()

	out, err := r.renderBytes(name, binding, htmlOpt...)
	if err != nil {
		http.Error(r, err.Error(), http.StatusInternalServerError)
		return
	}

	r.Header().Set(ContentType, r.Opt.HTMLContentType+r.CompiledCharset)
	r.WriteHeader(status)
	io.Copy(r, out)
	bufpool.Put(out)
}

func (r *TplRender) HTMLSet(status int, setName, name string, binding interface{}, htmlOpt ...HTMLOptions) {
	r.startTime = time.Now()

	out, err := r.renderBytes(name, binding, htmlOpt...)
	if err != nil {
		http.Error(r, err.Error(), http.StatusInternalServerError)
		return
	}

	r.Header().Set(ContentType, r.Opt.HTMLContentType+r.CompiledCharset)
	r.WriteHeader(status)
	io.Copy(r, out)
	bufpool.Put(out)
}

func (r *TplRender) HTMLString(name string, binding interface{}, htmlOpt ...HTMLOptions) (string, error) {
	if out, err := r.renderBytes(name, binding, htmlOpt...); err != nil {
		return "", err
	} else {
		return out.String(), nil
	}
}

func (r *TplRender) HTMLSetString(setName, name string, binding interface{}, htmlOpt ...HTMLOptions) (string, error) {
	if out, err := r.renderBytes(name, binding, htmlOpt...); err != nil {
		return "", err
	} else {
		return out.String(), nil
	}
}

// Error writes the given HTTP status to the current ResponseWriter
func (r *TplRender) Error(status int, message ...string) {
	r.WriteHeader(status)
	if len(message) > 0 {
		r.Write([]byte(message[0]))
	}
}

func (r *TplRender) Status(status int) {
	r.WriteHeader(status)
}

func (r *TplRender) Redirect(location string, status ...int) {
	code := http.StatusFound
	if len(status) == 1 {
		code = status[0]
	}

	http.Redirect(r, r.Req, location, code)
}

func (r *TplRender) execute(name string, binding interface{}) (*bytes.Buffer, error) {
	buf := bufpool.Get()
	return buf, r.t.ExecuteTemplate(buf, name, binding)
}

func (r *TplRender) addYield(name string, binding interface{}) {
	funcs := template.FuncMap{
		"yield": func() (template.HTML, error) {
			buf, err := r.execute(name, binding)
			// return safe html here since we are rendering our own template
			return template.HTML(buf.String()), err
		},
		"current": func() (string, error) {
			return name, nil
		},
	}
	r.t.Funcs(funcs)
}

func (r *TplRender) prepareHTMLOptions(htmlOpt []HTMLOptions) HTMLOptions {
	if len(htmlOpt) > 0 {
		return htmlOpt[0]
	}

	return HTMLOptions{
		Layout: r.Opt.Layout,
	}
}

func (r *TplRender) SetTemplatePath(setName, dir string) {

}
<|endoftext|>


<commit_before>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package http

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

	. "github.com/TheThingsNetwork/ttn/core/errors"
	core "github.com/TheThingsNetwork/ttn/refactor"
	"github.com/TheThingsNetwork/ttn/utils/errors"
)

// httpAckNacker implements the AckNacker interface
type httpAckNacker struct {
	Chresp chan<- MsgRes // A channel dedicated to send back a response
}

// Ack implements the core.AckNacker interface
func (an httpAckNacker) Ack(p *core.Packet) error {
	if an.Chresp == nil {
		return nil
	}
	defer close(an.Chresp)

	if p == nil {
		an.Chresp <- MsgRes{StatusCode: http.StatusOK}
		return nil
	}

	raw, err := json.Marshal(*p)
	if err != nil {
		return errors.New(ErrInvalidStructure, err)
	}

	select {
	case an.Chresp <- MsgRes{
		StatusCode: http.StatusOK,
		Content:    raw,
	}:
		return nil
	case <-time.After(time.Millisecond * 50):
		return errors.New(ErrWrongBehavior, "No response was given to the acknacker")
	}
}

// Nack implements the core.AckNacker interface
func (an httpAckNacker) Nack() error {
	if an.Chresp == nil {
		return nil
	}
	defer close(an.Chresp)

	select {
	case an.Chresp <- MsgRes{
		StatusCode: http.StatusNotFound,
		Content:    []byte(`{"message":"Not in charge of the associated device"}`),
	}:
	case <-time.After(time.Millisecond * 50):
		return errors.New(ErrWrongBehavior, "No response was given to the acknacker")
	}
	return nil
}
<commit_msg>[refactor] Fix wrong use of json marshaller in httpAckNacker<commit_after>// Copyright © 2015 The Things Network
// Use of this source code is governed by the MIT license that can be found in the LICENSE file.

package http

import (
	"net/http"
	"time"

	. "github.com/TheThingsNetwork/ttn/core/errors"
	core "github.com/TheThingsNetwork/ttn/refactor"
	"github.com/TheThingsNetwork/ttn/utils/errors"
)

// httpAckNacker implements the AckNacker interface
type httpAckNacker struct {
	Chresp chan<- MsgRes // A channel dedicated to send back a response
}

// Ack implements the core.AckNacker interface
func (an httpAckNacker) Ack(p *core.Packet) error {
	if an.Chresp == nil {
		return nil
	}
	defer close(an.Chresp)

	if p == nil {
		an.Chresp <- MsgRes{StatusCode: http.StatusOK}
		return nil
	}

	data, err := (*p).MarshalBinary()
	if err != nil {
		return errors.New(ErrInvalidStructure, err)
	}

	select {
	case an.Chresp <- MsgRes{
		StatusCode: http.StatusOK,
		Content:    data,
	}:
		return nil
	case <-time.After(time.Millisecond * 50):
		return errors.New(ErrWrongBehavior, "No response was given to the acknacker")
	}
}

// Nack implements the core.AckNacker interface
func (an httpAckNacker) Nack() error {
	if an.Chresp == nil {
		return nil
	}
	defer close(an.Chresp)

	select {
	case an.Chresp <- MsgRes{
		StatusCode: http.StatusNotFound,
		Content:    []byte(`{"message":"Not in charge of the associated device"}`),
	}:
	case <-time.After(time.Millisecond * 50):
		return errors.New(ErrWrongBehavior, "No response was given to the acknacker")
	}
	return nil
}
<|endoftext|>


<commit_before>package sdl

// #cgo pkg-config: sdl2
// #cgo LDFLAGS: -lSDL2_image
//
// #include "SDL.h"
//	#include "SDL_image.h"
import "C"

import "unsafe"

func LoadImage(fileName string) (*Surface, error) {
	cName := C.CString(fileName)
	surf := C.IMG_Load(cName)
	C.free(unsafe.Pointer(cName))

	if surf == nil {
		return &Surface{}, getError()
	}

	return &Surface{surf}, nil
}
<commit_msg>image: remove double pointer<commit_after>package sdl

// #cgo pkg-config: sdl2
// #cgo LDFLAGS: -lSDL2_image
//
// #include "SDL.h"
//	#include "SDL_image.h"
import "C"

import "unsafe"

func LoadImage(fileName string) (Surface, error) {
	cName := C.CString(fileName)
	surf := C.IMG_Load(cName)
	C.free(unsafe.Pointer(cName))

	if surf == nil {
		return Surface{}, getError()
	}

	return Surface{surf}, nil
}
<|endoftext|>