<commit_before>package chartserver

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	clientTimeout         = 10 * time.Second
	maxIdleConnections    = 10
	idleConnectionTimeout = 30 * time.Second
)

// ChartClient is a http client to get the content from the external http server
type ChartClient struct {
	// HTTP client
	httpClient *http.Client

	// Auth info
	credentail *Credential
}

// NewChartClient is constructor of ChartClient
// credentail can be nil
func NewChartClient(credentail *Credential) *ChartClient { // Create http client with customized timeouts
	client := &http.Client{
		Timeout: clientTimeout,
		Transport: &http.Transport{
			MaxIdleConns:    maxIdleConnections,
			IdleConnTimeout: idleConnectionTimeout,
		},
	}

	return &ChartClient{
		httpClient: client,
		credentail: credentail,
	}
}

// GetContent get the bytes from the specified url
func (cc *ChartClient) GetContent(addr string) ([]byte, error) {
	response, err := cc.sendRequest(addr, http.MethodGet, nil, []int{http.StatusOK})
	if err != nil {
		return nil, err
	}

	content, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	return content, nil
}

// DeleteContent sends deleting request to the addr to delete content
func (cc *ChartClient) DeleteContent(addr string) error {
	_, err := cc.sendRequest(addr, http.MethodDelete, nil, []int{http.StatusOK})
	return err
}

// sendRequest sends requests to the addr with the specified spec
func (cc *ChartClient) sendRequest(addr string, method string, body io.Reader, expectedCodes []int) (*http.Response, error) {
	if len(strings.TrimSpace(addr)) == 0 {
		return nil, errors.New("empty url is not allowed")
	}

	fullURI, err := url.Parse(addr)
	if err != nil {
		return nil, fmt.Errorf("invalid url: %s", err.Error())
	}

	request, err := http.NewRequest(method, addr, body)
	if err != nil {
		return nil, err
	}

	// Set basic auth
	if cc.credentail != nil {
		request.SetBasicAuth(cc.credentail.Username, cc.credentail.Password)
	}

	response, err := cc.httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	isExpectedStatusCode := false
	for _, eCode := range expectedCodes {
		if eCode == response.StatusCode {
			isExpectedStatusCode = true
			break
		}
	}

	if !isExpectedStatusCode {
		content, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return nil, err
		}
		defer response.Body.Close()

		if err := extractError(content); err != nil {
			return nil, err
		}

		return nil, fmt.Errorf("%s '%s' failed with error: %s", method, fullURI.Path, content)
	}

	return response, nil
}
<commit_msg>Fix misspellings of the word credential<commit_after>package chartserver

import (
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

const (
	clientTimeout         = 10 * time.Second
	maxIdleConnections    = 10
	idleConnectionTimeout = 30 * time.Second
)

// ChartClient is a http client to get the content from the external http server
type ChartClient struct {
	// HTTP client
	httpClient *http.Client

	// Auth info
	credential *Credential
}

// NewChartClient is constructor of ChartClient
// credential can be nil
func NewChartClient(credential *Credential) *ChartClient { // Create http client with customized timeouts
	client := &http.Client{
		Timeout: clientTimeout,
		Transport: &http.Transport{
			MaxIdleConns:    maxIdleConnections,
			IdleConnTimeout: idleConnectionTimeout,
		},
	}

	return &ChartClient{
		httpClient: client,
		credential: credential,
	}
}

// GetContent get the bytes from the specified url
func (cc *ChartClient) GetContent(addr string) ([]byte, error) {
	response, err := cc.sendRequest(addr, http.MethodGet, nil, []int{http.StatusOK})
	if err != nil {
		return nil, err
	}

	content, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, err
	}
	defer response.Body.Close()

	return content, nil
}

// DeleteContent sends deleting request to the addr to delete content
func (cc *ChartClient) DeleteContent(addr string) error {
	_, err := cc.sendRequest(addr, http.MethodDelete, nil, []int{http.StatusOK})
	return err
}

// sendRequest sends requests to the addr with the specified spec
func (cc *ChartClient) sendRequest(addr string, method string, body io.Reader, expectedCodes []int) (*http.Response, error) {
	if len(strings.TrimSpace(addr)) == 0 {
		return nil, errors.New("empty url is not allowed")
	}

	fullURI, err := url.Parse(addr)
	if err != nil {
		return nil, fmt.Errorf("invalid url: %s", err.Error())
	}

	request, err := http.NewRequest(method, addr, body)
	if err != nil {
		return nil, err
	}

	// Set basic auth
	if cc.credential != nil {
		request.SetBasicAuth(cc.credential.Username, cc.credential.Password)
	}

	response, err := cc.httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	isExpectedStatusCode := false
	for _, eCode := range expectedCodes {
		if eCode == response.StatusCode {
			isExpectedStatusCode = true
			break
		}
	}

	if !isExpectedStatusCode {
		content, err := ioutil.ReadAll(response.Body)
		if err != nil {
			return nil, err
		}
		defer response.Body.Close()

		if err := extractError(content); err != nil {
			return nil, err
		}

		return nil, fmt.Errorf("%s '%s' failed with error: %s", method, fullURI.Path, content)
	}

	return response, nil
}
<|endoftext|>


<commit_before>package chartserver

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	commonhttp "github.com/goharbor/harbor/src/common/http"
	"github.com/goharbor/harbor/src/lib/errors"
)

const (
	clientTimeout         = 10 * time.Second
	maxIdleConnections    = 10
	idleConnectionTimeout = 30 * time.Second
)

var (
	once           sync.Once
	chartTransport *http.Transport
)

// ChartClient is a http client to get the content from the external http server
type ChartClient struct {
	// HTTP client
	httpClient *http.Client

	// Auth info
	credential *Credential
}

// NewChartClient is constructor of ChartClient
// credential can be nil
func NewChartClient(credential *Credential) *ChartClient { // Create http client with customized timeouts
	once.Do(func() {
		chartTransport = commonhttp.GetHTTPTransport(commonhttp.SecureTransport).Clone()
		chartTransport.MaxIdleConns = maxIdleConnections
		chartTransport.IdleConnTimeout = idleConnectionTimeout
	})

	client := &http.Client{
		Timeout:   clientTimeout,
		Transport: chartTransport,
	}

	return &ChartClient{
		httpClient: client,
		credential: credential,
	}
}

// GetContent get the bytes from the specified url
func (cc *ChartClient) GetContent(addr string) ([]byte, error) {
	response, err := cc.sendRequest(addr, http.MethodGet, nil)
	if err != nil {
		err = errors.Wrap(err, "get content failed")
		return nil, err
	}

	content, err := ioutil.ReadAll(response.Body)
	if err != nil {
		err = errors.Wrap(err, "Read response body error")
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		text, err := extractError(content)
		if err != nil {
			err = errors.Wrap(err, "Extract content error failed")
			return nil, err
		}
		return nil, &commonhttp.Error{
			Code:    response.StatusCode,
			Message: text,
		}
	}
	return content, nil
}

// DeleteContent sends deleting request to the addr to delete content
func (cc *ChartClient) DeleteContent(addr string) error {
	response, err := cc.sendRequest(addr, http.MethodDelete, nil)
	if err != nil {
		return err
	}

	content, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		text, err := extractError(content)
		if err != nil {
			return err
		}
		return &commonhttp.Error{
			Code:    response.StatusCode,
			Message: text,
		}
	}

	return nil
}

// sendRequest sends requests to the addr with the specified spec
func (cc *ChartClient) sendRequest(addr string, method string, body io.Reader) (*http.Response, error) {
	if len(strings.TrimSpace(addr)) == 0 {
		return nil, errors.New("empty url is not allowed")
	}

	fullURI, err := url.Parse(addr)
	if err != nil {
		err = errors.Wrap(err, "Invalid url")
		return nil, err
	}

	request, err := http.NewRequest(method, addr, body)
	if err != nil {
		return nil, err
	}

	// Set basic auth
	if cc.credential != nil {
		request.SetBasicAuth(cc.credential.Username, cc.credential.Password)
	}

	response, err := cc.httpClient.Do(request)
	if err != nil {
		err = errors.Wrap(err, fmt.Sprintf("send request %s %s failed", method, fullURI.Path))
		return nil, err
	}

	return response, nil
}
<commit_msg>Enhance: Prolong the timeout of chartclient<commit_after>package chartserver

import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	commonhttp "github.com/goharbor/harbor/src/common/http"
	"github.com/goharbor/harbor/src/lib/errors"
)

const (
	clientTimeout         = 30 * time.Second
	maxIdleConnections    = 10
	idleConnectionTimeout = 30 * time.Second
)

var (
	once           sync.Once
	chartTransport *http.Transport
)

// ChartClient is a http client to get the content from the external http server
type ChartClient struct {
	// HTTP client
	httpClient *http.Client

	// Auth info
	credential *Credential
}

// NewChartClient is constructor of ChartClient
// credential can be nil
func NewChartClient(credential *Credential) *ChartClient { // Create http client with customized timeouts
	once.Do(func() {
		chartTransport = commonhttp.GetHTTPTransport(commonhttp.SecureTransport).Clone()
		chartTransport.MaxIdleConns = maxIdleConnections
		chartTransport.IdleConnTimeout = idleConnectionTimeout
	})

	client := &http.Client{
		Timeout:   clientTimeout,
		Transport: chartTransport,
	}

	return &ChartClient{
		httpClient: client,
		credential: credential,
	}
}

// GetContent get the bytes from the specified url
func (cc *ChartClient) GetContent(addr string) ([]byte, error) {
	response, err := cc.sendRequest(addr, http.MethodGet, nil)
	if err != nil {
		err = errors.Wrap(err, "get content failed")
		return nil, err
	}

	content, err := ioutil.ReadAll(response.Body)
	if err != nil {
		err = errors.Wrap(err, "Read response body error")
		return nil, err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		text, err := extractError(content)
		if err != nil {
			err = errors.Wrap(err, "Extract content error failed")
			return nil, err
		}
		return nil, &commonhttp.Error{
			Code:    response.StatusCode,
			Message: text,
		}
	}
	return content, nil
}

// DeleteContent sends deleting request to the addr to delete content
func (cc *ChartClient) DeleteContent(addr string) error {
	response, err := cc.sendRequest(addr, http.MethodDelete, nil)
	if err != nil {
		return err
	}

	content, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return err
	}
	defer response.Body.Close()

	if response.StatusCode != http.StatusOK {
		text, err := extractError(content)
		if err != nil {
			return err
		}
		return &commonhttp.Error{
			Code:    response.StatusCode,
			Message: text,
		}
	}

	return nil
}

// sendRequest sends requests to the addr with the specified spec
func (cc *ChartClient) sendRequest(addr string, method string, body io.Reader) (*http.Response, error) {
	if len(strings.TrimSpace(addr)) == 0 {
		return nil, errors.New("empty url is not allowed")
	}

	fullURI, err := url.Parse(addr)
	if err != nil {
		err = errors.Wrap(err, "Invalid url")
		return nil, err
	}

	request, err := http.NewRequest(method, addr, body)
	if err != nil {
		return nil, err
	}

	// Set basic auth
	if cc.credential != nil {
		request.SetBasicAuth(cc.credential.Username, cc.credential.Password)
	}

	response, err := cc.httpClient.Do(request)
	if err != nil {
		err = errors.Wrap(err, fmt.Sprintf("send request %s %s failed", method, fullURI.Path))
		return nil, err
	}

	return response, nil
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"encoding/json"
	"github.com/bmizerany/pq"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"time"
)

type Api struct {
	Db      *sql.DB
	Version string
}

var api Api

func init() {
	// version
	api.Version = "0.0.1"

	// setup database connection
	db, err := sql.Open("postgres", "dbname=cwfy sslmode=disable")
	if err != nil {
		log.Fatal("Cannot open database connection", err)
	}
	api.Db = db
}

func main() {
	log.Print("starting ChicagoWorksforYou.com API server")

	// listen for SIGINT (h/t http://stackoverflow.com/a/12571099/1247272)
	notify_channel := make(chan os.Signal, 1)
	signal.Notify(notify_channel, os.Interrupt, os.Kill)
	go func() {
		for _ = range notify_channel {
			log.Printf("stopping ChicagoWorksForYou.com API server")
			api.Db.Close()
			os.Exit(1)
		}
	}()

	router := mux.NewRouter()
	router.HandleFunc("/health_check", HealthCheckHandler)
	router.HandleFunc("/services.json", ServicesHandler)
	router.HandleFunc("/wards/{id}/requests.json", WardRequestsHandler)
	router.HandleFunc("/wards/{id}/counts.json", WardCountsHandler)
	http.ListenAndServe(":5000", router)
}

func WrapJson(unwrapped []byte, callback []string) (jsn []byte) {
	jsn = unwrapped
	if len(callback) > 0 {
		wrapped := strings.Join([]string{callback[0], "(", string(jsn), ");"}, "")
		jsn = []byte(wrapped)
	}

	return
}

func WardCountsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given ward, return the number of service requests opened
	// grouped by day, then by service request type
	//
	// Parameters:
	//
	//	count:          the number of days of data to return
	//	end_date:       date that +count+ is based from.
	//	service_code:   the code used by the City of Chicago to categorize service requests
	//	callback:       function to wrap response in (for JSONP functionality)
	//
	// Sample API output
	//
	// Note that the end date is June 12, and the results include the end_date.
	// $ curl "http://localhost:5000/wards/10/counts.json?service_code=4fd3b167e750846744000005&count=5&end_date=2013-06-12"
	// {
	//   "2013-06-06": 2,
	//   "2013-06-07": 4,
	//   "2013-06-09": 5,
	//   "2013-06-10": 6,
	//   "2013-06-12": 23
	// }
	//
	vars := mux.Vars(request)
	ward_id := vars["id"]
	params := request.URL.Query()

	// determine date range. default is last 7 days.
	days, _ := strconv.Atoi(params["count"][0])

	end, _ := time.Parse("2006-01-02", params["end_date"][0])
	end = end.AddDate(0, 0, 1) // inc to the following day
	start := end.AddDate(0, 0, -days)

	log.Printf("fetching counts for ward %s code %s for past %d days", ward_id, params["service_code"][0], days)
	log.Printf("date range is %s to %s", start, end)

	rows, err := api.Db.Query("SELECT COUNT(*), DATE(requested_datetime) as requested_date FROM service_requests WHERE ward = $1 "+
		"AND duplicate IS NULL AND service_code = $2 AND requested_datetime >= $3::date AND requested_datetime <= $4::date "+
		"GROUP BY DATE(requested_datetime) ORDER BY requested_date;", string(ward_id), params["service_code"][0], start, end)
	if err != nil {
		log.Fatal("error fetching data for WardCountsHandler", err)
	}

	type WardCount struct {
		Requested_date time.Time
		Count          int
	}

	var counts []WardCount
	for rows.Next() {
		wc := WardCount{}
		if err := rows.Scan(&wc.Count, &wc.Requested_date); err != nil {
			log.Print("error reading row of ward count", err)
		}

		// trunc the requested time to just date
		counts = append(counts, wc)
	}

	resp := make(map[string]int)

	for _, c := range counts {
		key := c.Requested_date.Format("2006-01-02")
		resp[key] = c.Count
	}

	jsn, _ := json.MarshalIndent(resp, "", "  ")
	jsn = WrapJson(jsn, params["callback"])

	response.Write(jsn)
}

func WardRequestsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given ward, return recent service requests

	vars := mux.Vars(request)
	ward_id := vars["id"]
	params := request.URL.Query()
	log.Print("fetch requests for ward ", ward_id)

	rows, err := api.Db.Query("SELECT lat,long,ward,police_district,service_request_id,status,service_name,service_code,agency_responsible,address,channel,media_url,requested_datetime,updated_datetime,created_at,updated_at,duplicate,parent_service_request_id,id FROM service_requests WHERE duplicate IS NULL AND ward = $1 ORDER BY updated_at DESC LIMIT 100;", ward_id)

	if err != nil {
		log.Fatal("error fetching data for WardRequestsHandler", err)
	}

	type Open311RequestRow struct {
		Lat, Long                                                                                float64
		Ward, Police_district, Id                                                                                int
		Service_request_id, Status, Service_name, Service_code, Agency_responsible, Address, Channel, Media_url, Duplicate, Parent_service_request_id sql.NullString
		Requested_datetime, Updated_datetime, Created_at, Updated_at                                                                                pq.NullTime // FIXME: should these be proper time objects?
		Extended_attributes                                                                                map[string]interface{}
	}

	var result []Open311RequestRow

	for rows.Next() {
		var row Open311RequestRow
		if err := rows.Scan(&row.Lat, &row.Long, &row.Ward, &row.Police_district,
			&row.Service_request_id, &row.Status, &row.Service_name,
			&row.Service_code, &row.Agency_responsible, &row.Address,
			&row.Channel, &row.Media_url, &row.Requested_datetime,
			&row.Updated_datetime, &row.Created_at, &row.Updated_at,
			&row.Duplicate, &row.Parent_service_request_id,
			&row.Id); err != nil {
			log.Fatal("error reading row", err)
		}

		result = append(result, row)
	}

	jsn, _ := json.MarshalIndent(result, "", "  ")
	jsn = WrapJson(jsn, params["callback"])
	response.Write(jsn)
}

func ServicesHandler(response http.ResponseWriter, request *http.Request) {
	// return counts of requests, grouped by service name
	//
	// Sample output:
	//
	// [
	//   {
	//     "Count": 1139,
	//     "Service_code": "4fd3b167e750846744000005",
	//     "Service_name": "Graffiti Removal"
	//   },
	//   {
	//     "Count": 25,
	//     "Service_code": "4fd6e4ece750840569000019",
	//     "Service_name": "Restaurant Complaint"
	//   },
	//
	//  ... snip ...
	//
	// ]

	type ServicesCount struct {
		Count        int
		Service_code string
		Service_name string
	}

	var services []ServicesCount

	rows, err := api.Db.Query("SELECT COUNT(*), service_code, service_name FROM service_requests WHERE duplicate IS NULL GROUP BY service_code,service_name;")

	if err != nil {
		log.Fatal("error fetching data for ServicesHandler", err)
	}

	for rows.Next() {
		var count int
		var service_code, service_name string

		if err := rows.Scan(&count, &service_code, &service_name); err != nil {
			log.Fatal("error reading row", err)
		}

		row := ServicesCount{Count: count, Service_code: service_code, Service_name: service_name}
		services = append(services, row)
	}

	jsn, _ := json.MarshalIndent(services, "", "  ")
	response.Write(jsn)
}

func HealthCheckHandler(response http.ResponseWriter, request *http.Request) {
	params := request.URL.Query()

	response.Header().Add("Content-type", "application/json")

	type HealthCheck struct {
		Count    int
		Database bool
		Healthy  bool
		Version  string
	}

	health_check := HealthCheck{Version: api.Version}

	health_check.Database = api.Db.Ping() == nil

	rows, _ := api.Db.Query("SELECT COUNT(*) FROM service_requests;")
	for rows.Next() {
		if err := rows.Scan(&health_check.Count); err != nil {
			log.Fatal("error fetching count", err)
		}
	}

	// calculate overall health
	health_check.Healthy = health_check.Count > 0 && health_check.Database

	log.Printf("health_check: %+v", health_check)
	if !health_check.Healthy {
		log.Printf("health_check failed")
	}
	jsn, _ := json.MarshalIndent(health_check, "", "  ")
	jsn = WrapJson(jsn, params["callback"])
	response.Write(jsn)
}
<commit_msg>adding first pass of RequestCountsHandler<commit_after>package main

import (
	"database/sql"
	"encoding/json"
	"github.com/bmizerany/pq"
	"github.com/gorilla/mux"
	"log"
	"net/http"
	"os"
	"os/signal"
	"strconv"
	"strings"
	"time"
)

type Api struct {
	Db      *sql.DB
	Version string
}

var api Api

func init() {
	// version
	api.Version = "0.0.1"

	// setup database connection
	db, err := sql.Open("postgres", "dbname=cwfy sslmode=disable")
	if err != nil {
		log.Fatal("Cannot open database connection", err)
	}
	api.Db = db
}

func main() {
	log.Print("starting ChicagoWorksforYou.com API server")

	// listen for SIGINT (h/t http://stackoverflow.com/a/12571099/1247272)
	notify_channel := make(chan os.Signal, 1)
	signal.Notify(notify_channel, os.Interrupt, os.Kill)
	go func() {
		for _ = range notify_channel {
			log.Printf("stopping ChicagoWorksForYou.com API server")
			api.Db.Close()
			os.Exit(1)
		}
	}()

	router := mux.NewRouter()
	router.HandleFunc("/health_check", HealthCheckHandler)
	router.HandleFunc("/services.json", ServicesHandler)
	router.HandleFunc("/wards/{id}/requests.json", WardRequestsHandler)
	router.HandleFunc("/wards/{id}/counts.json", WardCountsHandler)
	router.HandleFunc("/requests/{service_code}/counts.json", RequestCountsHandler)
	http.ListenAndServe(":5000", router)
}

func WrapJson(unwrapped []byte, callback []string) (jsn []byte) {
	jsn = unwrapped
	if len(callback) > 0 {
		wrapped := strings.Join([]string{callback[0], "(", string(jsn), ");"}, "")
		jsn = []byte(wrapped)
	}

	return
}

func RequestCountsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given request service type and date, return the count
	// of requests for that date, grouped by ward

	vars := mux.Vars(request)
	service_code := vars["service_code"]
	params := request.URL.Query()

	// determine date range. default is last 7 days.
	days, _ := strconv.Atoi(params["count"][0])

	end, _ := time.Parse("2006-01-02", params["end_date"][0])
	end = end.AddDate(0, 0, 1) // inc to the following day
	start := end.AddDate(0, 0, -days)

	log.Printf("date range is %s to %s", start, end)

	rows, err := api.Db.Query("SELECT COUNT(*), ward FROM service_requests WHERE service_code "+
		"= $1 AND duplicate IS NULL AND requested_datetime >= $2::date "+
		" AND requested_datetime <= $3::date  GROUP BY ward ORDER BY ward;",
		string(service_code), start, end)

	if err != nil {
		log.Fatal("error fetching data for RequestCountsHandler", err)
	}

	type WardCount struct {
		Ward  int
		Count int
	}

	var counts []WardCount
	for rows.Next() {
		wc := WardCount{}
		if err := rows.Scan(&wc.Count, &wc.Ward); err != nil {
			log.Print("error reading row of ward count", err)
		}

		// trunc the requested time to just date
		counts = append(counts, wc)
	}

	// resp := make(map[string]int)
	//
	//     for _, c := range counts {
	//             key := c.Requested_date.Format("2006-01-02")
	//             resp[key] = c.Count
	//     }

	jsn, _ := json.MarshalIndent(counts, "", "  ")
	jsn = WrapJson(jsn, params["callback"])

	response.Write(jsn)
}

func WardCountsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given ward, return the number of service requests opened
	// grouped by day, then by service request type
	//
	// Parameters:
	//
	//	count:          the number of days of data to return
	//	end_date:       date that +count+ is based from.
	//	service_code:   the code used by the City of Chicago to categorize service requests
	//	callback:       function to wrap response in (for JSONP functionality)
	//
	// Sample API output
	//
	// Note that the end date is June 12, and the results include the end_date.
	// $ curl "http://localhost:5000/wards/10/counts.json?service_code=4fd3b167e750846744000005&count=5&end_date=2013-06-12"
	// {
	//   "2013-06-06": 2,
	//   "2013-06-07": 4,
	//   "2013-06-09": 5,
	//   "2013-06-10": 6,
	//   "2013-06-12": 23
	// }
	//
	vars := mux.Vars(request)
	ward_id := vars["id"]
	params := request.URL.Query()

	// determine date range. default is last 7 days.
	days, _ := strconv.Atoi(params["count"][0])

	end, _ := time.Parse("2006-01-02", params["end_date"][0])
	end = end.AddDate(0, 0, 1) // inc to the following day
	start := end.AddDate(0, 0, -days)

	log.Printf("fetching counts for ward %s code %s for past %d days", ward_id, params["service_code"][0], days)
	log.Printf("date range is %s to %s", start, end)

	rows, err := api.Db.Query("SELECT COUNT(*), DATE(requested_datetime) as requested_date FROM service_requests WHERE ward = $1 "+
		"AND duplicate IS NULL AND service_code = $2 AND requested_datetime >= $3::date AND requested_datetime <= $4::date "+
		"GROUP BY DATE(requested_datetime) ORDER BY requested_date;", string(ward_id), params["service_code"][0], start, end)
	if err != nil {
		log.Fatal("error fetching data for WardCountsHandler", err)
	}

	type WardCount struct {
		Requested_date time.Time
		Count          int
	}

	var counts []WardCount
	for rows.Next() {
		wc := WardCount{}
		if err := rows.Scan(&wc.Count, &wc.Requested_date); err != nil {
			log.Print("error reading row of ward count", err)
		}

		// trunc the requested time to just date
		counts = append(counts, wc)
	}

	resp := make(map[string]int)

	for _, c := range counts {
		key := c.Requested_date.Format("2006-01-02")
		resp[key] = c.Count
	}

	jsn, _ := json.MarshalIndent(resp, "", "  ")
	jsn = WrapJson(jsn, params["callback"])

	response.Write(jsn)
}

func WardRequestsHandler(response http.ResponseWriter, request *http.Request) {
	// for a given ward, return recent service requests

	vars := mux.Vars(request)
	ward_id := vars["id"]
	params := request.URL.Query()
	log.Print("fetch requests for ward ", ward_id)

	rows, err := api.Db.Query("SELECT lat,long,ward,police_district,service_request_id,status,service_name,service_code,agency_responsible,address,channel,media_url,requested_datetime,updated_datetime,created_at,updated_at,duplicate,parent_service_request_id,id FROM service_requests WHERE duplicate IS NULL AND ward = $1 ORDER BY updated_at DESC LIMIT 100;", ward_id)

	if err != nil {
		log.Fatal("error fetching data for WardRequestsHandler", err)
	}

	type Open311RequestRow struct {
		Lat, Long                                                                                float64
		Ward, Police_district, Id                                                                                int
		Service_request_id, Status, Service_name, Service_code, Agency_responsible, Address, Channel, Media_url, Duplicate, Parent_service_request_id sql.NullString
		Requested_datetime, Updated_datetime, Created_at, Updated_at                                                                                pq.NullTime // FIXME: should these be proper time objects?
		Extended_attributes                                                                                map[string]interface{}
	}

	var result []Open311RequestRow

	for rows.Next() {
		var row Open311RequestRow
		if err := rows.Scan(&row.Lat, &row.Long, &row.Ward, &row.Police_district,
			&row.Service_request_id, &row.Status, &row.Service_name,
			&row.Service_code, &row.Agency_responsible, &row.Address,
			&row.Channel, &row.Media_url, &row.Requested_datetime,
			&row.Updated_datetime, &row.Created_at, &row.Updated_at,
			&row.Duplicate, &row.Parent_service_request_id,
			&row.Id); err != nil {
			log.Fatal("error reading row", err)
		}

		result = append(result, row)
	}

	jsn, _ := json.MarshalIndent(result, "", "  ")
	jsn = WrapJson(jsn, params["callback"])
	response.Write(jsn)
}

func ServicesHandler(response http.ResponseWriter, request *http.Request) {
	// return counts of requests, grouped by service name
	//
	// Sample output:
	//
	// [
	//   {
	//     "Count": 1139,
	//     "Service_code": "4fd3b167e750846744000005",
	//     "Service_name": "Graffiti Removal"
	//   },
	//   {
	//     "Count": 25,
	//     "Service_code": "4fd6e4ece750840569000019",
	//     "Service_name": "Restaurant Complaint"
	//   },
	//
	//  ... snip ...
	//
	// ]

	type ServicesCount struct {
		Count        int
		Service_code string
		Service_name string
	}

	var services []ServicesCount

	rows, err := api.Db.Query("SELECT COUNT(*), service_code, service_name FROM service_requests WHERE duplicate IS NULL GROUP BY service_code,service_name;")

	if err != nil {
		log.Fatal("error fetching data for ServicesHandler", err)
	}

	for rows.Next() {
		var count int
		var service_code, service_name string

		if err := rows.Scan(&count, &service_code, &service_name); err != nil {
			log.Fatal("error reading row", err)
		}

		row := ServicesCount{Count: count, Service_code: service_code, Service_name: service_name}
		services = append(services, row)
	}

	jsn, _ := json.MarshalIndent(services, "", "  ")
	response.Write(jsn)
}

func HealthCheckHandler(response http.ResponseWriter, request *http.Request) {
	params := request.URL.Query()

	response.Header().Add("Content-type", "application/json")

	type HealthCheck struct {
		Count    int
		Database bool
		Healthy  bool
		Version  string
	}

	health_check := HealthCheck{Version: api.Version}

	health_check.Database = api.Db.Ping() == nil

	rows, _ := api.Db.Query("SELECT COUNT(*) FROM service_requests;")
	for rows.Next() {
		if err := rows.Scan(&health_check.Count); err != nil {
			log.Fatal("error fetching count", err)
		}
	}

	// calculate overall health
	health_check.Healthy = health_check.Count > 0 && health_check.Database

	log.Printf("health_check: %+v", health_check)
	if !health_check.Healthy {
		log.Printf("health_check failed")
	}
	jsn, _ := json.MarshalIndent(health_check, "", "  ")
	jsn = WrapJson(jsn, params["callback"])
	response.Write(jsn)
}
<|endoftext|>


<commit_before>package api

import (
	"log"
	"net/http"
	"regexp"
	"strings"
	"github.com/codegangsta/martini"
	"github.com/centurylinklabs/panamax-marathon-adapter/utils"
)

// The one and only martini instance.
var mServer *martini.Martini
var adapterInstance PanamaxAdapter

func init() {
	mServer = martini.New()
	// Setup middleware
	mServer.Use(martini.Recovery())
	mServer.Use(martini.Logger())
	mServer.Use(mapEncoder)
	mServer.Use(adapter)
	// Setup routes
	r := martini.NewRouter()
	r.Get(`/services`, getServices)
	r.Get(`/services/:id`, getService)
	r.Post(`/services`, createService)

	// Add the router action
	mServer.Action(r.Handle)
}

// The regex to check for the requested format (allows an optional trailing
// slash)
var rxExt = regexp.MustCompile(`(\.(?:json))\/?$`)

// MapEncoder intercepts the request's URL, detects the requested format,
// and injects the correct encoder dependency for this request. It rewrites
// the URL to remove the format extension, so that routes can be defined
// without it.
func mapEncoder(c martini.Context, w http.ResponseWriter, r *http.Request) {
	// Get the format extension
	matches := rxExt.FindStringSubmatch(r.URL.Path)
	ft := ".json"
	if len(matches) > 1 {
		// Rewrite the URL without the format extension
		l := len(r.URL.Path) - len(matches[1])
		if strings.HasSuffix(r.URL.Path, "/") {
			l--
		}
		r.URL.Path = r.URL.Path[:l]
		ft = matches[1]
	}
	// Inject the requested encoder
	switch ft {
	// Add cases for other formats
	default:
		c.MapTo(utils.JsonEncoder{}, (*utils.Encoder)(nil))
		w.Header().Set("Content-Type", "application/json")
	}
}

func adapter(c martini.Context, w http.ResponseWriter, r *http.Request) {
	c.Map(adapter)
}

func ListenAndServe(theAdapter PanamaxAdapter) {
        adapterInstance = theAdapter
	err := http.ListenAndServe(":8001", mServer)
	if	err != nil {
		log.Fatal(err)
	}
}
<commit_msg>Fix minor adapter instance variable name.<commit_after>package api

import (
	"log"
	"net/http"
	"regexp"
	"strings"
	"github.com/codegangsta/martini"
	"github.com/centurylinklabs/panamax-marathon-adapter/utils"
)

// The one and only martini instance.
var mServer *martini.Martini
var adapterInstance PanamaxAdapter

func init() {
	mServer = martini.New()
	// Setup middleware
	mServer.Use(martini.Recovery())
	mServer.Use(martini.Logger())
	mServer.Use(mapEncoder)
	mServer.Use(adapter)
	// Setup routes
	r := martini.NewRouter()
	r.Get(`/services`, getServices)
	r.Get(`/services/:id`, getService)
	r.Post(`/services`, createService)

	// Add the router action
	mServer.Action(r.Handle)
}

// The regex to check for the requested format (allows an optional trailing
// slash)
var rxExt = regexp.MustCompile(`(\.(?:json))\/?$`)

// MapEncoder intercepts the request's URL, detects the requested format,
// and injects the correct encoder dependency for this request. It rewrites
// the URL to remove the format extension, so that routes can be defined
// without it.
func mapEncoder(c martini.Context, w http.ResponseWriter, r *http.Request) {
	// Get the format extension
	matches := rxExt.FindStringSubmatch(r.URL.Path)
	ft := ".json"
	if len(matches) > 1 {
		// Rewrite the URL without the format extension
		l := len(r.URL.Path) - len(matches[1])
		if strings.HasSuffix(r.URL.Path, "/") {
			l--
		}
		r.URL.Path = r.URL.Path[:l]
		ft = matches[1]
	}
	// Inject the requested encoder
	switch ft {
	// Add cases for other formats
	default:
		c.MapTo(utils.JsonEncoder{}, (*utils.Encoder)(nil))
		w.Header().Set("Content-Type", "application/json")
	}
}

func adapter(c martini.Context, w http.ResponseWriter, r *http.Request) {
	c.Map(adapterInstance)
}

func ListenAndServe(theAdapter PanamaxAdapter) {
        adapterInstance = theAdapter
	err := http.ListenAndServe(":8001", mServer)
	if	err != nil {
		log.Fatal(err)
	}
}
<|endoftext|>


<commit_before>package slack

// OutgoingMessage is used for the realtime API, and seems incomplete.
type OutgoingMessage struct {
	ID      int    `json:"id"`
	Channel string `json:"channel,omitempty"`
	Text    string `json:"text,omitempty"`
	Type    string `json:"type,omitempty"`
}

// Message is an auxiliary type to allow us to have a message containing sub messages
type Message struct {
	Msg
	SubMessage *Msg `json:"message,omitempty"`
}

// Msg contains information about a slack message
type Msg struct {
	// Basic Message
	Type        string       `json:"type,omitempty"`
	Channel     string       `json:"channel,omitempty"`
	User        string       `json:"user,omitempty"`
	Text        string       `json:"text,omitempty"`
	Timestamp   string       `json:"ts,omitempty"`
	IsStarred   bool         `json:"is_starred,omitempty"`
	PinnedTo    []string     `json:"pinned_to, omitempty"`
	Attachments []Attachment `json:"attachments,omitempty"`
	Edited      *Edited      `json:"edited,omitempty"`

	// Message Subtypes
	SubType string `json:"subtype,omitempty"`

	// Hidden Subtypes
	Hidden           bool   `json:"hidden,omitempty"`     // message_changed, message_deleted, unpinned_item
	DeletedTimestamp string `json:"deleted_ts,omitempty"` // message_deleted
	EventTimestamp   string `json:"event_ts,omitempty"`

	// bot_message (https://api.slack.com/events/message/bot_message)
	BotID    string `json:"bot_id,omitempty"`
	Username string `json:"username,omitempty"`
	Icons    *Icon  `json:"icons,omitempty"`

	// channel_join, group_join
	Inviter string `json:"inviter,omitempty"`

	// channel_topic, group_topic
	Topic string `json:"topic,omitempty"`

	// channel_purpose, group_purpose
	Purpose string `json:"purpose,omitempty"`

	// channel_name, group_name
	Name    string `json:"name,omitempty"`
	OldName string `json:"old_name,omitempty"`

	// channel_archive, group_archive
	Members []string `json:"members,omitempty"`

	// file_share, file_comment, file_mention
	File *File `json:"file,omitempty"`

	// file_share
	Upload bool `json:"upload,omitempty"`

	// file_comment
	Comment *Comment `json:"comment,omitempty"`

	// pinned_item
	ItemType string `json:"item_type,omitempty"`

	// https://api.slack.com/rtm
	ReplyTo int    `json:"reply_to,omitempty"`
	Team    string `json:"team,omitempty"`

	// reactions
	Reactions []ItemReaction `json:"reactions,omitempty"`
}

// Icon is used for bot messages
type Icon struct {
	IconURL   string `json:"icon_url,omitempty"`
	IconEmoji string `json:"icon_emoji,omitempty"`
}

// Edited indicates that a message has been edited.
type Edited struct {
	User      string `json:"user,omitempty"`
	Timestamp string `json:"ts,omitempty"`
}

// Event contains the event type
type Event struct {
	Type string `json:"type,omitempty"`
}

// Ping contains information about a Ping Event
type Ping struct {
	ID   int    `json:"id"`
	Type string `json:"type"`
}

// Pong contains information about a Pong Event
type Pong struct {
	Type    string `json:"type"`
	ReplyTo int    `json:"reply_to"`
}

// NewOutgoingMessage prepares an OutgoingMessage that the user can
// use to send a message. Use this function to properly set the
// messageID.
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
	id := rtm.idGen.Next()
	return &OutgoingMessage{
		ID:      id,
		Type:    "message",
		Channel: channel,
		Text:    text,
	}
}

// NewTypingMessage prepares an OutgoingMessage that the user can
// use to send as a typing indicator. Use this function to properly set the
// messageID.
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage {
	id := rtm.idGen.Next()
	return &OutgoingMessage{
		ID:      id,
		Type:    "typing",
		Channel: channel,
	}
}
<commit_msg>Add Thread TimeStamp<commit_after>package slack

// OutgoingMessage is used for the realtime API, and seems incomplete.
type OutgoingMessage struct {
	ID      int    `json:"id"`
	Channel string `json:"channel,omitempty"`
	Text    string `json:"text,omitempty"`
	Type    string `json:"type,omitempty"`
}

// Message is an auxiliary type to allow us to have a message containing sub messages
type Message struct {
	Msg
	SubMessage *Msg `json:"message,omitempty"`
}

// Msg contains information about a slack message
type Msg struct {
	// Basic Message
	Type            string       `json:"type,omitempty"`
	Channel         string       `json:"channel,omitempty"`
	User            string       `json:"user,omitempty"`
	Text            string       `json:"text,omitempty"`
	Timestamp       string       `json:"ts,omitempty"`
	ThreadTimeStamp string       `json:"thread_ts,omitempty"`
	IsStarred       bool         `json:"is_starred,omitempty"`
	PinnedTo        []string     `json:"pinned_to, omitempty"`
	Attachments     []Attachment `json:"attachments,omitempty"`
	Edited          *Edited      `json:"edited,omitempty"`

	// Message Subtypes
	SubType string `json:"subtype,omitempty"`

	// Hidden Subtypes
	Hidden           bool   `json:"hidden,omitempty"`     // message_changed, message_deleted, unpinned_item
	DeletedTimestamp string `json:"deleted_ts,omitempty"` // message_deleted
	EventTimestamp   string `json:"event_ts,omitempty"`

	// bot_message (https://api.slack.com/events/message/bot_message)
	BotID    string `json:"bot_id,omitempty"`
	Username string `json:"username,omitempty"`
	Icons    *Icon  `json:"icons,omitempty"`

	// channel_join, group_join
	Inviter string `json:"inviter,omitempty"`

	// channel_topic, group_topic
	Topic string `json:"topic,omitempty"`

	// channel_purpose, group_purpose
	Purpose string `json:"purpose,omitempty"`

	// channel_name, group_name
	Name    string `json:"name,omitempty"`
	OldName string `json:"old_name,omitempty"`

	// channel_archive, group_archive
	Members []string `json:"members,omitempty"`

	// file_share, file_comment, file_mention
	File *File `json:"file,omitempty"`

	// file_share
	Upload bool `json:"upload,omitempty"`

	// file_comment
	Comment *Comment `json:"comment,omitempty"`

	// pinned_item
	ItemType string `json:"item_type,omitempty"`

	// https://api.slack.com/rtm
	ReplyTo int    `json:"reply_to,omitempty"`
	Team    string `json:"team,omitempty"`

	// reactions
	Reactions []ItemReaction `json:"reactions,omitempty"`
}

// Icon is used for bot messages
type Icon struct {
	IconURL   string `json:"icon_url,omitempty"`
	IconEmoji string `json:"icon_emoji,omitempty"`
}

// Edited indicates that a message has been edited.
type Edited struct {
	User      string `json:"user,omitempty"`
	Timestamp string `json:"ts,omitempty"`
}

// Event contains the event type
type Event struct {
	Type string `json:"type,omitempty"`
}

// Ping contains information about a Ping Event
type Ping struct {
	ID   int    `json:"id"`
	Type string `json:"type"`
}

// Pong contains information about a Pong Event
type Pong struct {
	Type    string `json:"type"`
	ReplyTo int    `json:"reply_to"`
}

// NewOutgoingMessage prepares an OutgoingMessage that the user can
// use to send a message. Use this function to properly set the
// messageID.
func (rtm *RTM) NewOutgoingMessage(text string, channel string) *OutgoingMessage {
	id := rtm.idGen.Next()
	return &OutgoingMessage{
		ID:      id,
		Type:    "message",
		Channel: channel,
		Text:    text,
	}
}

// NewTypingMessage prepares an OutgoingMessage that the user can
// use to send as a typing indicator. Use this function to properly set the
// messageID.
func (rtm *RTM) NewTypingMessage(channel string) *OutgoingMessage {
	id := rtm.idGen.Next()
	return &OutgoingMessage{
		ID:      id,
		Type:    "typing",
		Channel: channel,
	}
}
<|endoftext|>


<commit_before>package gumble

import (
	"code.google.com/p/goprotobuf/proto"
	"github.com/layeh/gopus"
	"github.com/layeh/gumble/gumble/MumbleProto"
)

// User represents a user that is currently connected to the server.
type User struct {
	client  *Client
	decoder *gopus.Decoder

	session, userID                          uint32
	name                                     string
	channel                                  *Channel
	mute, deaf, suppress, selfMute, selfDeaf bool
	comment                                  string
	commentHash                              []byte
	hash                                     string
	texture, textureHash                     []byte
	prioritySpeaker                          bool
	recording                                bool

	statsFetched bool
	stats        UserStats
}

// Session returns the user's session ID.
func (u *User) Session() uint {
	return uint(u.session)
}

// UserID returns the user's ID. Returns an invalid value if the user is not
// registered.
func (u *User) UserID() uint {
	return uint(u.userID)
}

// Name returns the user's name.
func (u *User) Name() string {
	return u.name
}

// Channel returns a pointer to the channel that the user is currently in.
func (u *User) Channel() *Channel {
	return u.channel
}

// IsMuted returns true if the user has been muted.
func (u *User) IsMuted() bool {
	return u.mute
}

// IsDeafened returns true if the user has been deafened.
func (u *User) IsDeafened() bool {
	return u.deaf
}

// IsSuppressed returns true if the user has been suppressed.
func (u *User) IsSuppressed() bool {
	return u.suppress
}

// IsSelfMuted returns true if the user has been muted by him/herself.
func (u *User) IsSelfMuted() bool {
	return u.selfMute
}

// IsSelfDeafened returns true if the user has been deafened by him/herself.
func (u *User) IsSelfDeafened() bool {
	return u.selfDeaf
}

// Comment returns the user's comment.
func (u *User) Comment() string {
	return u.comment
}

// CommentHash returns the user's comment hash. This function can return nil.
func (u *User) CommentHash() []byte {
	return u.commentHash
}

// Hash returns a string representation of the user's certificate hash.
func (u *User) Hash() string {
	return u.hash
}

// Texture returns the user's texture (avatar). This function can return nil.
func (u *User) Texture() []byte {
	return u.texture
}

// SetTexture sets the user's texture.
func (u *User) SetTexture(texture []byte) {
	packet := MumbleProto.UserState{
		Session: &u.session,
		Texture: texture,
	}
	u.client.Send(protoMessage{&packet})
}

// TextureHash returns the user's texture hash. This  can return nil.
func (u *User) TextureHash() []byte {
	return u.textureHash
}

// IsPrioritySpeaker returns true if the user is the priority speaker in the
// channel.
func (u *User) IsPrioritySpeaker() bool {
	return u.prioritySpeaker
}

// IsRecording returns true if the user is recording audio.
func (u *User) IsRecording() bool {
	return u.recording
}

// IsRegistered returns true if the user's certificate has been registered with
// the server. A registered user will have a valid user ID.
func (u *User) IsRegistered() bool {
	return u.userID > 0
}

// Register will register the user with the server. If the client has
// permission to do so, the user will shortly be given a UserID.
func (u *User) Register() {
	packet := MumbleProto.UserState{
		Session: &u.session,
		UserId:  proto.Uint32(0),
	}
	u.client.Send(protoMessage{&packet})
}

// SetComment will set the user's comment to the given string. The user's
// comment will be erased if the comment is set to the empty string.
func (u *User) SetComment(comment string) {
	packet := MumbleProto.UserState{
		Session: &u.session,
		Comment: &comment,
	}
	u.client.Send(protoMessage{&packet})
}

// Move will move the user to the given channel.
func (u *User) Move(channel *Channel) {
	packet := MumbleProto.UserState{
		Session:   &u.session,
		ChannelId: &channel.id,
	}
	u.client.Send(protoMessage{&packet})
}

// Kick will kick the user from the server.
func (u *User) Kick(reason string) {
	packet := MumbleProto.UserRemove{
		Session: &u.session,
		Reason:  &reason,
	}
	u.client.Send(protoMessage{&packet})
}

// Ban will ban the user from the server.
func (u *User) Ban(reason string) {
	packet := MumbleProto.UserRemove{
		Session: &u.session,
		Reason:  &reason,
		Ban:     proto.Bool(true),
	}
	u.client.Send(protoMessage{&packet})
}

// SetMuted sets whether the user can transmit audio or not.
func (u *User) SetMuted(muted bool) {
	packet := MumbleProto.UserState{
		Session: &u.session,
		Mute:    proto.Bool(muted),
	}
	u.client.Send(protoMessage{&packet})
}

// SetDeafened sets whether the user can receive audio or not.
func (u *User) SetDeafened(muted bool) {
	packet := MumbleProto.UserState{
		Session: &u.session,
		Deaf:    proto.Bool(muted),
	}
	u.client.Send(protoMessage{&packet})
}

// SetSelfMuted sets whether the user can transmit audio or not.
//
// This method should only be called on Client.Self().
func (u *User) SetSelfMuted(muted bool) {
	packet := MumbleProto.UserState{
		Session:  &u.session,
		SelfMute: proto.Bool(muted),
	}
	u.client.Send(protoMessage{&packet})
}

// SetSelfDeafened sets whether the user can receive audio or not.
//
// This method should only be called on Client.Self().
func (u *User) SetSelfDeafened(muted bool) {
	packet := MumbleProto.UserState{
		Session:  &u.session,
		SelfDeaf: proto.Bool(muted),
	}
	u.client.Send(protoMessage{&packet})
}

// Stats returns the user's stats, and a boolean value specifying if the stats
// are valid or not.
func (u *User) Stats() (UserStats, bool) {
	return u.stats, u.statsFetched
}

// Request requests user information that has not yet been sent to the client.
// The supported request types are: RequestStats, RequestTexture, and
// RequestComment.
func (u *User) Request(request Request) {
	if (request & RequestStats) != 0 {
		packet := MumbleProto.UserStats{
			Session: &u.session,
		}
		u.client.Send(protoMessage{&packet})
	}

	packet := MumbleProto.RequestBlob{}
	if (request & RequestTexture) != 0 {
		packet.SessionTexture = []uint32{u.session}
	}
	if (request & RequestComment) != 0 {
		packet.SessionComment = []uint32{u.session}
	}
	if packet.SessionTexture != nil || packet.SessionComment != nil {
		u.client.Send(protoMessage{&packet})
	}
}

// Send will send a text message to the user.
func (u *User) Send(message string) {
	textMessage := TextMessage{
		Users:   []*User{u},
		Message: message,
	}
	u.client.Send(&textMessage)
}
<commit_msg>add User.SetPrioritySpeaker<commit_after>package gumble

import (
	"code.google.com/p/goprotobuf/proto"
	"github.com/layeh/gopus"
	"github.com/layeh/gumble/gumble/MumbleProto"
)

// User represents a user that is currently connected to the server.
type User struct {
	client  *Client
	decoder *gopus.Decoder

	session, userID                          uint32
	name                                     string
	channel                                  *Channel
	mute, deaf, suppress, selfMute, selfDeaf bool
	comment                                  string
	commentHash                              []byte
	hash                                     string
	texture, textureHash                     []byte
	prioritySpeaker                          bool
	recording                                bool

	statsFetched bool
	stats        UserStats
}

// Session returns the user's session ID.
func (u *User) Session() uint {
	return uint(u.session)
}

// UserID returns the user's ID. Returns an invalid value if the user is not
// registered.
func (u *User) UserID() uint {
	return uint(u.userID)
}

// Name returns the user's name.
func (u *User) Name() string {
	return u.name
}

// Channel returns a pointer to the channel that the user is currently in.
func (u *User) Channel() *Channel {
	return u.channel
}

// IsMuted returns true if the user has been muted.
func (u *User) IsMuted() bool {
	return u.mute
}

// IsDeafened returns true if the user has been deafened.
func (u *User) IsDeafened() bool {
	return u.deaf
}

// IsSuppressed returns true if the user has been suppressed.
func (u *User) IsSuppressed() bool {
	return u.suppress
}

// IsSelfMuted returns true if the user has been muted by him/herself.
func (u *User) IsSelfMuted() bool {
	return u.selfMute
}

// IsSelfDeafened returns true if the user has been deafened by him/herself.
func (u *User) IsSelfDeafened() bool {
	return u.selfDeaf
}

// Comment returns the user's comment.
func (u *User) Comment() string {
	return u.comment
}

// CommentHash returns the user's comment hash. This function can return nil.
func (u *User) CommentHash() []byte {
	return u.commentHash
}

// Hash returns a string representation of the user's certificate hash.
func (u *User) Hash() string {
	return u.hash
}

// Texture returns the user's texture (avatar). This function can return nil.
func (u *User) Texture() []byte {
	return u.texture
}

// SetTexture sets the user's texture.
func (u *User) SetTexture(texture []byte) {
	packet := MumbleProto.UserState{
		Session: &u.session,
		Texture: texture,
	}
	u.client.Send(protoMessage{&packet})
}

// TextureHash returns the user's texture hash. This  can return nil.
func (u *User) TextureHash() []byte {
	return u.textureHash
}

// IsPrioritySpeaker returns true if the user is the priority speaker in the
// channel.
func (u *User) IsPrioritySpeaker() bool {
	return u.prioritySpeaker
}

// PrioritySpeaker sets if the user is the priority speaker in the channel.
func (u *User) SetPrioritySpeaker(prioritySpeaker bool) {
	packet := MumbleProto.UserState{
		Session:         &u.session,
		PrioritySpeaker: &prioritySpeaker,
	}
	u.client.Send(protoMessage{&packet})
}

// IsRecording returns true if the user is recording audio.
func (u *User) IsRecording() bool {
	return u.recording
}

// IsRegistered returns true if the user's certificate has been registered with
// the server. A registered user will have a valid user ID.
func (u *User) IsRegistered() bool {
	return u.userID > 0
}

// Register will register the user with the server. If the client has
// permission to do so, the user will shortly be given a UserID.
func (u *User) Register() {
	packet := MumbleProto.UserState{
		Session: &u.session,
		UserId:  proto.Uint32(0),
	}
	u.client.Send(protoMessage{&packet})
}

// SetComment will set the user's comment to the given string. The user's
// comment will be erased if the comment is set to the empty string.
func (u *User) SetComment(comment string) {
	packet := MumbleProto.UserState{
		Session: &u.session,
		Comment: &comment,
	}
	u.client.Send(protoMessage{&packet})
}

// Move will move the user to the given channel.
func (u *User) Move(channel *Channel) {
	packet := MumbleProto.UserState{
		Session:   &u.session,
		ChannelId: &channel.id,
	}
	u.client.Send(protoMessage{&packet})
}

// Kick will kick the user from the server.
func (u *User) Kick(reason string) {
	packet := MumbleProto.UserRemove{
		Session: &u.session,
		Reason:  &reason,
	}
	u.client.Send(protoMessage{&packet})
}

// Ban will ban the user from the server.
func (u *User) Ban(reason string) {
	packet := MumbleProto.UserRemove{
		Session: &u.session,
		Reason:  &reason,
		Ban:     proto.Bool(true),
	}
	u.client.Send(protoMessage{&packet})
}

// SetMuted sets whether the user can transmit audio or not.
func (u *User) SetMuted(muted bool) {
	packet := MumbleProto.UserState{
		Session: &u.session,
		Mute:    proto.Bool(muted),
	}
	u.client.Send(protoMessage{&packet})
}

// SetDeafened sets whether the user can receive audio or not.
func (u *User) SetDeafened(muted bool) {
	packet := MumbleProto.UserState{
		Session: &u.session,
		Deaf:    proto.Bool(muted),
	}
	u.client.Send(protoMessage{&packet})
}

// SetSelfMuted sets whether the user can transmit audio or not.
//
// This method should only be called on Client.Self().
func (u *User) SetSelfMuted(muted bool) {
	packet := MumbleProto.UserState{
		Session:  &u.session,
		SelfMute: proto.Bool(muted),
	}
	u.client.Send(protoMessage{&packet})
}

// SetSelfDeafened sets whether the user can receive audio or not.
//
// This method should only be called on Client.Self().
func (u *User) SetSelfDeafened(muted bool) {
	packet := MumbleProto.UserState{
		Session:  &u.session,
		SelfDeaf: proto.Bool(muted),
	}
	u.client.Send(protoMessage{&packet})
}

// Stats returns the user's stats, and a boolean value specifying if the stats
// are valid or not.
func (u *User) Stats() (UserStats, bool) {
	return u.stats, u.statsFetched
}

// Request requests user information that has not yet been sent to the client.
// The supported request types are: RequestStats, RequestTexture, and
// RequestComment.
func (u *User) Request(request Request) {
	if (request & RequestStats) != 0 {
		packet := MumbleProto.UserStats{
			Session: &u.session,
		}
		u.client.Send(protoMessage{&packet})
	}

	packet := MumbleProto.RequestBlob{}
	if (request & RequestTexture) != 0 {
		packet.SessionTexture = []uint32{u.session}
	}
	if (request & RequestComment) != 0 {
		packet.SessionComment = []uint32{u.session}
	}
	if packet.SessionTexture != nil || packet.SessionComment != nil {
		u.client.Send(protoMessage{&packet})
	}
}

// Send will send a text message to the user.
func (u *User) Send(message string) {
	textMessage := TextMessage{
		Users:   []*User{u},
		Message: message,
	}
	u.client.Send(&textMessage)
}
<|endoftext|>


<commit_before>/*
Copyright 2017 Ankyra

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 core

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/ankyra/escape-core/parsers"
	"github.com/ankyra/escape-core/script"
	"github.com/ankyra/escape-core/templates"
	"github.com/ankyra/escape-core/util"
	"github.com/ankyra/escape-core/variables"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"strings"
)

const CurrentApiVersion = 3

type ExecStage struct {
	Script string `json:"script"`
}

type ConsumerConfig struct {
	Name string `json:"name"`
}

func NewConsumerConfig(name string) *ConsumerConfig {
	return &ConsumerConfig{name}
}

type ProviderConfig struct {
	Name string `json:"name"`
}

func NewProviderConfig(name string) *ProviderConfig {
	return &ProviderConfig{name}
}

type ExtensionConfig struct {
	ReleaseId string `json:"release_id"`
}

func NewExtensionConfig(releaseId string) *ExtensionConfig {
	return &ExtensionConfig{releaseId}
}

type ReleaseMetadata struct {
	ApiVersion  int               `json:"api_version"`
	Branch      string            `json:"branch"`
	Description string            `json:"description"`
	Files       map[string]string `json:"files", {}`
	Logo        string            `json:"logo"`
	Name        string            `json:"name"`
	Revision    string            `json:"git_revision"`
	Metadata    map[string]string `json:"metadata"`
	Version     string            `json:"version"`
	Repository  string            `json:"repository"`

	Consumes    []*ConsumerConfig     `json:"consumes"`
	Depends     []*DependencyConfig   `json:"depends"`
	Errands     map[string]*Errand    `json:"errands"`
	Extends     []*ExtensionConfig    `json:"extends"`
	Inputs      []*variables.Variable `json:"inputs"`
	Outputs     []*variables.Variable `json:"outputs"`
	Project     string                `json:"project"`
	Provides    []*ProviderConfig     `json:"provides"`
	Stages      map[string]*ExecStage `json:"stages"`
	Templates   []*templates.Template `json:"templates"`
	VariableCtx map[string]string     `json:"variable_context"`
}

func NewEmptyReleaseMetadata() *ReleaseMetadata {
	return &ReleaseMetadata{
		ApiVersion: CurrentApiVersion,
		Files:      map[string]string{},
		Metadata:   map[string]string{},

		Consumes:    []*ConsumerConfig{},
		Depends:     []*DependencyConfig{},
		Errands:     map[string]*Errand{},
		Extends:     []*ExtensionConfig{},
		Inputs:      []*variables.Variable{},
		Outputs:     []*variables.Variable{},
		Provides:    []*ProviderConfig{},
		Stages:      map[string]*ExecStage{},
		Templates:   []*templates.Template{},
		VariableCtx: map[string]string{},
	}
}

func NewReleaseMetadata(name, version string) *ReleaseMetadata {
	m := NewEmptyReleaseMetadata()
	m.Name = name
	m.Version = version
	m.Project = "_"
	return m
}

func NewReleaseMetadataFromJsonString(content string) (*ReleaseMetadata, error) {
	result := NewEmptyReleaseMetadata()
	if err := json.Unmarshal([]byte(content), &result); err != nil {
		return nil, fmt.Errorf("Couldn't unmarshal JSON release metadata: %s", err.Error())
	}
	return result, validate(result)
}

func NewReleaseMetadataFromFile(metadataFile string) (*ReleaseMetadata, error) {
	if !util.PathExists(metadataFile) {
		return nil, errors.New("Release metadata file " + metadataFile + " does not exist")
	}
	content, err := ioutil.ReadFile(metadataFile)
	if err != nil {
		return nil, err
	}
	return NewReleaseMetadataFromJsonString(string(content))
}

func (m *ReleaseMetadata) Validate() error {
	return validate(m)
}

func validate(m *ReleaseMetadata) error {
	if m == nil {
		return fmt.Errorf("Missing release metadata")
	}
	if m.Name == "" {
		return fmt.Errorf("Missing name field in release metadata")
	}
	if err := validateName(m.Name); err != nil {
		return err
	}
	if m.Version == "" {
		return fmt.Errorf("Missing version field in release metadata")
	}
	if m.Project == "" {
		m.Project = "_"
	}
	if err := validateName(m.Project); m.Project != "_" && err != nil {
		return err
	}
	if m.ApiVersion <= 0 || m.ApiVersion > CurrentApiVersion {
		return fmt.Errorf("The release metadata is compiled with a version of Escape targetting API version v%d, but this build supports up to v%d", m.ApiVersion, CurrentApiVersion)
	}
	if err := parsers.ValidateVersion(m.Version); err != nil {
		return err
	}
	for _, i := range m.Inputs {
		if err := i.Validate(); err != nil {
			return err
		}
	}
	for _, i := range m.Outputs {
		if err := i.Validate(); err != nil {
			return err
		}
	}
	for _, d := range m.Depends {
		if err := d.Validate(m); err != nil {
			return err
		}
	}
	return nil
}

func validateName(name string) error {
	re := regexp.MustCompile("^[a-z]+[a-z0-9-_]+$")
	if !re.MatchString(name) {
		return fmt.Errorf("Invalid name '%s'", name)
	}
	protectedNames := map[string]bool{
		"this":    false,
		"string":  false,
		"integer": false,
		"list":    false,
		"dict":    false,
		"func":    false,
	}
	if _, found := protectedNames[name]; found {
		return fmt.Errorf("The name '%s' is a protected variable.", name)
	}
	return nil
}

func (m *ReleaseMetadata) AddExtension(releaseId string) {
	for _, e := range m.Extends {
		if e.ReleaseId == releaseId {
			return
		}
	}
	m.Extends = append(m.Extends, NewExtensionConfig(releaseId))
}

func (m *ReleaseMetadata) GetExtensions() []string {
	result := []string{}
	for _, ext := range m.Extends {
		result = append(result, ext.ReleaseId)
	}
	return result
}

func (m *ReleaseMetadata) GetStage(stage string) *ExecStage {
	result, ok := m.Stages[stage]
	if !ok {
		result = &ExecStage{}
		m.Stages[stage] = result
	}
	return result
}

func (m *ReleaseMetadata) SetStage(stage, script string) {
	if script == "" {
		return
	}
	st := m.GetStage(stage)
	st.Script = script
}

func (m *ReleaseMetadata) GetScript(stage string) string {
	return m.GetStage(stage).Script
}

func (m *ReleaseMetadata) AddConsumes(c string) {
	for _, consumer := range m.Consumes {
		if consumer.Name == c {
			return
		}
	}
	m.Consumes = append(m.Consumes, NewConsumerConfig(c))
}

func (m *ReleaseMetadata) SetConsumes(c []string) {
	for _, consumer := range c {
		m.AddConsumes(consumer)
	}
}

func (m *ReleaseMetadata) GetConsumes() []string {
	result := []string{}
	for _, c := range m.Consumes {
		result = append(result, c.Name)
	}
	return result
}

func (m *ReleaseMetadata) GetErrands() map[string]*Errand {
	result := map[string]*Errand{}
	for key, val := range m.Errands {
		result[key] = val
	}
	return result
}

func (m *ReleaseMetadata) GetInputs() []*variables.Variable {
	result := []*variables.Variable{}
	for _, i := range m.Inputs {
		result = append(result, i)
	}
	return result
}

func (m *ReleaseMetadata) GetOutputs() []*variables.Variable {
	result := []*variables.Variable{}
	for _, i := range m.Outputs {
		result = append(result, i)
	}
	return result
}

func (m *ReleaseMetadata) AddProvides(p string) {
	for _, provider := range m.Provides {
		if provider.Name == p {
			return
		}
	}
	m.Provides = append(m.Provides, NewProviderConfig(p))
}

func (m *ReleaseMetadata) GetProvides() []string {
	result := []string{}
	for _, c := range m.Provides {
		result = append(result, c.Name)
	}
	return result
}

func (m *ReleaseMetadata) SetProvides(p []string) {
	for _, provider := range p {
		m.AddProvides(provider)
	}
}

func (m *ReleaseMetadata) AddDependency(dep *DependencyConfig) {
	m.Depends = append(m.Depends, dep)
}

func (m *ReleaseMetadata) AddDependencyFromString(dep string) {
	m.Depends = append(m.Depends, NewDependencyConfig(dep))
}

func (m *ReleaseMetadata) SetDependencies(deps []string) {
	m.Depends = []*DependencyConfig{}
	for _, d := range deps {
		m.AddDependencyFromString(d)
	}
}

func (m *ReleaseMetadata) GetVariableContext() map[string]string {
	if m.VariableCtx == nil {
		return map[string]string{}
	}
	return m.VariableCtx
}

func (m *ReleaseMetadata) SetVariableInContext(v string, ref string) {
	ctx := m.GetVariableContext()
	ctx[v] = ref
	m.VariableCtx = ctx
}

func (m *ReleaseMetadata) GetReleaseId() string {
	return m.Name + "-v" + m.Version
}

func (m *ReleaseMetadata) GetQualifiedReleaseId() string {
	return m.GetProject() + "/" + m.Name + "-v" + m.Version
}

func (m *ReleaseMetadata) GetProject() string {
	if m.Project == "" {
		return "_"
	}
	return m.Project
}

func (m *ReleaseMetadata) GetVersionlessReleaseId() string {
	return m.GetProject() + "/" + m.Name
}

func (m *ReleaseMetadata) AddInputVariable(input *variables.Variable) {
	for _, i := range m.Inputs {
		if i.Id == input.Id {
			i.Default = input.Default
			return
		}
	}
	m.Inputs = append(m.Inputs, input)
}

func (m *ReleaseMetadata) AddOutputVariable(output *variables.Variable) {
	for _, i := range m.Outputs {
		if i.Id == output.Id {
			return
		}
	}
	m.Outputs = append(m.Outputs, output)
}

func (m *ReleaseMetadata) ToJson() string {
	str, err := json.MarshalIndent(m, "", "   ")
	if err != nil {
		panic(err)
	}
	return string(str)
}

func (m *ReleaseMetadata) ToDict() (map[string]interface{}, error) {
	asJson := []byte(m.ToJson())
	result := map[string]interface{}{}
	if err := json.Unmarshal(asJson, &result); err != nil {
		return nil, fmt.Errorf("Couldn't marshal release metadata: %s. This is a bug in Escape", err.Error())
	}
	return result, nil
}

func (m *ReleaseMetadata) WriteJsonFile(path string) error {
	contents := []byte(m.ToJson())
	return ioutil.WriteFile(path, contents, 0644)
}

func (m *ReleaseMetadata) AddFileWithDigest(path, hexDigest string) {
	m.Files[path] = hexDigest
}

func (m *ReleaseMetadata) ToDependency() *Dependency {
	return NewDependencyFromMetadata(m)
}

func (m *ReleaseMetadata) GetDirectories() []string {
	dirs := map[string]bool{}
	for file := range m.Files {
		dir, _ := filepath.Split(file)
		dirs[dir] = true
		root := ""
		for _, d := range strings.Split(dir, "/") {
			if d != "" {
				root += d + "/"
				dirs[root] = true
			}
		}
	}
	result := []string{}
	for d := range dirs {
		if d != "" {
			result = append(result, d)
		}
	}
	return result
}

func (m *ReleaseMetadata) ToScript() script.Script {
	return script.LiftDict(m.ToScriptMap())
}

func (m *ReleaseMetadata) ToScriptMap() map[string]script.Script {
	metadataDict := map[string]script.Script{}
	for key, val := range m.Metadata {
		metadataDict[key] = script.LiftString(val)
	}
	return map[string]script.Script{
		"metadata": script.LiftDict(metadataDict),

		"branch":              script.LiftString(m.Branch),
		"description":         script.LiftString(m.Description),
		"logo":                script.LiftString(m.Logo),
		"name":                script.LiftString(m.Name),
		"revision":            script.LiftString(m.Revision),
		"repository":          script.LiftString(m.Repository),
		"version":             script.LiftString(m.Version),
		"release":             script.LiftString(m.GetReleaseId()),
		"versionless_release": script.LiftString(m.GetVersionlessReleaseId()),
		"id": script.LiftString(m.GetQualifiedReleaseId()),
	}
}
<commit_msg>Bump metadata version<commit_after>/*
Copyright 2017 Ankyra

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 core

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/ankyra/escape-core/parsers"
	"github.com/ankyra/escape-core/script"
	"github.com/ankyra/escape-core/templates"
	"github.com/ankyra/escape-core/util"
	"github.com/ankyra/escape-core/variables"
	"io/ioutil"
	"path/filepath"
	"regexp"
	"strings"
)

const CurrentApiVersion = 4

type ExecStage struct {
	Script string `json:"script"`
}

type ConsumerConfig struct {
	Name string `json:"name"`
}

func NewConsumerConfig(name string) *ConsumerConfig {
	return &ConsumerConfig{name}
}

type ProviderConfig struct {
	Name string `json:"name"`
}

func NewProviderConfig(name string) *ProviderConfig {
	return &ProviderConfig{name}
}

type ExtensionConfig struct {
	ReleaseId string `json:"release_id"`
}

func NewExtensionConfig(releaseId string) *ExtensionConfig {
	return &ExtensionConfig{releaseId}
}

type ReleaseMetadata struct {
	ApiVersion  int               `json:"api_version"`
	Branch      string            `json:"branch"`
	Description string            `json:"description"`
	Files       map[string]string `json:"files", {}`
	Logo        string            `json:"logo"`
	Name        string            `json:"name"`
	Revision    string            `json:"git_revision"`
	Metadata    map[string]string `json:"metadata"`
	Version     string            `json:"version"`
	Repository  string            `json:"repository"`

	Consumes    []*ConsumerConfig     `json:"consumes"`
	Depends     []*DependencyConfig   `json:"depends"`
	Errands     map[string]*Errand    `json:"errands"`
	Extends     []*ExtensionConfig    `json:"extends"`
	Inputs      []*variables.Variable `json:"inputs"`
	Outputs     []*variables.Variable `json:"outputs"`
	Project     string                `json:"project"`
	Provides    []*ProviderConfig     `json:"provides"`
	Stages      map[string]*ExecStage `json:"stages"`
	Templates   []*templates.Template `json:"templates"`
	VariableCtx map[string]string     `json:"variable_context"`
}

func NewEmptyReleaseMetadata() *ReleaseMetadata {
	return &ReleaseMetadata{
		ApiVersion: CurrentApiVersion,
		Files:      map[string]string{},
		Metadata:   map[string]string{},

		Consumes:    []*ConsumerConfig{},
		Depends:     []*DependencyConfig{},
		Errands:     map[string]*Errand{},
		Extends:     []*ExtensionConfig{},
		Inputs:      []*variables.Variable{},
		Outputs:     []*variables.Variable{},
		Provides:    []*ProviderConfig{},
		Stages:      map[string]*ExecStage{},
		Templates:   []*templates.Template{},
		VariableCtx: map[string]string{},
	}
}

func NewReleaseMetadata(name, version string) *ReleaseMetadata {
	m := NewEmptyReleaseMetadata()
	m.Name = name
	m.Version = version
	m.Project = "_"
	return m
}

func NewReleaseMetadataFromJsonString(content string) (*ReleaseMetadata, error) {
	result := NewEmptyReleaseMetadata()
	if err := json.Unmarshal([]byte(content), &result); err != nil {
		return nil, fmt.Errorf("Couldn't unmarshal JSON release metadata: %s", err.Error())
	}
	return result, validate(result)
}

func NewReleaseMetadataFromFile(metadataFile string) (*ReleaseMetadata, error) {
	if !util.PathExists(metadataFile) {
		return nil, errors.New("Release metadata file " + metadataFile + " does not exist")
	}
	content, err := ioutil.ReadFile(metadataFile)
	if err != nil {
		return nil, err
	}
	return NewReleaseMetadataFromJsonString(string(content))
}

func (m *ReleaseMetadata) Validate() error {
	return validate(m)
}

func validate(m *ReleaseMetadata) error {
	if m == nil {
		return fmt.Errorf("Missing release metadata")
	}
	if m.Name == "" {
		return fmt.Errorf("Missing name field in release metadata")
	}
	if err := validateName(m.Name); err != nil {
		return err
	}
	if m.Version == "" {
		return fmt.Errorf("Missing version field in release metadata")
	}
	if m.Project == "" {
		m.Project = "_"
	}
	if err := validateName(m.Project); m.Project != "_" && err != nil {
		return err
	}
	if m.ApiVersion <= 0 || m.ApiVersion > CurrentApiVersion {
		return fmt.Errorf("The release metadata is compiled with a version of Escape targetting API version v%d, but this build supports up to v%d", m.ApiVersion, CurrentApiVersion)
	}
	if err := parsers.ValidateVersion(m.Version); err != nil {
		return err
	}
	for _, i := range m.Inputs {
		if err := i.Validate(); err != nil {
			return err
		}
	}
	for _, i := range m.Outputs {
		if err := i.Validate(); err != nil {
			return err
		}
	}
	for _, d := range m.Depends {
		if err := d.Validate(m); err != nil {
			return err
		}
	}
	return nil
}

func validateName(name string) error {
	re := regexp.MustCompile("^[a-z]+[a-z0-9-_]+$")
	if !re.MatchString(name) {
		return fmt.Errorf("Invalid name '%s'", name)
	}
	protectedNames := map[string]bool{
		"this":    false,
		"string":  false,
		"integer": false,
		"list":    false,
		"dict":    false,
		"func":    false,
	}
	if _, found := protectedNames[name]; found {
		return fmt.Errorf("The name '%s' is a protected variable.", name)
	}
	return nil
}

func (m *ReleaseMetadata) AddExtension(releaseId string) {
	for _, e := range m.Extends {
		if e.ReleaseId == releaseId {
			return
		}
	}
	m.Extends = append(m.Extends, NewExtensionConfig(releaseId))
}

func (m *ReleaseMetadata) GetExtensions() []string {
	result := []string{}
	for _, ext := range m.Extends {
		result = append(result, ext.ReleaseId)
	}
	return result
}

func (m *ReleaseMetadata) GetStage(stage string) *ExecStage {
	result, ok := m.Stages[stage]
	if !ok {
		result = &ExecStage{}
		m.Stages[stage] = result
	}
	return result
}

func (m *ReleaseMetadata) SetStage(stage, script string) {
	if script == "" {
		return
	}
	st := m.GetStage(stage)
	st.Script = script
}

func (m *ReleaseMetadata) GetScript(stage string) string {
	return m.GetStage(stage).Script
}

func (m *ReleaseMetadata) AddConsumes(c string) {
	for _, consumer := range m.Consumes {
		if consumer.Name == c {
			return
		}
	}
	m.Consumes = append(m.Consumes, NewConsumerConfig(c))
}

func (m *ReleaseMetadata) SetConsumes(c []string) {
	for _, consumer := range c {
		m.AddConsumes(consumer)
	}
}

func (m *ReleaseMetadata) GetConsumes() []string {
	result := []string{}
	for _, c := range m.Consumes {
		result = append(result, c.Name)
	}
	return result
}

func (m *ReleaseMetadata) GetErrands() map[string]*Errand {
	result := map[string]*Errand{}
	for key, val := range m.Errands {
		result[key] = val
	}
	return result
}

func (m *ReleaseMetadata) GetInputs() []*variables.Variable {
	result := []*variables.Variable{}
	for _, i := range m.Inputs {
		result = append(result, i)
	}
	return result
}

func (m *ReleaseMetadata) GetOutputs() []*variables.Variable {
	result := []*variables.Variable{}
	for _, i := range m.Outputs {
		result = append(result, i)
	}
	return result
}

func (m *ReleaseMetadata) AddProvides(p string) {
	for _, provider := range m.Provides {
		if provider.Name == p {
			return
		}
	}
	m.Provides = append(m.Provides, NewProviderConfig(p))
}

func (m *ReleaseMetadata) GetProvides() []string {
	result := []string{}
	for _, c := range m.Provides {
		result = append(result, c.Name)
	}
	return result
}

func (m *ReleaseMetadata) SetProvides(p []string) {
	for _, provider := range p {
		m.AddProvides(provider)
	}
}

func (m *ReleaseMetadata) AddDependency(dep *DependencyConfig) {
	m.Depends = append(m.Depends, dep)
}

func (m *ReleaseMetadata) AddDependencyFromString(dep string) {
	m.Depends = append(m.Depends, NewDependencyConfig(dep))
}

func (m *ReleaseMetadata) SetDependencies(deps []string) {
	m.Depends = []*DependencyConfig{}
	for _, d := range deps {
		m.AddDependencyFromString(d)
	}
}

func (m *ReleaseMetadata) GetVariableContext() map[string]string {
	if m.VariableCtx == nil {
		return map[string]string{}
	}
	return m.VariableCtx
}

func (m *ReleaseMetadata) SetVariableInContext(v string, ref string) {
	ctx := m.GetVariableContext()
	ctx[v] = ref
	m.VariableCtx = ctx
}

func (m *ReleaseMetadata) GetReleaseId() string {
	return m.Name + "-v" + m.Version
}

func (m *ReleaseMetadata) GetQualifiedReleaseId() string {
	return m.GetProject() + "/" + m.Name + "-v" + m.Version
}

func (m *ReleaseMetadata) GetProject() string {
	if m.Project == "" {
		return "_"
	}
	return m.Project
}

func (m *ReleaseMetadata) GetVersionlessReleaseId() string {
	return m.GetProject() + "/" + m.Name
}

func (m *ReleaseMetadata) AddInputVariable(input *variables.Variable) {
	for _, i := range m.Inputs {
		if i.Id == input.Id {
			i.Default = input.Default
			return
		}
	}
	m.Inputs = append(m.Inputs, input)
}

func (m *ReleaseMetadata) AddOutputVariable(output *variables.Variable) {
	for _, i := range m.Outputs {
		if i.Id == output.Id {
			return
		}
	}
	m.Outputs = append(m.Outputs, output)
}

func (m *ReleaseMetadata) ToJson() string {
	str, err := json.MarshalIndent(m, "", "   ")
	if err != nil {
		panic(err)
	}
	return string(str)
}

func (m *ReleaseMetadata) ToDict() (map[string]interface{}, error) {
	asJson := []byte(m.ToJson())
	result := map[string]interface{}{}
	if err := json.Unmarshal(asJson, &result); err != nil {
		return nil, fmt.Errorf("Couldn't marshal release metadata: %s. This is a bug in Escape", err.Error())
	}
	return result, nil
}

func (m *ReleaseMetadata) WriteJsonFile(path string) error {
	contents := []byte(m.ToJson())
	return ioutil.WriteFile(path, contents, 0644)
}

func (m *ReleaseMetadata) AddFileWithDigest(path, hexDigest string) {
	m.Files[path] = hexDigest
}

func (m *ReleaseMetadata) ToDependency() *Dependency {
	return NewDependencyFromMetadata(m)
}

func (m *ReleaseMetadata) GetDirectories() []string {
	dirs := map[string]bool{}
	for file := range m.Files {
		dir, _ := filepath.Split(file)
		dirs[dir] = true
		root := ""
		for _, d := range strings.Split(dir, "/") {
			if d != "" {
				root += d + "/"
				dirs[root] = true
			}
		}
	}
	result := []string{}
	for d := range dirs {
		if d != "" {
			result = append(result, d)
		}
	}
	return result
}

func (m *ReleaseMetadata) ToScript() script.Script {
	return script.LiftDict(m.ToScriptMap())
}

func (m *ReleaseMetadata) ToScriptMap() map[string]script.Script {
	metadataDict := map[string]script.Script{}
	for key, val := range m.Metadata {
		metadataDict[key] = script.LiftString(val)
	}
	return map[string]script.Script{
		"metadata": script.LiftDict(metadataDict),

		"branch":              script.LiftString(m.Branch),
		"description":         script.LiftString(m.Description),
		"logo":                script.LiftString(m.Logo),
		"name":                script.LiftString(m.Name),
		"revision":            script.LiftString(m.Revision),
		"repository":          script.LiftString(m.Repository),
		"version":             script.LiftString(m.Version),
		"release":             script.LiftString(m.GetReleaseId()),
		"versionless_release": script.LiftString(m.GetVersionlessReleaseId()),
		"id": script.LiftString(m.GetQualifiedReleaseId()),
	}
}
<|endoftext|>


<commit_before>// this file contains a pre-processor to pull some stuff out of the markdown file before parsing it

package gnosis

import (
	"bufio"
	"bytes"
	"errors"
	"html/template"
	"os"
)

type PageMetadata struct {
	Keywords map[string]bool
	Topics   map[string]bool
	Loaded   bool
	Page     []byte
}

// takes a single line of input and determines if it's a top level markdown header
func (pdata *PageMetadata) lineIsTitle(line []byte) bool {
	// trim any whitespace from the start and the end of the line
	line = bytes.TrimSpace(line)

	// run through all of the ='s - make sure they're all correct
	for i := 0; i < len(line); i++ {
		if line[i] != '=' {
			return false
		}
	}

	// if you got here, it should all be legit
	return true
}

// take a given line, and check it against every possible type of tag
func (pdata *PageMetadata) processMetadata(line []byte) {
	pdata.checkMatch(line, []byte("tag"), &pdata.Topics)
	pdata.checkMatch(line, []byte("topic"), &pdata.Topics)
	pdata.checkMatch(line, []byte("category"), &pdata.Topics)

	pdata.checkMatch(line, []byte("keyword"), &pdata.Keywords)
	pdata.checkMatch(line, []byte("keywords"), &pdata.Keywords)
	pdata.checkMatch(line, []byte("meta"), &pdata.Keywords)
}

func (pdata *PageMetadata) LoadPage(pageName string) error {
	f, err := os.Open(pageName)
	reader := bufio.NewReader(f)
	upperLine, fullLine, err := reader.ReadLine()

	// inspect the first line you read
	if err != nil {
		return err
	} else if !fullLine {
		return errors.New("first line I read wasn't a full line")
	} else if pdata.lineIsTitle(upperLine) {
		return errors.New("first line looks an awful lot like the underside of the title o.O")
	}

	lowerLine, fullLine, err := reader.ReadLine()

	// inspect the lower line
	if err != nil {
		return err
	} else if !fullLine {
		return errors.New("second line I read wasn't a full line")
	} else if pdata.lineIsTitle(lowerLine) {

		// read the rest of the page
		var restOfPage []byte
		_, err = reader.Read(restOfPage)
		if err != nil {
			return err
		}
		// if the second line is a title, read the rest of the page in
		// you don't have any metadata to work with here, move on
		pdata.Page = bytes.Join([][]byte{upperLine, lowerLine, restOfPage}, []byte("\n"))

		// you've successfully loaded the page - so return nothing
		pdata.Loaded = true
		return nil
	}

	// if you're at this point, the first line is metadata
	// you gotta process it and work with the next line
	// so let's just read through the file until we hit the title
	for !pdata.lineIsTitle(lowerLine) {
		// process the line
		pdata.processMetadata(upperLine)
		// shift the lower line up
		upperLine = lowerLine
		// read in a new lower line
		lowerLine, fullLine, err = reader.ReadLine()
		if err != nil {
			return err
		} else if !fullLine {
			return errors.New("I filled my buffer with a line")
		}
	}

	// by this point, I should have read everything in - let's read the rest and just return it
	var restOfPage []byte
	_, err = reader.Read(restOfPage)
	pdata.Page = bytes.Join([][]byte{upperLine, lowerLine, restOfPage}, []byte("\n"))

	return err
}

func (pdata *PageMetadata) checkMatch(input []byte, looking []byte, tracker *map[string]bool) {
	// trim off any blank spaces at the start of the line
	value := bytes.TrimSpace(input)

	// should be a substring match based on the start of the array
	if bytes.Equal(input[:len(looking)], looking) {

		// trim off the target from the []byte
		value = input[len(looking):]

		// trim spaces at the start and at the end
		value = bytes.TrimSpace(value)

		if value[0] == ':' || value[0] == '=' {
			value = bytes.Trim(value, " \t\n=:")
		}

		// replace any spaces in the middle with -'s
		value = bytes.Replace(value, []byte(" "), []byte("-"), -1)

		// suppress any double dashes
		for i := 0; i < len(value)-1; i++ {
			if value[i] == '-' && value[i+1] == '-' {
				value = append(value[:i], value[i+1:]...)
			}
		}

		// now just add the value to the array that you're tracking
		if *tracker != nil {
			(*tracker)[string(value)] = true
		} else {
			*tracker = map[string]bool{string(value): true}
		}
	}
}

// returns all the tags within a list as an array of strings
func (pdata *PageMetadata) ListMeta() ([]string, []string) {
	topics := []string{}
	for oneTag, _ := range pdata.Topics {
		topics = append(topics[:], oneTag)
	}

	keywords := []string{}
	for oneKeyword, _ := range pdata.Keywords {
		keywords = append(keywords[:], oneKeyword)
	}

	return topics, keywords
}

// return the bytes to display the tags on the page
// takes the prefix for the tags
func (pdata *PageMetadata) PrintTopics(tagPrefix string) template.HTML {
	response := []byte{}
	openingTag := []byte("
")
	closingTag := []byte("
")
	for oneTag, _ := range pdata.Topics {
		response = bytes.Join([][]byte{openingTag, []byte(tagPrefix), []byte(oneTag), closingTag}, []byte(""))
	}
	return template.HTML(response)
}

// returns the bytes to add the keywrods to the html output
func (pdata *PageMetadata) PrintKeywords() template.HTML {
	response := []byte("<meta name='keywords' content='")
	for oneKeyword, _ := range pdata.Keywords {
		response = bytes.Join([][]byte{response, []byte(oneKeyword)}, []byte(","))
	}
	// clean up the end of the string and add the ending tag
	response = bytes.TrimSuffix(response, []byte{','})
	response = append(response, []byte("'>")...)

	return template.HTML(response)
}

// runs through all restricted tags, and looks for a match
// if matched, returns true, otherwise false
func (pdata *PageMetadata) MatchedTag(checkTags []string) bool {
	for _, tag := range checkTags {
		if pdata.Topics[tag] == true {
			return true
		}
	}
	return false
}
<commit_msg>got everything working, now working on LoadPage<commit_after>// this file contains a pre-processor to pull some stuff out of the markdown file before parsing it

package gnosis

import (
	"bufio"
	"bytes"
	"errors"
	"html/template"
	"os"
)

type PageMetadata struct {
	Keywords map[string]bool
	Topics   map[string]bool
	Loaded   bool
	Page     []byte
}

// takes a single line of input and determines if it's a top level markdown header
func (pdata *PageMetadata) lineIsTitle(line []byte) bool {
	// trim any whitespace from the start and the end of the line
	line = bytes.TrimSpace(line)

	// run through all of the ='s - make sure they're all correct
	for i := 0; i < len(line); i++ {
		if line[i] != '=' {
			return false
		}
	}

	// if you got here, it should all be legit
	return true
}

// take a given line, and check it against every possible type of tag
func (pdata *PageMetadata) processMetadata(line []byte) {
	pdata.checkMatch(line, []byte("tag"), &pdata.Topics)
	pdata.checkMatch(line, []byte("topic"), &pdata.Topics)
	pdata.checkMatch(line, []byte("category"), &pdata.Topics)

	pdata.checkMatch(line, []byte("keyword"), &pdata.Keywords)
	pdata.checkMatch(line, []byte("keywords"), &pdata.Keywords)
	pdata.checkMatch(line, []byte("meta"), &pdata.Keywords)
}

func (pdata *PageMetadata) checkMatch(input []byte, looking []byte, tracker *map[string]bool) {
	// trim off any blank spaces at the start of the line
	value := bytes.TrimSpace(input)

	// should be a substring match based on the start of the array
	if bytes.Equal(input[:len(looking)], looking) {

		// trim off the target from the []byte
		value = input[len(looking):]

		// trim spaces at the start and at the end
		value = bytes.TrimSpace(value)

		if value[0] == ':' || value[0] == '=' {
			value = bytes.Trim(value, " \t\n=:")
		}

		// replace any spaces in the middle with -'s
		value = bytes.Replace(value, []byte(" "), []byte("-"), -1)

		// suppress any double dashes
		for i := 0; i < len(value)-1; i++ {
			if value[i] == '-' && value[i+1] == '-' {
				value = append(value[:i], value[i+1:]...)
			}
		}

		// now just add the value to the array that you're tracking
		if *tracker != nil {
			(*tracker)[string(value)] = true
		} else {
			*tracker = map[string]bool{string(value): true}
		}
	}
}

func (pdata *PageMetadata) LoadPage(pageName string) error {
	f, err := os.Open(pageName)
	reader := bufio.NewReader(f)
	upperLine, fullLine, err := reader.ReadLine()

	// inspect the first line you read
	if err != nil {
		return err
	} else if !fullLine {
		return errors.New("first line I read wasn't a full line")
	} else if pdata.lineIsTitle(upperLine) {
		return errors.New("first line looks an awful lot like the underside of the title o.O")
	}

	lowerLine, fullLine, err := reader.ReadLine()

	// inspect the lower line
	if err != nil {
		return err
	} else if !fullLine {
		return errors.New("second line I read wasn't a full line")
	} else if pdata.lineIsTitle(lowerLine) {

		// read the rest of the page
		var restOfPage []byte
		_, err = reader.Read(restOfPage)
		if err != nil {
			return err
		}
		// if the second line is a title, read the rest of the page in
		// you don't have any metadata to work with here, move on
		pdata.Page = bytes.Join([][]byte{upperLine, lowerLine, restOfPage}, []byte("\n"))

		// you've successfully loaded the page - so return nothing
		pdata.Loaded = true
		return nil
	}

	// if you're at this point, the first line is metadata
	// you gotta process it and work with the next line
	// so let's just read through the file until we hit the title
	for !pdata.lineIsTitle(lowerLine) {
		// process the line
		pdata.processMetadata(upperLine)
		// shift the lower line up
		upperLine = lowerLine
		// read in a new lower line
		lowerLine, fullLine, err = reader.ReadLine()
		if err != nil {
			return err
		} else if !fullLine {
			return errors.New("I filled my buffer with a line")
		}
	}

	// by this point, I should have read everything in - let's read the rest and just return it
	var restOfPage []byte
	_, err = reader.Read(restOfPage)
	pdata.Page = bytes.Join([][]byte{upperLine, lowerLine, restOfPage}, []byte("\n"))

	return err
}

// returns all the tags within a list as an array of strings
func (pdata *PageMetadata) ListMeta() ([]string, []string) {
	topics := []string{}
	for oneTag, _ := range pdata.Topics {
		topics = append(topics[:], oneTag)
	}

	keywords := []string{}
	for oneKeyword, _ := range pdata.Keywords {
		keywords = append(keywords[:], oneKeyword)
	}

	return topics, keywords
}

// return the bytes to display the tags on the page
// takes the prefix for the tags
func (pdata *PageMetadata) PrintTopics(tagPrefix string) template.HTML {
	response := []byte{}
	openingTag := []byte("
")
	closingTag := []byte("
")
	for oneTag, _ := range pdata.Topics {
		response = bytes.Join([][]byte{openingTag, []byte(tagPrefix), []byte(oneTag), closingTag}, []byte(""))
	}
	return template.HTML(response)
}

// returns the bytes to add the keywrods to the html output
func (pdata *PageMetadata) PrintKeywords() template.HTML {
	response := []byte("<meta name='keywords' content='")
	for oneKeyword, _ := range pdata.Keywords {
		response = bytes.Join([][]byte{response, []byte(oneKeyword)}, []byte(","))
	}
	// clean up the end of the string and add the ending tag
	response = bytes.TrimSuffix(response, []byte{','})
	response = append(response, []byte("'>")...)

	return template.HTML(response)
}

// runs through all restricted tags, and looks for a match
// if matched, returns true, otherwise false
func (pdata *PageMetadata) MatchedTag(checkTags []string) bool {
	for _, tag := range checkTags {
		if pdata.Topics[tag] == true {
			return true
		}
	}
	return false
}
<|endoftext|>


<commit_before>// this file contains a pre-processor to pull some stuff out of the markdown file before parsing it

package gnosis

import (
	"bufio"
	"bytes"
	"errors"
	"html/template"
	"os"
)

type PageMetadata struct {
	Keywords map[string]bool
	Tags     map[string]bool
	Loaded   bool
	Page     []byte
}

func (pdata *PageMetadata) LoadPage(pageName string) error {
	f, err := os.Open(pageName)
	reader := bufio.NewReader(f)
	upperLine, fullLine, err := reader.ReadLine()

	// inspect the first line you read
	if err != nil {
		return err
	} else if !fullLine {
		return errors.New("first line I read wasn't a full line")
	} else if pdata.lineIsTitle(upperLine) {
		return errors.New("first line looks an awful lot like the underside of the title o.O")
	}

	lowerLine, fullLine, err := reader.ReadLine()

	// inspect the lower line
	if err != nil {
		return err
	} else if !fullLine {
		return errors.New("second line I read wasn't a full line")
	} else if pdata.lineIsTitle(lowerLine) {
		// if the second line is a title, read the rest of the page in
		// you don't have any metadata to work with here, move on
		pdata.Page = append(upperLine, '\n', lowerLine, '\n')

		_, err = reader.Read(lowerLine)
		if err != nil {
			return err
		}

		pdata.Page = append(pdata.Page, lowerLine)

		// you've successfully loaded the page - so return nothing
		pdata.Loaded = true
		return nil
	}

	// if you're at this point, the first line is metadata
	// you gotta process it and work with the next line
	// so let's just read through the file until we hit the title
	for !pdata.lineIsTitle(lowerLine) {
		// process the line
		ProcessMetadata(upperLine)
		// shift the lower line up
		upperLine = lowerLine
		// read in a new lower line
		lowerLine, fullLine, err := reader.ReadLine()
		if err != nil {
			return err
		} else if !fullLine {
			return errors.New("I filled my buffer with a line")
		}
	}

	// by this point, I should have read everything in - let's read the rest and just return it
	upperLine = append(upperLine, '\n', lowerLine, '\n')

	_, err = reader.Read(lowerLine)

	pdata.Page = append(upperLine, lowerLine)

	return err
}

// takes a single line of input and determines if it's a top level markdown header
func (pdata *PageMetadata) lineIsTitle(line []byte) bool {
	finalLength := len(line)
	i := 0

	// if the row doesn't start with tabs, spaces, or ='s
	if (line[i] != ' ' && line[i] != '=') && line[i] != '\t' {
		return false
	}

	// skip any spaces or tabs at the start
	for line[i] == ' ' || line[i] == '\t' {
		i++
	}

	// if the next item's not a =, bail out
	if line[i] != '=' {
		return false
	}

	// run through all of the ='s
	for line[i] == '=' {
		i++
	}

	if line[i] != ' ' && line[i] != '\t' && line[i] != '\n' {
		return false
	}

	//ditch all spaces after any ='s
	for line[i] == ' ' || line[i] == '\t' {
		i++
	}

	if finalLength == i+1 {
		return true
	} else {
		return false
	}
}

func (pdata *PageMetadata) checkMatch(input []byte, looking []byte, tracker []string) {
	// trim off any blank spaces at the start of the line
	value := bytes.Trim(input, " \t")

	if input[:len(looking)] == looking {
		// trim off the target from the []byte
		value = input[len(looking):]

		// trim spaces at the start and at the end
		value = bytes.Trim(value, " \t\n")

		if value[0] == ':' || value[0] == '=' {
			value = bytes.Trim(value, " \t\n=:")
		}

		// replace any spaces in the middle with -'s
		bytes.Replace(value, " ", "-", -1)

		// suppress any double dashes
		for i := 1; i < len(value); i++ {
			if value[i-1] == '-' && value[1] == '-' {
				value = value[:i] + value[i+1:]
			}
		}

		// now just add the value to the array that you're tracking
		tracker[value] = true
	}
}

// returns all the tags within a list as an array of strings
func (pdata *PageMetadata) ListMeta() ([]string, []sting) {
	tags := new([]string)
	for oneTag, _ := range pdata.Tags {
		tags.Append(oneTag)
	}

	keywords := new([]string)
	for oneKeyword, _ := range pdata.Keywords {
		keywords.Append(oneKeyword)
	}

	return tags, keywords
}

// return the bytes to display the tags on the page
// takes the prefix for the tags
func (pdata *PageMetadata) PrintTags(tagPrefix string) template.HTML {
	response := new([]byte)
	for oneTag, _ := range pdata.Tags {
		response.Append("
")
		response.Append(tagPrefix)
		response.Append(oneTag)
		response.Append("
")
	}
	return template.HTML(response)
}

// returns the bytes to add the keywrods to the html output
func (pdata *PageMetadata) PrintKeywords() template.HTML {
	response := []byte("<meta name='keywords' content='")
	for oneKeyword, _ := range pdata.Keywords {
		response.Append(oneKeyword)
		response.Append(',')
	}
	// clean up the end of the string and add the ending tag
	response = response.TrimSuffix(response, ',')
	response.Append("'>")

	return template.HTML(response)
}

// runs through all restricted tags, and looks for a match
// if matched, returns true, otherwise false
func (pdata *PageMetadata) MatchedTag(checkTags []string) bool {
	for _, tag := range checkTags {
		if pdata.Tags[tag] == true {
			return true
		}
	}
	return false
}

func (pdata *PageMetadata) ProcessMetadata(line []byte) error {
	pdata.checkMatch(line, "tag", pdata.Tags)
	pdata.checkMatch(line, "topic", pdata.Tags)
	pdata.checkMatch(line, "category", pdata.Tags)

	pdata.checkMatch(line, "keyword", pdata.Keywords)
	pdata.checkMatch(line, "keywords", pdata.Keywords)
	pdata.checkMatch(line, "meta", pdata.Keywords)
}
<commit_msg>fixed last calls to .Append - changing then to append([]interface)<commit_after>// this file contains a pre-processor to pull some stuff out of the markdown file before parsing it

package gnosis

import (
	"bufio"
	"bytes"
	"errors"
	"html/template"
	"os"
)

type PageMetadata struct {
	Keywords map[string]bool
	Tags     map[string]bool
	Loaded   bool
	Page     []byte
}

func (pdata *PageMetadata) LoadPage(pageName string) error {
	f, err := os.Open(pageName)
	reader := bufio.NewReader(f)
	upperLine, fullLine, err := reader.ReadLine()

	// inspect the first line you read
	if err != nil {
		return err
	} else if !fullLine {
		return errors.New("first line I read wasn't a full line")
	} else if pdata.lineIsTitle(upperLine) {
		return errors.New("first line looks an awful lot like the underside of the title o.O")
	}

	lowerLine, fullLine, err := reader.ReadLine()

	// inspect the lower line
	if err != nil {
		return err
	} else if !fullLine {
		return errors.New("second line I read wasn't a full line")
	} else if pdata.lineIsTitle(lowerLine) {
		// if the second line is a title, read the rest of the page in
		// you don't have any metadata to work with here, move on
		pdata.Page = append(upperLine, '\n', lowerLine, '\n')

		_, err = reader.Read(lowerLine)
		if err != nil {
			return err
		}

		pdata.Page = append(pdata.Page, lowerLine)

		// you've successfully loaded the page - so return nothing
		pdata.Loaded = true
		return nil
	}

	// if you're at this point, the first line is metadata
	// you gotta process it and work with the next line
	// so let's just read through the file until we hit the title
	for !pdata.lineIsTitle(lowerLine) {
		// process the line
		ProcessMetadata(upperLine)
		// shift the lower line up
		upperLine = lowerLine
		// read in a new lower line
		lowerLine, fullLine, err := reader.ReadLine()
		if err != nil {
			return err
		} else if !fullLine {
			return errors.New("I filled my buffer with a line")
		}
	}

	// by this point, I should have read everything in - let's read the rest and just return it
	upperLine = append(upperLine, '\n', lowerLine, '\n')

	_, err = reader.Read(lowerLine)

	pdata.Page = append(upperLine, lowerLine)

	return err
}

// takes a single line of input and determines if it's a top level markdown header
func (pdata *PageMetadata) lineIsTitle(line []byte) bool {
	finalLength := len(line)
	i := 0

	// if the row doesn't start with tabs, spaces, or ='s
	if (line[i] != ' ' && line[i] != '=') && line[i] != '\t' {
		return false
	}

	// skip any spaces or tabs at the start
	for line[i] == ' ' || line[i] == '\t' {
		i++
	}

	// if the next item's not a =, bail out
	if line[i] != '=' {
		return false
	}

	// run through all of the ='s
	for line[i] == '=' {
		i++
	}

	if line[i] != ' ' && line[i] != '\t' && line[i] != '\n' {
		return false
	}

	//ditch all spaces after any ='s
	for line[i] == ' ' || line[i] == '\t' {
		i++
	}

	if finalLength == i+1 {
		return true
	} else {
		return false
	}
}

func (pdata *PageMetadata) checkMatch(input []byte, looking []byte, tracker []string) {
	// trim off any blank spaces at the start of the line
	value := bytes.Trim(input, " \t")

	if input[:len(looking)] == looking {
		// trim off the target from the []byte
		value = input[len(looking):]

		// trim spaces at the start and at the end
		value = bytes.Trim(value, " \t\n")

		if value[0] == ':' || value[0] == '=' {
			value = bytes.Trim(value, " \t\n=:")
		}

		// replace any spaces in the middle with -'s
		bytes.Replace(value, " ", "-", -1)

		// suppress any double dashes
		for i := 1; i < len(value); i++ {
			if value[i-1] == '-' && value[1] == '-' {
				value = value[:i] + value[i+1:]
			}
		}

		// now just add the value to the array that you're tracking
		tracker[value] = true
	}
}

// returns all the tags within a list as an array of strings
func (pdata *PageMetadata) ListMeta() ([]string, []sting) {
	tags := new([]string)
	for oneTag, _ := range pdata.Tags {
		topics = append(topics, oneTag)
	}

	keywords := new([]string)
	for oneKeyword, _ := range pdata.Keywords {
		keywords = append(keywords, oneKeyword)
	}

	return tags, keywords
}

// return the bytes to display the tags on the page
// takes the prefix for the tags
func (pdata *PageMetadata) PrintTags(tagPrefix string) template.HTML {
	response := new([]byte)
	for oneTag, _ := range pdata.Tags {
		response = append(response, "
", tagPrefix, oneTag, "
")
	}
	return template.HTML(response)
}

// returns the bytes to add the keywrods to the html output
func (pdata *PageMetadata) PrintKeywords() template.HTML {
	response := []byte("<meta name='keywords' content='")
	for oneKeyword, _ := range pdata.Keywords {
		response = append(response, oneKeyword)
	}
	// clean up the end of the string and add the ending tag
	response = response.TrimSuffix(response, ',')
	response = append(response, "'>")

	return template.HTML(response)
}

// runs through all restricted tags, and looks for a match
// if matched, returns true, otherwise false
func (pdata *PageMetadata) MatchedTag(checkTags []string) bool {
	for _, tag := range checkTags {
		if pdata.Tags[tag] == true {
			return true
		}
	}
	return false
}

func (pdata *PageMetadata) ProcessMetadata(line []byte) error {
	pdata.checkMatch(line, "tag", pdata.Tags)
	pdata.checkMatch(line, "topic", pdata.Tags)
	pdata.checkMatch(line, "category", pdata.Tags)

	pdata.checkMatch(line, "keyword", pdata.Keywords)
	pdata.checkMatch(line, "keywords", pdata.Keywords)
	pdata.checkMatch(line, "meta", pdata.Keywords)
}
<|endoftext|>