<commit_before>/*
Copyright 2019 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"log"
	"os"
	"os/exec"
	"sort"
	"strings"
	"sync"
)

type LogLine struct {
	Action  string
	Test    string
	Package string
	Output  string
	Elapsed float32
}

func main() {
	if err := goTest(os.Args[1:]); err != nil {
		os.Exit(1)
	}
}

func goTest(testArgs []string) error {
	args := append([]string{"test", "-json"}, testArgs...)
	verbose := isVerbose(testArgs)

	cmd := exec.CommandContext(context.Background(), "go", args...)
	out, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	failedTests := map[string]bool{}
	var failedLogs []LogLine
	var allLogs []LogLine

	var wc sync.WaitGroup
	wc.Add(1)

	go func() {
		defer wc.Done()

		// Print logs while tests are running
		scanner := bufio.NewScanner(out)
		for i := 0; scanner.Scan(); i++ {
			var l LogLine
			if err := json.Unmarshal(scanner.Bytes(), &l); err != nil {
				log.Panicf("unable to parse line %d: %v", i, err)
			}

			allLogs = append(allLogs, l)

			if l.Action == "output" {
				if verbose || (l.Test == "" && l.Output != "PASS\n" && l.Output != "FAIL\n" && !strings.HasPrefix(l.Output, "coverage:") && !strings.Contains(l.Output, "[no test files]")) {
					fmt.Print(failInRed(l.Output))
				}
			}

			// Is this an error?
			if (l.Action == "fail" || strings.Contains(l.Output, "FAIL")) && l.Test != "" {
				if failedTests[l.Package+"/"+l.Test] {
					continue
				}
				failedTests[l.Package+"/"+l.Test] = true

				failedLogs = append(failedLogs, l)
			}
		}

		// Print detailed information about failures.
		if len(failedLogs) > 0 {
			fmt.Println(red("\n=== Failed Tests ==="))

			for _, l := range failedLogs {
				fmt.Println(bold(trimPackage(l.Package) + "/" + l.Test))

				for _, l := range logsForTest(l.Test, l.Package, allLogs) {
					if l.Action == "output" && l.Output != "" && !strings.HasPrefix(l.Output, "=== RUN") {
						fmt.Print(failInRed(l.Output))
					}
				}
			}
		}

		// Print top slowest tests.
		fmt.Println(yellow("\n=== Slow Tests ==="))
		for _, l := range topSlowest(20, allLogs) {
			fmt.Printf("%.2fs\t%s\n", l.Elapsed, l.Test)
		}
	}()

	err = cmd.Run()
	wc.Wait()
	return err
}

func failInRed(msg string) string {
	return strings.ReplaceAll(msg, "FAIL", red("FAIL"))
}

func red(msg string) string {
	return "\033[0;31m" + msg + "\033[0m"
}

func yellow(msg string) string {
	return "\033[0;33m" + msg + "\033[0m"
}

func bold(msg string) string {
	return "\033[1m" + msg + "\033[0m"
}

func trimPackage(pkg string) string {
	return strings.TrimPrefix(pkg, "github.com/GoogleContainerTools/skaffold")
}

func isVerbose(args []string) bool {
	for _, arg := range args {
		if arg == "-v" {
			return true
		}
	}

	return false
}

func logsForTest(test, pkg string, all []LogLine) []LogLine {
	var forTest []LogLine

	for _, l := range all {
		if l.Package == pkg && l.Test == test {
			forTest = append(forTest, l)
		}
	}

	return forTest
}

func topSlowest(max int, all []LogLine) []LogLine {
	var top []LogLine

	for _, l := range all {
		if l.Test != "" && l.Elapsed > 0 {
			top = append(top, l)
		}
	}

	sort.Slice(top, func(i, j int) bool { return top[i].Elapsed > top[j].Elapsed })

	if len(top) <= max {
		return top
	}
	return top[0:max]
}
<commit_msg>Fix go test helper<commit_after>/*
Copyright 2019 The Skaffold Authors

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"sort"
	"strings"
	"sync"
)

type LogLine struct {
	Action  string
	Test    string
	Package string
	Output  string
	Elapsed float32
}

func main() {
	if err := goTest(os.Args[1:]); err != nil {
		os.Exit(1)
	}
}

func goTest(testArgs []string) error {
	args := append([]string{"test", "-json"}, testArgs...)
	verbose := isVerbose(testArgs)

	cmd := exec.CommandContext(context.Background(), "go", args...)
	out, err := cmd.StdoutPipe()
	if err != nil {
		return err
	}

	failedTests := map[string]bool{}
	var failedLogs []LogLine
	var allLogs []LogLine

	var wc sync.WaitGroup
	wc.Add(1)

	go func() {
		defer wc.Done()

		// Print logs while tests are running
		scanner := bufio.NewScanner(out)
		for i := 0; scanner.Scan(); i++ {
			line := scanner.Bytes()

			var l LogLine
			if err := json.Unmarshal(line, &l); err != nil {
				// Sometimes, `go test -json` outputs plain text instead of json.
				// For example in case of a build error.
				fmt.Println(failInRed(string(line)))
				continue
			}

			allLogs = append(allLogs, l)

			if l.Action == "output" {
				if verbose || (l.Test == "" && l.Output != "PASS\n" && l.Output != "FAIL\n" && !strings.HasPrefix(l.Output, "coverage:") && !strings.Contains(l.Output, "[no test files]")) {
					fmt.Print(failInRed(l.Output))
				}
			}

			// Is this an error?
			if (l.Action == "fail" || strings.Contains(l.Output, "FAIL")) && l.Test != "" {
				if failedTests[l.Package+"/"+l.Test] {
					continue
				}
				failedTests[l.Package+"/"+l.Test] = true

				failedLogs = append(failedLogs, l)
			}
		}

		// Print detailed information about failures.
		if len(failedLogs) > 0 {
			fmt.Println(red("\n=== Failed Tests ==="))

			for _, l := range failedLogs {
				fmt.Println(bold(trimPackage(l.Package) + "/" + l.Test))

				for _, l := range logsForTest(l.Test, l.Package, allLogs) {
					if l.Action == "output" && l.Output != "" && !strings.HasPrefix(l.Output, "=== RUN") {
						fmt.Print(failInRed(l.Output))
					}
				}
			}
		}

		// Print top slowest tests.
		fmt.Println(yellow("\n=== Slow Tests ==="))
		for _, l := range topSlowest(20, allLogs) {
			fmt.Printf("%.2fs\t%s\n", l.Elapsed, l.Test)
		}
	}()

	err = cmd.Run()
	wc.Wait()
	return err
}

func failInRed(msg string) string {
	return strings.ReplaceAll(msg, "FAIL", red("FAIL"))
}

func red(msg string) string {
	return "\033[0;31m" + msg + "\033[0m"
}

func yellow(msg string) string {
	return "\033[0;33m" + msg + "\033[0m"
}

func bold(msg string) string {
	return "\033[1m" + msg + "\033[0m"
}

func trimPackage(pkg string) string {
	return strings.TrimPrefix(pkg, "github.com/GoogleContainerTools/skaffold")
}

func isVerbose(args []string) bool {
	for _, arg := range args {
		if arg == "-v" {
			return true
		}
	}

	return false
}

func logsForTest(test, pkg string, all []LogLine) []LogLine {
	var forTest []LogLine

	for _, l := range all {
		if l.Package == pkg && l.Test == test {
			forTest = append(forTest, l)
		}
	}

	return forTest
}

func topSlowest(max int, all []LogLine) []LogLine {
	var top []LogLine

	for _, l := range all {
		if l.Test != "" && l.Elapsed > 0 {
			top = append(top, l)
		}
	}

	sort.Slice(top, func(i, j int) bool { return top[i].Elapsed > top[j].Elapsed })

	if len(top) <= max {
		return top
	}
	return top[0:max]
}
<|endoftext|>


<commit_before>package handler

// Contains common methods used for writing appengine apps.

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"

	"strings"

	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
)

type handlerError struct {
	AppVersion string   `json:"appVersion"`
	URL        *url.URL `json:"url"`
	Method     string   `json:"method"`
	StatusCode int      `json:"statusCode"`
	InstanceID string   `json:"instanceId"`
	VersionID  string   `json:"versionId"`
	RequestID  string   `json:"requestId"`
	ModuleName string   `json:"moduleName"`
	Err        string   `json:"message"`
}

func (e *handlerError) Error() string {
	b, err := json.MarshalIndent(e, "", "  ")
	if err != nil {
		return err.Error()
	}
	return string(b)
}

// Base struct designed to be extended by more specific url handlers
type Base struct {
	Ctx context.Context
	Req *http.Request
	Res http.ResponseWriter
}

// OriginMiddleware returns a middleware function that validates the origin
// header within the request matches the allowed values
func OriginMiddleware(allowed []string) func(context.Context, http.ResponseWriter, *http.Request) context.Context {
	return func(c context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		origin := r.Header.Get("Origin")
		if len(origin) == 0 {
			return c
		}
		ok := validateOrigin(origin, allowed)
		if !ok {
			c2, cancel := context.WithCancel(c)
			cancel()
			return c2
		}

		w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
		w.Header().Add("Access-Control-Allow-Origin", origin)

		return c
	}
}

// ValidateOrigin is a helper method called within the ServeHTTP method on
// OPTION requests to validate the allowed origins
func (b *Base) ValidateOrigin(allowed []string) {
	origin := b.Req.Header.Get("Origin")
	ok := validateOrigin(origin, allowed)
	if !ok {
		_, cancel := context.WithCancel(b.Ctx)
		cancel()
	}
}

func validateOrigin(origin string, allowed []string) bool {
	if allowed == nil || len(allowed) == 0 {
		return true
	}
	if len(origin) == 0 {
		return false
	}
	for _, allowedOrigin := range allowed {
		if origin == allowedOrigin {
			return true
		}
	}
	return false
}

// ToJSON encodes an interface into the response writer with a default http
// status code of 200
func (b *Base) ToJSON(data interface{}) {
	err := json.NewEncoder(b.Res).Encode(data)
	if err != nil {
		b.Abort(http.StatusInternalServerError, fmt.Errorf("Decoding JSON: %v", err))
	}
}

// ToJSONWithStatus json encodes an interface into the response writer with a
// custom http status code
func (b *Base) ToJSONWithStatus(data interface{}, status int) {
	b.Res.WriteHeader(status)
	b.ToJSON(data)
}

// SendStatus writes the passed in status to the response without any data
func (b *Base) SendStatus(status int) {
	b.Res.WriteHeader(status)
}

// Bind must be called at the beginning of every request to set the required references
func (b *Base) Bind(c context.Context, w http.ResponseWriter, r *http.Request) {
	b.Ctx, b.Res, b.Req = c, w, r
}

// Header gets the request header value
func (b *Base) Header(name string) string {
	return b.Req.Header.Get(name)
}

// SetHeader sets a response header value
func (b *Base) SetHeader(name, value string) {
	b.Res.Header().Set(name, value)
}

// Abort is called when pre-maturally exiting from a handler function due to an
// error. A detailed error is delivered to the client and logged to provide the
// details required to identify the issue.
func (b *Base) Abort(statusCode int, err error) {
	c, cancel := context.WithCancel(b.Ctx)
	defer cancel()

	// testapp is the name given to all apps when being tested
	var isTest = appengine.AppID(c) == "testapp"

	hErr := &handlerError{
		URL:        b.Req.URL,
		Method:     b.Req.Method,
		StatusCode: statusCode,
		AppVersion: appengine.AppID(c),
		RequestID:  appengine.RequestID(c),
	}
	if err != nil {
		hErr.Err = err.Error()
	}

	if !isTest {
		hErr.InstanceID = appengine.InstanceID()
		hErr.VersionID = appengine.VersionID(c)
		hErr.ModuleName = appengine.ModuleName(c)
	}

	// log method to appengine log
	log.Errorf(c, hErr.Error())

	if strings.Index(b.Req.Header.Get("Accept"), "application/json") > 0 {
		b.Res.WriteHeader(statusCode)
		json.NewEncoder(b.Res).Encode(hErr)
	}
}

// QueryParam obtains the value matching the passed in name within the request
// url's querystring
func (b *Base) QueryParam(name string) string {
	return b.Req.URL.Query().Get(name)
}

// QueryKey returns the decoded *datastore.Key value from the query params
func (b *Base) QueryKey(name string) *datastore.Key {
	raw := b.QueryParam(name)
	if len(raw) == 0 {
		return nil
	}

	key, _ := datastore.DecodeKey(raw)
	return key
}

// PathParam returns the decoded *datastore.Key value from the url
func (b *Base) PathParam(tpl string) string {
	path := b.Req.URL.Path
	startIndex := strings.Index(tpl, ":")
	endIndex := strings.Index(path[startIndex:], "/")
	if endIndex == -1 {
		endIndex = len(path)
	}
	return path[startIndex:endIndex]
}

// PathKey returns the decodded *datastore.Key value from the url
func (b *Base) PathKey(tpl string) *datastore.Key {
	rawKey := b.PathParam(tpl)
	if len(rawKey) == 0 {
		return nil
	}
	key, err := datastore.DecodeKey(rawKey)
	if err != nil {
		b.Abort(http.StatusBadRequest, fmt.Errorf("decode key: %s/:key - %v", tpl, err))
		return nil
	}

	return key
}
<commit_msg>catch invalid index<commit_after>package handler

// Contains common methods used for writing appengine apps.

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"

	"strings"

	"golang.org/x/net/context"
	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/log"
)

type handlerError struct {
	AppVersion string   `json:"appVersion"`
	URL        *url.URL `json:"url"`
	Method     string   `json:"method"`
	StatusCode int      `json:"statusCode"`
	InstanceID string   `json:"instanceId"`
	VersionID  string   `json:"versionId"`
	RequestID  string   `json:"requestId"`
	ModuleName string   `json:"moduleName"`
	Err        string   `json:"message"`
}

func (e *handlerError) Error() string {
	b, err := json.MarshalIndent(e, "", "  ")
	if err != nil {
		return err.Error()
	}
	return string(b)
}

// Base struct designed to be extended by more specific url handlers
type Base struct {
	Ctx context.Context
	Req *http.Request
	Res http.ResponseWriter
}

// OriginMiddleware returns a middleware function that validates the origin
// header within the request matches the allowed values
func OriginMiddleware(allowed []string) func(context.Context, http.ResponseWriter, *http.Request) context.Context {
	return func(c context.Context, w http.ResponseWriter, r *http.Request) context.Context {
		origin := r.Header.Get("Origin")
		if len(origin) == 0 {
			return c
		}
		ok := validateOrigin(origin, allowed)
		if !ok {
			c2, cancel := context.WithCancel(c)
			cancel()
			return c2
		}

		w.Header().Add("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Add("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS")
		w.Header().Add("Access-Control-Allow-Origin", origin)

		return c
	}
}

// ValidateOrigin is a helper method called within the ServeHTTP method on
// OPTION requests to validate the allowed origins
func (b *Base) ValidateOrigin(allowed []string) {
	origin := b.Req.Header.Get("Origin")
	ok := validateOrigin(origin, allowed)
	if !ok {
		_, cancel := context.WithCancel(b.Ctx)
		cancel()
	}
}

func validateOrigin(origin string, allowed []string) bool {
	if allowed == nil || len(allowed) == 0 {
		return true
	}
	if len(origin) == 0 {
		return false
	}
	for _, allowedOrigin := range allowed {
		if origin == allowedOrigin {
			return true
		}
	}
	return false
}

// ToJSON encodes an interface into the response writer with a default http
// status code of 200
func (b *Base) ToJSON(data interface{}) {
	err := json.NewEncoder(b.Res).Encode(data)
	if err != nil {
		b.Abort(http.StatusInternalServerError, fmt.Errorf("Decoding JSON: %v", err))
	}
}

// ToJSONWithStatus json encodes an interface into the response writer with a
// custom http status code
func (b *Base) ToJSONWithStatus(data interface{}, status int) {
	b.Res.WriteHeader(status)
	b.ToJSON(data)
}

// SendStatus writes the passed in status to the response without any data
func (b *Base) SendStatus(status int) {
	b.Res.WriteHeader(status)
}

// Bind must be called at the beginning of every request to set the required references
func (b *Base) Bind(c context.Context, w http.ResponseWriter, r *http.Request) {
	b.Ctx, b.Res, b.Req = c, w, r
}

// Header gets the request header value
func (b *Base) Header(name string) string {
	return b.Req.Header.Get(name)
}

// SetHeader sets a response header value
func (b *Base) SetHeader(name, value string) {
	b.Res.Header().Set(name, value)
}

// Abort is called when pre-maturally exiting from a handler function due to an
// error. A detailed error is delivered to the client and logged to provide the
// details required to identify the issue.
func (b *Base) Abort(statusCode int, err error) {
	c, cancel := context.WithCancel(b.Ctx)
	defer cancel()

	// testapp is the name given to all apps when being tested
	var isTest = appengine.AppID(c) == "testapp"

	hErr := &handlerError{
		URL:        b.Req.URL,
		Method:     b.Req.Method,
		StatusCode: statusCode,
		AppVersion: appengine.AppID(c),
		RequestID:  appengine.RequestID(c),
	}
	if err != nil {
		hErr.Err = err.Error()
	}

	if !isTest {
		hErr.InstanceID = appengine.InstanceID()
		hErr.VersionID = appengine.VersionID(c)
		hErr.ModuleName = appengine.ModuleName(c)
	}

	// log method to appengine log
	log.Errorf(c, hErr.Error())

	if strings.Index(b.Req.Header.Get("Accept"), "application/json") > 0 {
		b.Res.WriteHeader(statusCode)
		json.NewEncoder(b.Res).Encode(hErr)
	}
}

// QueryParam obtains the value matching the passed in name within the request
// url's querystring
func (b *Base) QueryParam(name string) string {
	return b.Req.URL.Query().Get(name)
}

// QueryKey returns the decoded *datastore.Key value from the query params
func (b *Base) QueryKey(name string) *datastore.Key {
	raw := b.QueryParam(name)
	if len(raw) == 0 {
		return nil
	}

	key, _ := datastore.DecodeKey(raw)
	return key
}

// PathParam returns the decoded *datastore.Key value from the url
func (b *Base) PathParam(tpl string) string {
	path := b.Req.URL.Path
	startIndex := strings.Index(tpl, ":")
	endIndex := strings.Index(path[startIndex:], "/")
	if startIndex == -1 {
		return ""
	}
	if endIndex == -1 {
		endIndex = len(path)
	}
	return path[startIndex:endIndex]
}

// PathKey returns the decodded *datastore.Key value from the url
func (b *Base) PathKey(tpl string) *datastore.Key {
	rawKey := b.PathParam(tpl)
	if len(rawKey) == 0 {
		return nil
	}
	key, err := datastore.DecodeKey(rawKey)
	if err != nil {
		b.Abort(http.StatusBadRequest, fmt.Errorf("decode key: %s/:key - %v", tpl, err))
		return nil
	}

	return key
}
<|endoftext|>


<commit_before>package handlers

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"regexp"
	"sort"
	"time"

	"github.com/boltdb/bolt"
	humanize "github.com/dustin/go-humanize"
	"github.com/gojp/goreportcard/check"
	"github.com/gojp/goreportcard/download"
)

var reBadRepo = regexp.MustCompile(`package\s([\w\/\.]+)\: exit status \d+`)

func dirName(repo string) string {
	return fmt.Sprintf("repos/src/%s", repo)
}

func getFromCache(repo string) (checksResp, error) {
	// try and fetch from boltdb
	db, err := bolt.Open(DBPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return checksResp{}, fmt.Errorf("failed to open bolt database during GET: %v", err)
	}
	defer db.Close()

	resp := checksResp{}
	err = db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(RepoBucket))
		if b == nil {
			return errors.New("No repo bucket")
		}
		cached := b.Get([]byte(repo))
		if cached == nil {
			return fmt.Errorf("%q not found in cache", repo)
		}

		err = json.Unmarshal(cached, &resp)
		if err != nil {
			return fmt.Errorf("failed to parse JSON for %q in cache", repo)
		}
		return nil
	})

	if err != nil {
		return resp, err
	}

	resp.LastRefresh = resp.LastRefresh.UTC()
	resp.HumanizedLastRefresh = humanize.Time(resp.LastRefresh.UTC())

	return resp, nil
}

type score struct {
	Name          string              `json:"name"`
	Description   string              `json:"description"`
	FileSummaries []check.FileSummary `json:"file_summaries"`
	Weight        float64             `json:"weight"`
	Percentage    float64             `json:"percentage"`
	Error         string              `json:"error"`
}

type checksResp struct {
	Checks               []score   `json:"checks"`
	Average              float64   `json:"average"`
	Grade                Grade     `json:"grade"`
	Files                int       `json:"files"`
	Issues               int       `json:"issues"`
	Repo                 string    `json:"repo"`
	LastRefresh          time.Time `json:"last_refresh"`
	HumanizedLastRefresh string    `json:"humanized_last_refresh"`
}

func newChecksResp(repo string, forceRefresh bool) (checksResp, error) {
	if !forceRefresh {
		resp, err := getFromCache(repo)
		if err != nil {
			// just log the error and continue
			log.Println(err)
		} else {
			resp.Grade = grade(resp.Average * 100) // grade is not stored for some repos, yet
			return resp, nil
		}
	}

	// fetch the repo and grade it
	repoRoot, err := download.Download(repo, "repos/src")
	if err != nil {
		return checksResp{}, fmt.Errorf("could not clone repo: %v", err)
	}

	repo = repoRoot.Root

	dir := dirName(repo)
	filenames, err := check.GoFiles(dir)
	if err != nil {
		return checksResp{}, fmt.Errorf("could not get filenames: %v", err)
	}
	if len(filenames) == 0 {
		return checksResp{}, fmt.Errorf("no .go files found")
	}
	checks := []check.Check{
		check.GoFmt{Dir: dir, Filenames: filenames},
		check.GoVet{Dir: dir, Filenames: filenames},
		check.GoLint{Dir: dir, Filenames: filenames},
		check.GoCyclo{Dir: dir, Filenames: filenames},
		check.License{Dir: dir, Filenames: []string{}},
		check.Misspell{Dir: dir, Filenames: filenames},
		check.IneffAssign{Dir: dir, Filenames: filenames},
		// check.ErrCheck{Dir: dir, Filenames: filenames}, // disable errcheck for now, too slow and not finalized
	}

	ch := make(chan score)
	for _, c := range checks {
		go func(c check.Check) {
			p, summaries, err := c.Percentage()
			errMsg := ""
			if err != nil {
				log.Printf("ERROR: (%s) %v", c.Name(), err)
				errMsg = err.Error()
			}
			s := score{
				Name:          c.Name(),
				Description:   c.Description(),
				FileSummaries: summaries,
				Weight:        c.Weight(),
				Percentage:    p,
				Error:         errMsg,
			}
			ch <- s
		}(c)
	}

	resp := checksResp{
		Repo:                 repo,
		Files:                len(filenames),
		LastRefresh:          time.Now().UTC(),
		HumanizedLastRefresh: humanize.Time(time.Now().UTC()),
	}

	var total float64
	var totalWeight float64
	var issues = make(map[string]bool)
	for i := 0; i < len(checks); i++ {
		s := <-ch
		resp.Checks = append(resp.Checks, s)
		total += s.Percentage * s.Weight
		totalWeight += s.Weight
		for _, fs := range s.FileSummaries {
			issues[fs.Filename] = true
		}
	}
	total /= totalWeight

	sort.Sort(ByWeight(resp.Checks))
	resp.Average = total
	resp.Issues = len(issues)
	resp.Grade = grade(total * 100)

	return resp, nil
}

// ByWeight implements sorting for checks by weight descending
type ByWeight []score

func (a ByWeight) Len() int           { return len(a) }
func (a ByWeight) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByWeight) Less(i, j int) bool { return a[i].Weight > a[j].Weight }
<commit_msg>remove unused var<commit_after>package handlers

import (
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"sort"
	"time"

	"github.com/boltdb/bolt"
	humanize "github.com/dustin/go-humanize"
	"github.com/gojp/goreportcard/check"
	"github.com/gojp/goreportcard/download"
)

func dirName(repo string) string {
	return fmt.Sprintf("repos/src/%s", repo)
}

func getFromCache(repo string) (checksResp, error) {
	// try and fetch from boltdb
	db, err := bolt.Open(DBPath, 0600, &bolt.Options{Timeout: 1 * time.Second})
	if err != nil {
		return checksResp{}, fmt.Errorf("failed to open bolt database during GET: %v", err)
	}
	defer db.Close()

	resp := checksResp{}
	err = db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(RepoBucket))
		if b == nil {
			return errors.New("No repo bucket")
		}
		cached := b.Get([]byte(repo))
		if cached == nil {
			return fmt.Errorf("%q not found in cache", repo)
		}

		err = json.Unmarshal(cached, &resp)
		if err != nil {
			return fmt.Errorf("failed to parse JSON for %q in cache", repo)
		}
		return nil
	})

	if err != nil {
		return resp, err
	}

	resp.LastRefresh = resp.LastRefresh.UTC()
	resp.HumanizedLastRefresh = humanize.Time(resp.LastRefresh.UTC())

	return resp, nil
}

type score struct {
	Name          string              `json:"name"`
	Description   string              `json:"description"`
	FileSummaries []check.FileSummary `json:"file_summaries"`
	Weight        float64             `json:"weight"`
	Percentage    float64             `json:"percentage"`
	Error         string              `json:"error"`
}

type checksResp struct {
	Checks               []score   `json:"checks"`
	Average              float64   `json:"average"`
	Grade                Grade     `json:"grade"`
	Files                int       `json:"files"`
	Issues               int       `json:"issues"`
	Repo                 string    `json:"repo"`
	LastRefresh          time.Time `json:"last_refresh"`
	HumanizedLastRefresh string    `json:"humanized_last_refresh"`
}

func newChecksResp(repo string, forceRefresh bool) (checksResp, error) {
	if !forceRefresh {
		resp, err := getFromCache(repo)
		if err != nil {
			// just log the error and continue
			log.Println(err)
		} else {
			resp.Grade = grade(resp.Average * 100) // grade is not stored for some repos, yet
			return resp, nil
		}
	}

	// fetch the repo and grade it
	repoRoot, err := download.Download(repo, "repos/src")
	if err != nil {
		return checksResp{}, fmt.Errorf("could not clone repo: %v", err)
	}

	repo = repoRoot.Root

	dir := dirName(repo)
	filenames, err := check.GoFiles(dir)
	if err != nil {
		return checksResp{}, fmt.Errorf("could not get filenames: %v", err)
	}
	if len(filenames) == 0 {
		return checksResp{}, fmt.Errorf("no .go files found")
	}
	checks := []check.Check{
		check.GoFmt{Dir: dir, Filenames: filenames},
		check.GoVet{Dir: dir, Filenames: filenames},
		check.GoLint{Dir: dir, Filenames: filenames},
		check.GoCyclo{Dir: dir, Filenames: filenames},
		check.License{Dir: dir, Filenames: []string{}},
		check.Misspell{Dir: dir, Filenames: filenames},
		check.IneffAssign{Dir: dir, Filenames: filenames},
		// check.ErrCheck{Dir: dir, Filenames: filenames}, // disable errcheck for now, too slow and not finalized
	}

	ch := make(chan score)
	for _, c := range checks {
		go func(c check.Check) {
			p, summaries, err := c.Percentage()
			errMsg := ""
			if err != nil {
				log.Printf("ERROR: (%s) %v", c.Name(), err)
				errMsg = err.Error()
			}
			s := score{
				Name:          c.Name(),
				Description:   c.Description(),
				FileSummaries: summaries,
				Weight:        c.Weight(),
				Percentage:    p,
				Error:         errMsg,
			}
			ch <- s
		}(c)
	}

	resp := checksResp{
		Repo:                 repo,
		Files:                len(filenames),
		LastRefresh:          time.Now().UTC(),
		HumanizedLastRefresh: humanize.Time(time.Now().UTC()),
	}

	var total float64
	var totalWeight float64
	var issues = make(map[string]bool)
	for i := 0; i < len(checks); i++ {
		s := <-ch
		resp.Checks = append(resp.Checks, s)
		total += s.Percentage * s.Weight
		totalWeight += s.Weight
		for _, fs := range s.FileSummaries {
			issues[fs.Filename] = true
		}
	}
	total /= totalWeight

	sort.Sort(ByWeight(resp.Checks))
	resp.Average = total
	resp.Issues = len(issues)
	resp.Grade = grade(total * 100)

	return resp, nil
}

// ByWeight implements sorting for checks by weight descending
type ByWeight []score

func (a ByWeight) Len() int           { return len(a) }
func (a ByWeight) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
func (a ByWeight) Less(i, j int) bool { return a[i].Weight > a[j].Weight }
<|endoftext|>


<commit_before>package hclsyntax

import (
	"bytes"
	"fmt"

	"github.com/apparentlymart/go-textseg/v12/textseg"
	"github.com/hashicorp/hcl/v2"
)

// Token represents a sequence of bytes from some HCL code that has been
// tagged with a type and its range within the source file.
type Token struct {
	Type  TokenType
	Bytes []byte
	Range hcl.Range
}

// Tokens is a slice of Token.
type Tokens []Token

// TokenType is an enumeration used for the Type field on Token.
type TokenType rune

const (
	// Single-character tokens are represented by their own character, for
	// convenience in producing these within the scanner. However, the values
	// are otherwise arbitrary and just intended to be mnemonic for humans
	// who might see them in debug output.

	TokenOBrace   TokenType = '{'
	TokenCBrace   TokenType = '}'
	TokenOBrack   TokenType = '['
	TokenCBrack   TokenType = ']'
	TokenOParen   TokenType = '('
	TokenCParen   TokenType = ')'
	TokenOQuote   TokenType = '«'
	TokenCQuote   TokenType = '»'
	TokenOHeredoc TokenType = 'H'
	TokenCHeredoc TokenType = 'h'

	TokenStar    TokenType = '*'
	TokenSlash   TokenType = '/'
	TokenPlus    TokenType = '+'
	TokenMinus   TokenType = '-'
	TokenPercent TokenType = '%'

	TokenEqual         TokenType = '='
	TokenEqualOp       TokenType = '≔'
	TokenNotEqual      TokenType = '≠'
	TokenLessThan      TokenType = '<'
	TokenLessThanEq    TokenType = '≤'
	TokenGreaterThan   TokenType = '>'
	TokenGreaterThanEq TokenType = '≥'

	TokenAnd  TokenType = '∧'
	TokenOr   TokenType = '∨'
	TokenBang TokenType = '!'

	TokenDot   TokenType = '.'
	TokenComma TokenType = ','

	TokenEllipsis TokenType = '…'
	TokenFatArrow TokenType = '⇒'

	TokenQuestion TokenType = '?'
	TokenColon    TokenType = ':'

	TokenTemplateInterp  TokenType = '∫'
	TokenTemplateControl TokenType = 'λ'
	TokenTemplateSeqEnd  TokenType = '∎'

	TokenQuotedLit TokenType = 'Q' // might contain backslash escapes
	TokenStringLit TokenType = 'S' // cannot contain backslash escapes
	TokenNumberLit TokenType = 'N'
	TokenIdent     TokenType = 'I'

	TokenComment TokenType = 'C'

	TokenNewline TokenType = '\n'
	TokenEOF     TokenType = '␄'

	// The rest are not used in the language but recognized by the scanner so
	// we can generate good diagnostics in the parser when users try to write
	// things that might work in other languages they are familiar with, or
	// simply make incorrect assumptions about the HCL language.

	TokenBitwiseAnd    TokenType = '&'
	TokenBitwiseOr     TokenType = '|'
	TokenBitwiseNot    TokenType = '~'
	TokenBitwiseXor    TokenType = '^'
	TokenStarStar      TokenType = '➚'
	TokenApostrophe    TokenType = '\''
	TokenBacktick      TokenType = '`'
	TokenSemicolon     TokenType = ';'
	TokenTabs          TokenType = '␉'
	TokenInvalid       TokenType = '�'
	TokenBadUTF8       TokenType = '💩'
	TokenQuotedNewline TokenType = '␤'

	// TokenNil is a placeholder for when a token is required but none is
	// available, e.g. when reporting errors. The scanner will never produce
	// this as part of a token stream.
	TokenNil TokenType = '\x00'
)

func (t TokenType) GoString() string {
	return fmt.Sprintf("hclsyntax.%s", t.String())
}

type scanMode int

const (
	scanNormal scanMode = iota
	scanTemplate
	scanIdentOnly
)

type tokenAccum struct {
	Filename  string
	Bytes     []byte
	Pos       hcl.Pos
	Tokens    []Token
	StartByte int
}

func (f *tokenAccum) emitToken(ty TokenType, startOfs, endOfs int) {
	// Walk through our buffer to figure out how much we need to adjust
	// the start pos to get our end pos.

	start := f.Pos
	start.Column += startOfs + f.StartByte - f.Pos.Byte // Safe because only ASCII spaces can be in the offset
	start.Byte = startOfs + f.StartByte

	end := start
	end.Byte = endOfs + f.StartByte
	b := f.Bytes[startOfs:endOfs]
	for len(b) > 0 {
		advance, seq, _ := textseg.ScanGraphemeClusters(b, true)
		if (len(seq) == 1 && seq[0] == '\n') || (len(seq) == 2 && seq[0] == '\r' && seq[1] == '\n') {
			end.Line++
			end.Column = 1
		} else {
			end.Column++
		}
		b = b[advance:]
	}

	f.Pos = end

	f.Tokens = append(f.Tokens, Token{
		Type:  ty,
		Bytes: f.Bytes[startOfs:endOfs],
		Range: hcl.Range{
			Filename: f.Filename,
			Start:    start,
			End:      end,
		},
	})
}

type heredocInProgress struct {
	Marker      []byte
	StartOfLine bool
}

func tokenOpensFlushHeredoc(tok Token) bool {
	if tok.Type != TokenOHeredoc {
		return false
	}
	return bytes.HasPrefix(tok.Bytes, []byte{'<', '<', '-'})
}

// checkInvalidTokens does a simple pass across the given tokens and generates
// diagnostics for tokens that should _never_ appear in HCL source. This
// is intended to avoid the need for the parser to have special support
// for them all over.
//
// Returns a diagnostics with no errors if everything seems acceptable.
// Otherwise, returns zero or more error diagnostics, though tries to limit
// repetition of the same information.
func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
	var diags hcl.Diagnostics

	toldBitwise := 0
	toldExponent := 0
	toldBacktick := 0
	toldApostrophe := 0
	toldSemicolon := 0
	toldTabs := 0
	toldBadUTF8 := 0

	for _, tok := range tokens {
		// copy token so it's safe to point to it
		tok := tok

		switch tok.Type {
		case TokenBitwiseAnd, TokenBitwiseOr, TokenBitwiseXor, TokenBitwiseNot:
			if toldBitwise < 4 {
				var suggestion string
				switch tok.Type {
				case TokenBitwiseAnd:
					suggestion = " Did you mean boolean AND (\"&&\")?"
				case TokenBitwiseOr:
					suggestion = " Did you mean boolean OR (\"&&\")?"
				case TokenBitwiseNot:
					suggestion = " Did you mean boolean NOT (\"!\")?"
				}

				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Unsupported operator",
					Detail:   fmt.Sprintf("Bitwise operators are not supported.%s", suggestion),
					Subject:  &tok.Range,
				})
				toldBitwise++
			}
		case TokenStarStar:
			if toldExponent < 1 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Unsupported operator",
					Detail:   "\"**\" is not a supported operator. Exponentiation is not supported as an operator.",
					Subject:  &tok.Range,
				})

				toldExponent++
			}
		case TokenBacktick:
			// Only report for alternating (even) backticks, so we won't report both start and ends of the same
			// backtick-quoted string.
			if (toldBacktick % 2) == 0 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character",
					Detail:   "The \"`\" character is not valid. To create a multi-line string, use the \"heredoc\" syntax, like \"<<EOT\".",
					Subject:  &tok.Range,
				})
			}
			if toldBacktick <= 2 {
				toldBacktick++
			}
		case TokenApostrophe:
			if (toldApostrophe % 2) == 0 {
				newDiag := &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character",
					Detail:   "Single quotes are not valid. Use double quotes (\") to enclose strings.",
					Subject:  &tok.Range,
				}
				diags = append(diags, newDiag)
			}
			if toldApostrophe <= 2 {
				toldApostrophe++
			}
		case TokenSemicolon:
			if toldSemicolon < 1 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character",
					Detail:   "The \";\" character is not valid. Use newlines to separate arguments and blocks, and commas to separate items in collection values.",
					Subject:  &tok.Range,
				})

				toldSemicolon++
			}
		case TokenTabs:
			if toldTabs < 1 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character",
					Detail:   "Tab characters may not be used. The recommended indentation style is two spaces per indent.",
					Subject:  &tok.Range,
				})

				toldTabs++
			}
		case TokenBadUTF8:
			if toldBadUTF8 < 1 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character encoding",
					Detail:   "All input files must be UTF-8 encoded. Ensure that UTF-8 encoding is selected in your editor.",
					Subject:  &tok.Range,
				})

				toldBadUTF8++
			}
		case TokenQuotedNewline:
			diags = append(diags, &hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid multi-line string",
				Detail:   "Quoted strings may not be split over multiple lines. To produce a multi-line string, either use the \\n escape to represent a newline character or use the \"heredoc\" multi-line template syntax.",
				Subject:  &tok.Range,
			})
		case TokenInvalid:
			diags = append(diags, &hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid character",
				Detail:   "This character is not used within the language.",
				Subject:  &tok.Range,
			})
		}
	}
	return diags
}

var utf8BOM = []byte{0xef, 0xbb, 0xbf}

// stripUTF8BOM checks whether the given buffer begins with a UTF-8 byte order
// mark (0xEF 0xBB 0xBF) and, if so, returns a truncated slice with the same
// backing array but with the BOM skipped.
//
// If there is no BOM present, the given slice is returned verbatim.
func stripUTF8BOM(src []byte) []byte {
	if bytes.HasPrefix(src, utf8BOM) {
		return src[3:]
	}
	return src
}
<commit_msg>Fix a wrong error message for bitwise OR<commit_after>package hclsyntax

import (
	"bytes"
	"fmt"

	"github.com/apparentlymart/go-textseg/v12/textseg"
	"github.com/hashicorp/hcl/v2"
)

// Token represents a sequence of bytes from some HCL code that has been
// tagged with a type and its range within the source file.
type Token struct {
	Type  TokenType
	Bytes []byte
	Range hcl.Range
}

// Tokens is a slice of Token.
type Tokens []Token

// TokenType is an enumeration used for the Type field on Token.
type TokenType rune

const (
	// Single-character tokens are represented by their own character, for
	// convenience in producing these within the scanner. However, the values
	// are otherwise arbitrary and just intended to be mnemonic for humans
	// who might see them in debug output.

	TokenOBrace   TokenType = '{'
	TokenCBrace   TokenType = '}'
	TokenOBrack   TokenType = '['
	TokenCBrack   TokenType = ']'
	TokenOParen   TokenType = '('
	TokenCParen   TokenType = ')'
	TokenOQuote   TokenType = '«'
	TokenCQuote   TokenType = '»'
	TokenOHeredoc TokenType = 'H'
	TokenCHeredoc TokenType = 'h'

	TokenStar    TokenType = '*'
	TokenSlash   TokenType = '/'
	TokenPlus    TokenType = '+'
	TokenMinus   TokenType = '-'
	TokenPercent TokenType = '%'

	TokenEqual         TokenType = '='
	TokenEqualOp       TokenType = '≔'
	TokenNotEqual      TokenType = '≠'
	TokenLessThan      TokenType = '<'
	TokenLessThanEq    TokenType = '≤'
	TokenGreaterThan   TokenType = '>'
	TokenGreaterThanEq TokenType = '≥'

	TokenAnd  TokenType = '∧'
	TokenOr   TokenType = '∨'
	TokenBang TokenType = '!'

	TokenDot   TokenType = '.'
	TokenComma TokenType = ','

	TokenEllipsis TokenType = '…'
	TokenFatArrow TokenType = '⇒'

	TokenQuestion TokenType = '?'
	TokenColon    TokenType = ':'

	TokenTemplateInterp  TokenType = '∫'
	TokenTemplateControl TokenType = 'λ'
	TokenTemplateSeqEnd  TokenType = '∎'

	TokenQuotedLit TokenType = 'Q' // might contain backslash escapes
	TokenStringLit TokenType = 'S' // cannot contain backslash escapes
	TokenNumberLit TokenType = 'N'
	TokenIdent     TokenType = 'I'

	TokenComment TokenType = 'C'

	TokenNewline TokenType = '\n'
	TokenEOF     TokenType = '␄'

	// The rest are not used in the language but recognized by the scanner so
	// we can generate good diagnostics in the parser when users try to write
	// things that might work in other languages they are familiar with, or
	// simply make incorrect assumptions about the HCL language.

	TokenBitwiseAnd    TokenType = '&'
	TokenBitwiseOr     TokenType = '|'
	TokenBitwiseNot    TokenType = '~'
	TokenBitwiseXor    TokenType = '^'
	TokenStarStar      TokenType = '➚'
	TokenApostrophe    TokenType = '\''
	TokenBacktick      TokenType = '`'
	TokenSemicolon     TokenType = ';'
	TokenTabs          TokenType = '␉'
	TokenInvalid       TokenType = '�'
	TokenBadUTF8       TokenType = '💩'
	TokenQuotedNewline TokenType = '␤'

	// TokenNil is a placeholder for when a token is required but none is
	// available, e.g. when reporting errors. The scanner will never produce
	// this as part of a token stream.
	TokenNil TokenType = '\x00'
)

func (t TokenType) GoString() string {
	return fmt.Sprintf("hclsyntax.%s", t.String())
}

type scanMode int

const (
	scanNormal scanMode = iota
	scanTemplate
	scanIdentOnly
)

type tokenAccum struct {
	Filename  string
	Bytes     []byte
	Pos       hcl.Pos
	Tokens    []Token
	StartByte int
}

func (f *tokenAccum) emitToken(ty TokenType, startOfs, endOfs int) {
	// Walk through our buffer to figure out how much we need to adjust
	// the start pos to get our end pos.

	start := f.Pos
	start.Column += startOfs + f.StartByte - f.Pos.Byte // Safe because only ASCII spaces can be in the offset
	start.Byte = startOfs + f.StartByte

	end := start
	end.Byte = endOfs + f.StartByte
	b := f.Bytes[startOfs:endOfs]
	for len(b) > 0 {
		advance, seq, _ := textseg.ScanGraphemeClusters(b, true)
		if (len(seq) == 1 && seq[0] == '\n') || (len(seq) == 2 && seq[0] == '\r' && seq[1] == '\n') {
			end.Line++
			end.Column = 1
		} else {
			end.Column++
		}
		b = b[advance:]
	}

	f.Pos = end

	f.Tokens = append(f.Tokens, Token{
		Type:  ty,
		Bytes: f.Bytes[startOfs:endOfs],
		Range: hcl.Range{
			Filename: f.Filename,
			Start:    start,
			End:      end,
		},
	})
}

type heredocInProgress struct {
	Marker      []byte
	StartOfLine bool
}

func tokenOpensFlushHeredoc(tok Token) bool {
	if tok.Type != TokenOHeredoc {
		return false
	}
	return bytes.HasPrefix(tok.Bytes, []byte{'<', '<', '-'})
}

// checkInvalidTokens does a simple pass across the given tokens and generates
// diagnostics for tokens that should _never_ appear in HCL source. This
// is intended to avoid the need for the parser to have special support
// for them all over.
//
// Returns a diagnostics with no errors if everything seems acceptable.
// Otherwise, returns zero or more error diagnostics, though tries to limit
// repetition of the same information.
func checkInvalidTokens(tokens Tokens) hcl.Diagnostics {
	var diags hcl.Diagnostics

	toldBitwise := 0
	toldExponent := 0
	toldBacktick := 0
	toldApostrophe := 0
	toldSemicolon := 0
	toldTabs := 0
	toldBadUTF8 := 0

	for _, tok := range tokens {
		// copy token so it's safe to point to it
		tok := tok

		switch tok.Type {
		case TokenBitwiseAnd, TokenBitwiseOr, TokenBitwiseXor, TokenBitwiseNot:
			if toldBitwise < 4 {
				var suggestion string
				switch tok.Type {
				case TokenBitwiseAnd:
					suggestion = " Did you mean boolean AND (\"&&\")?"
				case TokenBitwiseOr:
					suggestion = " Did you mean boolean OR (\"||\")?"
				case TokenBitwiseNot:
					suggestion = " Did you mean boolean NOT (\"!\")?"
				}

				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Unsupported operator",
					Detail:   fmt.Sprintf("Bitwise operators are not supported.%s", suggestion),
					Subject:  &tok.Range,
				})
				toldBitwise++
			}
		case TokenStarStar:
			if toldExponent < 1 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Unsupported operator",
					Detail:   "\"**\" is not a supported operator. Exponentiation is not supported as an operator.",
					Subject:  &tok.Range,
				})

				toldExponent++
			}
		case TokenBacktick:
			// Only report for alternating (even) backticks, so we won't report both start and ends of the same
			// backtick-quoted string.
			if (toldBacktick % 2) == 0 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character",
					Detail:   "The \"`\" character is not valid. To create a multi-line string, use the \"heredoc\" syntax, like \"<<EOT\".",
					Subject:  &tok.Range,
				})
			}
			if toldBacktick <= 2 {
				toldBacktick++
			}
		case TokenApostrophe:
			if (toldApostrophe % 2) == 0 {
				newDiag := &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character",
					Detail:   "Single quotes are not valid. Use double quotes (\") to enclose strings.",
					Subject:  &tok.Range,
				}
				diags = append(diags, newDiag)
			}
			if toldApostrophe <= 2 {
				toldApostrophe++
			}
		case TokenSemicolon:
			if toldSemicolon < 1 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character",
					Detail:   "The \";\" character is not valid. Use newlines to separate arguments and blocks, and commas to separate items in collection values.",
					Subject:  &tok.Range,
				})

				toldSemicolon++
			}
		case TokenTabs:
			if toldTabs < 1 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character",
					Detail:   "Tab characters may not be used. The recommended indentation style is two spaces per indent.",
					Subject:  &tok.Range,
				})

				toldTabs++
			}
		case TokenBadUTF8:
			if toldBadUTF8 < 1 {
				diags = append(diags, &hcl.Diagnostic{
					Severity: hcl.DiagError,
					Summary:  "Invalid character encoding",
					Detail:   "All input files must be UTF-8 encoded. Ensure that UTF-8 encoding is selected in your editor.",
					Subject:  &tok.Range,
				})

				toldBadUTF8++
			}
		case TokenQuotedNewline:
			diags = append(diags, &hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid multi-line string",
				Detail:   "Quoted strings may not be split over multiple lines. To produce a multi-line string, either use the \\n escape to represent a newline character or use the \"heredoc\" multi-line template syntax.",
				Subject:  &tok.Range,
			})
		case TokenInvalid:
			diags = append(diags, &hcl.Diagnostic{
				Severity: hcl.DiagError,
				Summary:  "Invalid character",
				Detail:   "This character is not used within the language.",
				Subject:  &tok.Range,
			})
		}
	}
	return diags
}

var utf8BOM = []byte{0xef, 0xbb, 0xbf}

// stripUTF8BOM checks whether the given buffer begins with a UTF-8 byte order
// mark (0xEF 0xBB 0xBF) and, if so, returns a truncated slice with the same
// backing array but with the BOM skipped.
//
// If there is no BOM present, the given slice is returned verbatim.
func stripUTF8BOM(src []byte) []byte {
	if bytes.HasPrefix(src, utf8BOM) {
		return src[3:]
	}
	return src
}
<|endoftext|>


<commit_before>package httpseverywhere

import (
	"net"
	"net/url"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/net/publicsuffix"

	"github.com/getlantern/golog"
)

var (
	log = golog.LoggerFor("httpseverywhere")
)

// Rewrite exports the rewrite method for users of this library.
var Rewrite = newAsync()

// rewrite changes an HTTP URL to rewrite.
type rewrite func(url *url.URL) (string, bool)

type https struct {
	// This is a map of root host names to Targets -- map[string]*Targets
	hostsToTargets atomic.Value
	runs           int64
	totalTime      int64
	max            int64
	maxHost        string
	statM          sync.RWMutex
}

// A rule maps the regular expression to match and the string to change it to.
// It also stores the compiled regular expression for efficiency.
type rule struct {
	from *regexp.Regexp
	From string
	To   string
}

// An exclusion just contains the compiled regular expression exclusion pattern.
type exclusion struct {
	Pattern string
	pattern *regexp.Regexp
}

// Rules is a struct containing rules and exclusions for a given rule set. This
// is public so that we can encode and decode it from GOB format.
type Rules struct {
	Rules      []*rule
	Exclusions []*exclusion
}

// Targets contains the target hosts for the given base domain.
type Targets struct {
	Plain map[string]bool

	wildcardPrefix []*regexp.Regexp
	wildcardSuffix []*regexp.Regexp

	// We use maps here to filter duplicates. Note these are only used in
	// preprocessing and in deserialization.
	WildcardPrefix map[string]bool
	WildcardSuffix map[string]bool

	Rules *Rules
}

// new creates a new rewrite instance from embedded GOB data with asynchronous
// loading of the rule sets to allow the caller to about around a 2 second
// delay.
func newAsync() rewrite {
	h := &https{}

	h.hostsToTargets.Store(make(map[string]*Targets))
	go func() {
		d := newDeserializer()
		temp := d.newHostsToTargets()
		h.hostsToTargets.Store(temp)
	}()

	return h.rewrite
}

// newSync creates a new rewrite instance from embedded GOB data.
func newSync() rewrite {
	h := &https{}
	d := newDeserializer()
	h.hostsToTargets.Store(d.newHostsToTargets())
	return h.rewrite
}

func (h *https) rewrite(url *url.URL) (string, bool) {
	if url.Scheme != "http" {
		return "", false
	}
	start := time.Now()
	host, root := extractHostAndRoot(url)

	if len(root) == 0 {
		log.Error("Root is the empty string!")
		return "", false
	}
	if targets, ok := h.hostsToTargets.Load().(map[string]*Targets)[root]; ok {
		https, done := targets.rewrite(url, host)
		h.addTiming(time.Now().Sub(start), url.String())

		return https, done
	}
	return "", false
}

func (h *https) addTiming(dur time.Duration, host string) {
	nan := dur.Nanoseconds()
	h.statM.Lock()
	h.runs++
	h.totalTime += nan
	if nan > h.max {
		h.max = nan
		h.maxHost = host
	}
	h.statM.Unlock()

	h.statM.RLock()
	log.Debugf("Average running time: %v", h.totalTime/h.runs)
	log.Debugf("Max running time: %v for host:", h.max, h.maxHost)
	h.statM.RUnlock()
}

func extractHostAndRoot(url *url.URL) (string, string) {
	host := withoutPort(url.Host)

	// We ignore the second return value which is just a bool indicating whether
	// it's an official ICANN TLD.
	tld, _ := publicsuffix.PublicSuffix(host)

	// Because some TLDs such as "co.uk" include "."s, we strip the TLD prior
	// to stripping subdomains.
	noTLD := strings.TrimSuffix(host, "."+tld)
	root := stripSubdomains(noTLD)
	return host, root
}

func withoutPort(hostport string) string {
	host, _, err := net.SplitHostPort(hostport)
	if err != nil {
		return hostport
	}
	return host
}

func stripSubdomains(host string) string {
	host = strings.TrimSpace(host)
	parts := strings.Split(host, ".")
	return parts[len(parts)-1]
}

func (t *Targets) rewrite(originalURL *url.URL, host string) (string, bool) {
	// We basically want to apply the associated set of rules if any of the
	// targets match the url.
	url := originalURL.String()
	log.Debugf("Attempting to rewrite url %v", url)
	for k := range t.Plain {
		if host == k {
			if r, done := t.Rules.rewrite(url); done {
				return r, done
			}
		}
	}
	if r, done := t.matchTargets(url, t.wildcardPrefix); done {
		return r, done
	}
	return t.matchTargets(url, t.wildcardSuffix)
}

func (t *Targets) matchTargets(url string, targets []*regexp.Regexp) (string, bool) {
	for _, pre := range targets {
		if pre.MatchString(url) {
			if r, done := t.Rules.rewrite(url); done {
				return r, done
			}
		}
	}
	return "", false
}

// rewrite converts the given URL to HTTPS if there is an associated rule for
// it.
func (r *Rules) rewrite(url string) (string, bool) {
	for _, exclude := range r.Exclusions {
		if exclude.pattern.MatchString(url) {
			return "", false
		}
	}
	for _, rule := range r.Rules {
		if rule.from.MatchString(url) {
			return rule.from.ReplaceAllString(url, rule.To), true
		}
	}
	return "", false
}
<commit_msg>convert to ms<commit_after>package httpseverywhere

import (
	"net"
	"net/url"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"golang.org/x/net/publicsuffix"

	"github.com/getlantern/golog"
)

var (
	log = golog.LoggerFor("httpseverywhere")
)

// Rewrite exports the rewrite method for users of this library.
var Rewrite = newAsync()

// rewrite changes an HTTP URL to rewrite.
type rewrite func(url *url.URL) (string, bool)

type https struct {
	// This is a map of root host names to Targets -- map[string]*Targets
	hostsToTargets atomic.Value
	runs           int64
	totalTime      int64
	max            int64
	maxHost        string
	statM          sync.RWMutex
}

// A rule maps the regular expression to match and the string to change it to.
// It also stores the compiled regular expression for efficiency.
type rule struct {
	from *regexp.Regexp
	From string
	To   string
}

// An exclusion just contains the compiled regular expression exclusion pattern.
type exclusion struct {
	Pattern string
	pattern *regexp.Regexp
}

// Rules is a struct containing rules and exclusions for a given rule set. This
// is public so that we can encode and decode it from GOB format.
type Rules struct {
	Rules      []*rule
	Exclusions []*exclusion
}

// Targets contains the target hosts for the given base domain.
type Targets struct {
	Plain map[string]bool

	wildcardPrefix []*regexp.Regexp
	wildcardSuffix []*regexp.Regexp

	// We use maps here to filter duplicates. Note these are only used in
	// preprocessing and in deserialization.
	WildcardPrefix map[string]bool
	WildcardSuffix map[string]bool

	Rules *Rules
}

// new creates a new rewrite instance from embedded GOB data with asynchronous
// loading of the rule sets to allow the caller to about around a 2 second
// delay.
func newAsync() rewrite {
	h := &https{}

	h.hostsToTargets.Store(make(map[string]*Targets))
	go func() {
		d := newDeserializer()
		temp := d.newHostsToTargets()
		h.hostsToTargets.Store(temp)
	}()

	return h.rewrite
}

// newSync creates a new rewrite instance from embedded GOB data.
func newSync() rewrite {
	h := &https{}
	d := newDeserializer()
	h.hostsToTargets.Store(d.newHostsToTargets())
	return h.rewrite
}

func (h *https) rewrite(url *url.URL) (string, bool) {
	if url.Scheme != "http" {
		return "", false
	}
	start := time.Now()
	host, root := extractHostAndRoot(url)

	if len(root) == 0 {
		log.Error("Root is the empty string!")
		return "", false
	}
	if targets, ok := h.hostsToTargets.Load().(map[string]*Targets)[root]; ok {
		https, done := targets.rewrite(url, host)
		h.addTiming(time.Now().Sub(start), url.String())

		return https, done
	}
	return "", false
}

func (h *https) addTiming(dur time.Duration, host string) {
	nan := dur.Nanoseconds() / 1000
	h.statM.Lock()
	h.runs++
	h.totalTime += nan
	if nan > h.max {
		h.max = nan
		h.maxHost = host
	}
	h.statM.Unlock()

	h.statM.RLock()

	log.Debugf("Average running time: %vms", h.totalTime/h.runs)
	log.Debugf("Max running time: %vms for host: %v", h.max, h.maxHost)
	h.statM.RUnlock()
}

func extractHostAndRoot(url *url.URL) (string, string) {
	host := withoutPort(url.Host)

	// We ignore the second return value which is just a bool indicating whether
	// it's an official ICANN TLD.
	tld, _ := publicsuffix.PublicSuffix(host)

	// Because some TLDs such as "co.uk" include "."s, we strip the TLD prior
	// to stripping subdomains.
	noTLD := strings.TrimSuffix(host, "."+tld)
	root := stripSubdomains(noTLD)
	return host, root
}

func withoutPort(hostport string) string {
	host, _, err := net.SplitHostPort(hostport)
	if err != nil {
		return hostport
	}
	return host
}

func stripSubdomains(host string) string {
	host = strings.TrimSpace(host)
	parts := strings.Split(host, ".")
	return parts[len(parts)-1]
}

func (t *Targets) rewrite(originalURL *url.URL, host string) (string, bool) {
	// We basically want to apply the associated set of rules if any of the
	// targets match the url.
	url := originalURL.String()
	log.Debugf("Attempting to rewrite url %v", url)
	for k := range t.Plain {
		if host == k {
			if r, done := t.Rules.rewrite(url); done {
				return r, done
			}
		}
	}
	if r, done := t.matchTargets(url, t.wildcardPrefix); done {
		return r, done
	}
	return t.matchTargets(url, t.wildcardSuffix)
}

func (t *Targets) matchTargets(url string, targets []*regexp.Regexp) (string, bool) {
	for _, pre := range targets {
		if pre.MatchString(url) {
			if r, done := t.Rules.rewrite(url); done {
				return r, done
			}
		}
	}
	return "", false
}

// rewrite converts the given URL to HTTPS if there is an associated rule for
// it.
func (r *Rules) rewrite(url string) (string, bool) {
	for _, exclude := range r.Exclusions {
		if exclude.pattern.MatchString(url) {
			return "", false
		}
	}
	for _, rule := range r.Rules {
		if rule.from.MatchString(url) {
			return rule.from.ReplaceAllString(url, rule.To), true
		}
	}
	return "", false
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"strconv"

	"time"

	"github.com/andrey-yantsen/teko-astra-go"
	"github.com/contactless/wbgo"
)

type AstraDevice struct {
	wbgo.DeviceBase
	astra         *astra_l.Driver
	device        *astra_l.Device
	address       uint16
	exists        bool
	modelObserver wbgo.ModelObserver
	sensors       map[uint16]*AstraDetector
	model         *AstraModel
}

func (a *AstraDevice) IsVirtual() bool {
	return false
}

func (a *AstraDevice) AcceptValue(name, value string) {
	// ignore retained values
}

func (a *AstraDevice) deleteSensor(id uint16) {
	a.device.DeleteLevel2Device(id)
	a.modelObserver.RemoveDevice(a)
	delete(a.sensors, id)
}

func (a *AstraDevice) getSensor(s astra_l.SensorInfo) *AstraDetector {
	ret := a.getSensorById(s.Id)
	ret.DeviceBase = wbgo.DeviceBase{
		DevName:  fmt.Sprintf("astra_%d_sensor_%d", a.address, s.Id),
		DevTitle: fmt.Sprintf("%s [%d-%d]", s.Type.Name, a.address, s.Id),
	}
	ret.sensorInfo = s
	return ret
}

func (a *AstraDevice) getSensorById(id uint16) *AstraDetector {
	if _, ok := a.sensors[id]; !ok {
		devName := fmt.Sprintf("astra_%d_sensor_%d", a.address, id)
		a.sensors[id] = &AstraDetector{
			DeviceBase: wbgo.DeviceBase{
				DevName: devName,
			},
			d:                 a,
			fieldsInitialized: make(map[string]bool),
		}
	}
	return a.sensors[id]
}

func (a *AstraDevice) AcceptOnValue(name, value string) bool {
	wbgo.Debug.Printf("[%s] AcceptOn %s = %s\n", a.Name(), name, value)
	switch name {
	case "register":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			bcd := a.astra.GetDevice(0xFF)
			if f, err := bcd.FindDevice(); err != nil {
				wbgo.Error.Println("Got error while finding device to register ", err)
				a.Observer.OnValue(a, "register", "0")
			} else {
				if err := a.device.RegisterDevice(f.EUI.GetShortDeviceEUI()); err != nil {
					wbgo.Error.Println("Got error while registering device ", err)
					a.Observer.OnValue(a, "register", "0")
				} else {
					a.device.CreateLevel2Net(1) // init device with channel 1
					a.SetTitle(f.DeviceType.Name)
					a.modelObserver.OnNewDevice(a)
					a.Publish()
				}
			}
		}()
	case "register_l2":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer a.Observer.OnValue(a, "register_l2", "0")
			if s, err := a.device.RegisterLevel2Device(0); err != nil {
				wbgo.Error.Println("Got error while registering L2 device ", err)
			} else {
				a.ensureSensor(*s)
			}
		}()
	case "delete_l2_all":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer a.Observer.OnValue(a, "delete_l2_all", "0")
			if err := a.device.DeleteAllLevel2Devices(); err != nil {
				wbgo.Error.Println("Got error while deleting all L2 devices ", err)
			} else {
				for _, d := range a.sensors {
					a.modelObserver.RemoveDevice(d)
				}
			}
		}()
	case "l2_channel":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer func() {
				if cfg, err := a.device.GetNetLevel2Config(); err != nil {
					wbgo.Error.Println("Got error while checking channel ", err)
				} else {
					wbgo.Info.Println("Channel set to", cfg.ChannelNo)
					a.Observer.OnValue(a, "l2_channel", strconv.Itoa(int(cfg.ChannelNo)))
				}
			}()
			if channelNo, err := strconv.Atoi(value); err != nil {
				wbgo.Error.Println("Unable to convert channelNo to int ", err)
			} else if channelNo == 0 {
				wbgo.Error.Println("Unable to set channel to 0")
			} else {
				if err := a.device.CreateLevel2Net(uint8(channelNo)); err != nil {
					wbgo.Error.Println("Got error while changing channel ", err)
				} else {
					for _, d := range a.sensors {
						a.modelObserver.RemoveDevice(d)
					}
				}
			}
		}()
	case "control_time":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer func() {
				if cfg, err := a.device.GetNetLevel2Config(); err != nil {
					wbgo.Error.Println("Got error while checking control time ", err)
				} else {
					wbgo.Info.Println("Control time set to", cfg.ControlTime)
					a.Observer.OnValue(a, "control_time", strconv.Itoa(int(cfg.ControlTime)))
				}
			}()
			if controlTime, err := strconv.Atoi(value); err != nil {
				wbgo.Error.Println("Unable to convert control time to int ", err)
			} else if controlTime == 0 {
				wbgo.Error.Println("Unable to set control time to 0")
			} else if controlTime > 240 {
				wbgo.Error.Println("Control time should be less than 241")
			} else {
				if cfg, err := a.device.GetNetLevel2Config(); err != nil {
					wbgo.Error.Println("Got error while checking current config", err)
				} else {
					cfgW := astra_l.RfNetParametersToSet{
						ChannelNo:   cfg.ChannelNo,
						ControlTime: uint8(controlTime),
					}
					if err := a.device.SetNetLevel2Config(cfgW); err != nil {
						wbgo.Error.Println("Got error while changing control time ", err)
					}
				}
			}
		}()
	case "new_radio_mode":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer func() {
				if cfg, err := a.device.GetNetLevel2Config(); err != nil {
					wbgo.Error.Println("Got error while checking net config", err)
				} else {
					wbgo.Info.Println("Radio mode set to", cfg.RfType)
					a.Observer.OnValue(a, "new_radio_mode", strconv.Itoa(int(cfg.RfType)))
				}
			}()
			mode := astra_l.RadioModeOld
			if value == "1" {
				mode = astra_l.RadioModeNew
			}
			if err := a.device.SetRadioMode(mode); err != nil {
				wbgo.Error.Println("Got error while setting radio mode", err)
			}
		}()
	}
	return false
}

func (a *AstraDevice) Poll() {
	if a.model.locked {
		return
	}
	a.model.lock()
	defer a.model.unlock()
	if events, err := a.device.GetEvents(); err != nil {
		if err.Error() != "Read timeout" {
			wbgo.Error.Println("Unable to get events ", err)
		}
	} else {
		for _, e := range events {
			wbgo.Info.Printf("Received event %T %+v\n", e, e)
			switch e := e.(type) {
			case astra_l.EventNoLink:
				as := a.ensureSensor(e.GetSensor())
				if !e.IsTestEvent() || a.model.processTestEvents {
					a.Observer.OnValue(a, "Last event time", time.Now().Format(time.UnixDate))
					as.handleEvent(e)
				}
			case astra_l.EventSStateOtherWithNoData, astra_l.EventSStateOtherWithSmoke,
				astra_l.EventSStateOtherWithTemperature, astra_l.EventSStateOtherWithTemperature2,
				astra_l.EventSStateOtherWithPower, astra_l.EventSStateRimRtr, astra_l.EventSStateRtmLC,
				astra_l.EventSStateBrr, astra_l.EventSStateKeychain:
				ev := e.(astra_l.SensorEvent)
				as := a.ensureSensor(ev.GetSensor())
				if !ev.IsTestEvent() || a.model.processTestEvents {
					a.Observer.OnValue(a, "Last event time", time.Now().Format(time.UnixDate))
					as.handleEvent(e)
				}
			case astra_l.EventRRStateTamperNorm:
				a.Observer.OnValue(a, "tamper", "0")
			case astra_l.EventRRStateTamperFault:
				a.Observer.OnValue(a, "tamper", "1")
			case astra_l.EventRRStateMainPsuFault:
				a.Observer.OnValue(a, "main_power_fault", "1")
			case astra_l.EventRRStateMainPsuNorm:
				a.Observer.OnValue(a, "main_power_fault", "0")
			case astra_l.EventRRStateReservePsuFault:
				a.Observer.OnValue(a, "reserve_power_fault", "1")
			case astra_l.EventRRStateReservePsuNorm:
				a.Observer.OnValue(a, "reserve_power_fault", "0")
			case astra_l.EventRRStatePsuFault:
				a.Observer.OnValue(a, "power_fault", "1")
			case astra_l.EventRRStatePsuNorm:
				a.Observer.OnValue(a, "power_fault", "0")
			case astra_l.EventRadioBlocked:
				a.Observer.OnValue(a, "rf_blocked", "1")
			case astra_l.EventRadioOk:
				a.Observer.OnValue(a, "rf_blocked", "0")
			default:
				wbgo.Error.Printf("Received unexpected event %T %+v\n", e, e)
			}
		}
	}
}

var sharedSwitches = map[string]string{
	"register":       "Register new Astra-RI-M",
	"register_l2":    "Register new Level2 detector",
	"delete_l2_all":  "Unregister all Level2 detectors",
	"new_radio_mode": "Use new radio mode",
}

var sharedAlarms = map[string]string{
	"rf_blocked":          "RF blocked",
	"rf_failure":          "RF failure",
	"tamper":              "Tamper",
	"power_fault":         "Power fault",
	"main_power_fault":    "Main power fault",
	"reserve_power_fault": "Reserve power fault",
}

func btosi(b bool) string {
	if b {
		return "1"
	} else {
		return "0"
	}
}

func (a *AstraDevice) initExistsDevice() {
	if a.exists {
		return
	}

	s, err := a.device.GetState()
	if err != nil {
		return
	}

	for alias := range sharedSwitches {
		value := "0"
		if alias == "register" {
			a.Observer.OnNewControl(a, wbgo.Control{
				Name:        alias,
				Title:       sharedSwitches[alias],
				Type:        "switch",
				Value:       value,
				Writability: wbgo.ForceReadOnly,
				Order:       1,
			})
			value = "1"
		}
		a.Observer.OnValue(a, alias, value)
	}

	a.Observer.OnValue(a, "rf_blocked", btosi(s.IsRfBlocked))
	a.Observer.OnValue(a, "rf_failure", btosi(s.IsRfError))
	a.Observer.OnValue(a, "tamper", btosi(s.IsTamper))
	a.Observer.OnValue(a, "power_fault", btosi(s.IsPowerFault))
	a.Observer.OnValue(a, "main_power_fault", btosi(s.IsMainPowerFault))
	a.Observer.OnValue(a, "reserve_power_fault", btosi(s.IsReservePowerFault))

	n, err := a.device.GetNetLevel2Config()
	if err != nil {
		return
	}

	a.Observer.OnValue(a, "l2_channel", strconv.Itoa(int(n.ChannelNo)))
	a.Observer.OnValue(a, "new_radio_mode", strconv.Itoa(int(n.RfType)))
	a.Observer.OnValue(a, "control_time", strconv.Itoa(int(n.ControlTime)))

	a.exists = true
}

func (a *AstraDevice) Publish() {
	for alias, title := range sharedSwitches {
		value := "0"
		a.Observer.OnNewControl(a, wbgo.Control{
			Name:  alias,
			Title: title,
			Type:  "switch",
			Value: value,
		})
	}

	a.Observer.OnNewControl(a, wbgo.Control{
		Name:   "l2_channel",
		Title:  "Level2 net channel No",
		Type:   "range",
		Value:  "0",
		HasMax: true,
		Max:    3,
	})

	a.Observer.OnNewControl(a, wbgo.Control{
		Name:   "control_time",
		Title:  "Radio channel control time",
		Type:   "range",
		Value:  "0",
		HasMax: true,
		Max:    240,
	})

	for alias, title := range sharedAlarms {
		a.Observer.OnNewControl(a, wbgo.Control{
			Name:        alias,
			Title:       title,
			Type:        "alarm",
			Value:       "0",
			Writability: wbgo.ForceReadOnly,
		})
	}

	if _, err := a.device.FindDevice(); err == nil {
		a.initExistsDevice()
	}
}

func (a *AstraDevice) ensureSensor(s astra_l.SensorInfo) *AstraDetector {
	as := a.getSensor(s)
	a.modelObserver.OnNewDevice(as)
	if _, ok := as.fieldsInitialized["publish"]; !ok {
		as.Publish()
		as.fieldsInitialized["publish"] = true
	}
	return as
}
<commit_msg>store `Last event time` in sensor values<commit_after>package main

import (
	"fmt"
	"strconv"

	"time"

	"github.com/andrey-yantsen/teko-astra-go"
	"github.com/contactless/wbgo"
)

type AstraDevice struct {
	wbgo.DeviceBase
	astra         *astra_l.Driver
	device        *astra_l.Device
	address       uint16
	exists        bool
	modelObserver wbgo.ModelObserver
	sensors       map[uint16]*AstraDetector
	model         *AstraModel
}

func (a *AstraDevice) IsVirtual() bool {
	return false
}

func (a *AstraDevice) AcceptValue(name, value string) {
	// ignore retained values
}

func (a *AstraDevice) deleteSensor(id uint16) {
	a.device.DeleteLevel2Device(id)
	a.modelObserver.RemoveDevice(a)
	delete(a.sensors, id)
}

func (a *AstraDevice) getSensor(s astra_l.SensorInfo) *AstraDetector {
	ret := a.getSensorById(s.Id)
	ret.DeviceBase = wbgo.DeviceBase{
		DevName:  fmt.Sprintf("astra_%d_sensor_%d", a.address, s.Id),
		DevTitle: fmt.Sprintf("%s [%d-%d]", s.Type.Name, a.address, s.Id),
	}
	ret.sensorInfo = s
	return ret
}

func (a *AstraDevice) getSensorById(id uint16) *AstraDetector {
	if _, ok := a.sensors[id]; !ok {
		devName := fmt.Sprintf("astra_%d_sensor_%d", a.address, id)
		a.sensors[id] = &AstraDetector{
			DeviceBase: wbgo.DeviceBase{
				DevName: devName,
			},
			d:                 a,
			fieldsInitialized: make(map[string]bool),
		}
	}
	return a.sensors[id]
}

func (a *AstraDevice) AcceptOnValue(name, value string) bool {
	wbgo.Debug.Printf("[%s] AcceptOn %s = %s\n", a.Name(), name, value)
	switch name {
	case "register":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			bcd := a.astra.GetDevice(0xFF)
			if f, err := bcd.FindDevice(); err != nil {
				wbgo.Error.Println("Got error while finding device to register ", err)
				a.Observer.OnValue(a, "register", "0")
			} else {
				if err := a.device.RegisterDevice(f.EUI.GetShortDeviceEUI()); err != nil {
					wbgo.Error.Println("Got error while registering device ", err)
					a.Observer.OnValue(a, "register", "0")
				} else {
					a.device.CreateLevel2Net(1) // init device with channel 1
					a.SetTitle(f.DeviceType.Name)
					a.modelObserver.OnNewDevice(a)
					a.Publish()
				}
			}
		}()
	case "register_l2":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer a.Observer.OnValue(a, "register_l2", "0")
			if s, err := a.device.RegisterLevel2Device(0); err != nil {
				wbgo.Error.Println("Got error while registering L2 device ", err)
			} else {
				a.ensureSensor(*s)
			}
		}()
	case "delete_l2_all":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer a.Observer.OnValue(a, "delete_l2_all", "0")
			if err := a.device.DeleteAllLevel2Devices(); err != nil {
				wbgo.Error.Println("Got error while deleting all L2 devices ", err)
			} else {
				for _, d := range a.sensors {
					a.modelObserver.RemoveDevice(d)
				}
			}
		}()
	case "l2_channel":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer func() {
				if cfg, err := a.device.GetNetLevel2Config(); err != nil {
					wbgo.Error.Println("Got error while checking channel ", err)
				} else {
					wbgo.Info.Println("Channel set to", cfg.ChannelNo)
					a.Observer.OnValue(a, "l2_channel", strconv.Itoa(int(cfg.ChannelNo)))
				}
			}()
			if channelNo, err := strconv.Atoi(value); err != nil {
				wbgo.Error.Println("Unable to convert channelNo to int ", err)
			} else if channelNo == 0 {
				wbgo.Error.Println("Unable to set channel to 0")
			} else {
				if err := a.device.CreateLevel2Net(uint8(channelNo)); err != nil {
					wbgo.Error.Println("Got error while changing channel ", err)
				} else {
					for _, d := range a.sensors {
						a.modelObserver.RemoveDevice(d)
					}
				}
			}
		}()
	case "control_time":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer func() {
				if cfg, err := a.device.GetNetLevel2Config(); err != nil {
					wbgo.Error.Println("Got error while checking control time ", err)
				} else {
					wbgo.Info.Println("Control time set to", cfg.ControlTime)
					a.Observer.OnValue(a, "control_time", strconv.Itoa(int(cfg.ControlTime)))
				}
			}()
			if controlTime, err := strconv.Atoi(value); err != nil {
				wbgo.Error.Println("Unable to convert control time to int ", err)
			} else if controlTime == 0 {
				wbgo.Error.Println("Unable to set control time to 0")
			} else if controlTime > 240 {
				wbgo.Error.Println("Control time should be less than 241")
			} else {
				if cfg, err := a.device.GetNetLevel2Config(); err != nil {
					wbgo.Error.Println("Got error while checking current config", err)
				} else {
					cfgW := astra_l.RfNetParametersToSet{
						ChannelNo:   cfg.ChannelNo,
						ControlTime: uint8(controlTime),
					}
					if err := a.device.SetNetLevel2Config(cfgW); err != nil {
						wbgo.Error.Println("Got error while changing control time ", err)
					}
				}
			}
		}()
	case "new_radio_mode":
		a.model.lock()
		go func() {
			defer a.model.unlock()
			defer func() {
				if cfg, err := a.device.GetNetLevel2Config(); err != nil {
					wbgo.Error.Println("Got error while checking net config", err)
				} else {
					wbgo.Info.Println("Radio mode set to", cfg.RfType)
					a.Observer.OnValue(a, "new_radio_mode", strconv.Itoa(int(cfg.RfType)))
				}
			}()
			mode := astra_l.RadioModeOld
			if value == "1" {
				mode = astra_l.RadioModeNew
			}
			if err := a.device.SetRadioMode(mode); err != nil {
				wbgo.Error.Println("Got error while setting radio mode", err)
			}
		}()
	}
	return false
}

func (a *AstraDevice) Poll() {
	if a.model.locked {
		return
	}
	a.model.lock()
	defer a.model.unlock()
	if events, err := a.device.GetEvents(); err != nil {
		if err.Error() != "Read timeout" {
			wbgo.Error.Println("Unable to get events ", err)
		}
	} else {
		for _, e := range events {
			wbgo.Info.Printf("Received event %T %+v\n", e, e)
			switch e := e.(type) {
			case astra_l.EventNoLink:
				as := a.ensureSensor(e.GetSensor())
				if !e.IsTestEvent() || a.model.processTestEvents {
					as.Observer.OnValue(a, "Last event time", time.Now().Format(time.UnixDate))
					as.handleEvent(e)
				}
			case astra_l.EventSStateOtherWithNoData, astra_l.EventSStateOtherWithSmoke,
				astra_l.EventSStateOtherWithTemperature, astra_l.EventSStateOtherWithTemperature2,
				astra_l.EventSStateOtherWithPower, astra_l.EventSStateRimRtr, astra_l.EventSStateRtmLC,
				astra_l.EventSStateBrr, astra_l.EventSStateKeychain:
				ev := e.(astra_l.SensorEvent)
				as := a.ensureSensor(ev.GetSensor())
				if !ev.IsTestEvent() || a.model.processTestEvents {
					as.Observer.OnValue(a, "Last event time", time.Now().Format(time.UnixDate))
					as.handleEvent(e)
				}
			case astra_l.EventRRStateTamperNorm:
				a.Observer.OnValue(a, "tamper", "0")
			case astra_l.EventRRStateTamperFault:
				a.Observer.OnValue(a, "tamper", "1")
			case astra_l.EventRRStateMainPsuFault:
				a.Observer.OnValue(a, "main_power_fault", "1")
			case astra_l.EventRRStateMainPsuNorm:
				a.Observer.OnValue(a, "main_power_fault", "0")
			case astra_l.EventRRStateReservePsuFault:
				a.Observer.OnValue(a, "reserve_power_fault", "1")
			case astra_l.EventRRStateReservePsuNorm:
				a.Observer.OnValue(a, "reserve_power_fault", "0")
			case astra_l.EventRRStatePsuFault:
				a.Observer.OnValue(a, "power_fault", "1")
			case astra_l.EventRRStatePsuNorm:
				a.Observer.OnValue(a, "power_fault", "0")
			case astra_l.EventRadioBlocked:
				a.Observer.OnValue(a, "rf_blocked", "1")
			case astra_l.EventRadioOk:
				a.Observer.OnValue(a, "rf_blocked", "0")
			default:
				wbgo.Error.Printf("Received unexpected event %T %+v\n", e, e)
			}
		}
	}
}

var sharedSwitches = map[string]string{
	"register":       "Register new Astra-RI-M",
	"register_l2":    "Register new Level2 detector",
	"delete_l2_all":  "Unregister all Level2 detectors",
	"new_radio_mode": "Use new radio mode",
}

var sharedAlarms = map[string]string{
	"rf_blocked":          "RF blocked",
	"rf_failure":          "RF failure",
	"tamper":              "Tamper",
	"power_fault":         "Power fault",
	"main_power_fault":    "Main power fault",
	"reserve_power_fault": "Reserve power fault",
}

func btosi(b bool) string {
	if b {
		return "1"
	} else {
		return "0"
	}
}

func (a *AstraDevice) initExistsDevice() {
	if a.exists {
		return
	}

	s, err := a.device.GetState()
	if err != nil {
		return
	}

	for alias := range sharedSwitches {
		value := "0"
		if alias == "register" {
			a.Observer.OnNewControl(a, wbgo.Control{
				Name:        alias,
				Title:       sharedSwitches[alias],
				Type:        "switch",
				Value:       value,
				Writability: wbgo.ForceReadOnly,
				Order:       1,
			})
			value = "1"
		}
		a.Observer.OnValue(a, alias, value)
	}

	a.Observer.OnValue(a, "rf_blocked", btosi(s.IsRfBlocked))
	a.Observer.OnValue(a, "rf_failure", btosi(s.IsRfError))
	a.Observer.OnValue(a, "tamper", btosi(s.IsTamper))
	a.Observer.OnValue(a, "power_fault", btosi(s.IsPowerFault))
	a.Observer.OnValue(a, "main_power_fault", btosi(s.IsMainPowerFault))
	a.Observer.OnValue(a, "reserve_power_fault", btosi(s.IsReservePowerFault))

	n, err := a.device.GetNetLevel2Config()
	if err != nil {
		return
	}

	a.Observer.OnValue(a, "l2_channel", strconv.Itoa(int(n.ChannelNo)))
	a.Observer.OnValue(a, "new_radio_mode", strconv.Itoa(int(n.RfType)))
	a.Observer.OnValue(a, "control_time", strconv.Itoa(int(n.ControlTime)))

	a.exists = true
}

func (a *AstraDevice) Publish() {
	for alias, title := range sharedSwitches {
		value := "0"
		a.Observer.OnNewControl(a, wbgo.Control{
			Name:  alias,
			Title: title,
			Type:  "switch",
			Value: value,
		})
	}

	a.Observer.OnNewControl(a, wbgo.Control{
		Name:   "l2_channel",
		Title:  "Level2 net channel No",
		Type:   "range",
		Value:  "0",
		HasMax: true,
		Max:    3,
	})

	a.Observer.OnNewControl(a, wbgo.Control{
		Name:   "control_time",
		Title:  "Radio channel control time",
		Type:   "range",
		Value:  "0",
		HasMax: true,
		Max:    240,
	})

	for alias, title := range sharedAlarms {
		a.Observer.OnNewControl(a, wbgo.Control{
			Name:        alias,
			Title:       title,
			Type:        "alarm",
			Value:       "0",
			Writability: wbgo.ForceReadOnly,
		})
	}

	if _, err := a.device.FindDevice(); err == nil {
		a.initExistsDevice()
	}
}

func (a *AstraDevice) ensureSensor(s astra_l.SensorInfo) *AstraDetector {
	as := a.getSensor(s)
	a.modelObserver.OnNewDevice(as)
	if _, ok := as.fieldsInitialized["publish"]; !ok {
		as.Publish()
		as.fieldsInitialized["publish"] = true
	}
	return as
}
<|endoftext|>


<commit_before>package AuthorizeCIM

import (
	"net/http"
	"bytes"
	"io/ioutil"
	"encoding/json"
	"fmt"
)

var api_endpoint string
var apiName string
var apiKey string
var testMode string

var CurrentUser User

func SetAPIInfo(name string, key string, mode string) {
	apiKey = key
	apiName = name
	if mode == "test"
		testMode = "testMode"
		api_endpoint = "https://apitest.authorize.net/xml/v1/request.api"
	else
		testMode = "liveMode"
		api_endpoint = "https://api.authorize.net/xml/v1/request.api"
	end
}

func MakeUser (userID string) User {
	CurrentUser = User{ID: "55", Email: userID, ProfileID: "0"}
	return CurrentUser
}

func CreateCustomerProfile(userInfo AuthUser) (string, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := Profile{MerchantCustomerID: userInfo.Uuid, Description: userInfo.Description, Email: userInfo.Email}
	request := CreateCustomerProfileRequest{authToken, profile}
	newprofile := NewCustomerProfile{request}
	jsoned, _ := json.Marshal(newprofile)
	outgoing, _ := SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	var new_uuid string
	if success {
		new_uuid = outgoing["customerProfileId"].(string)
		CurrentUser.ProfileID = new_uuid
		fmt.Println(CurrentUser)
	} else {
		new_uuid = "0"
	}
	return new_uuid, success
}

func GetCustomerProfile(profileID string) (map[string]interface{}, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := getCustomerProfileRequest{authToken, profileID}
	input := CustomerProfile{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ :=SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	fmt.Println(outgoing)
	userProfile := outgoing["profile"].(map[string]interface{})
	fmt.Println(userProfile)
	return outgoing, success
}

func GetAllProfiles() []interface{} {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profilerequest := getCustomerProfileIdsRequest{authToken}
	all := AllCustomerProfileIds{profilerequest}
	jsoned, _ := json.Marshal(all)
	outgoing, _ :=SendRequest(string(jsoned))
	return outgoing["ids"].([]interface{})
}

func DeleteCustomerProfile(profileID string) bool {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := deleteCustomerProfileRequest{authToken, profileID}
	input := deleteCustomerProfile{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func CreateCustomerBillingProfile(profileID string, creditCard CreditCard, address Address) (string, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	paymentProfile := PaymentBillingProfile{Address: address, Payment: Payment{CreditCard:creditCard}}
	request := CreateCustomerBillingProfileRequest{authToken, profileID, paymentProfile, testMode}
	newprofile := NewCustomerBillingProfile{request}
	jsoned, _ := json.Marshal(newprofile)
	outgoing, _ :=SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	var new_paymentID string
	if status {
		new_paymentID = outgoing["customerPaymentProfileId"].(string)
		fmt.Println(CurrentUser)
	} else {
		new_paymentID = "0"
	}
	return new_paymentID, status
}

func GetCustomerPaymentProfile(profileID string, paymentID string) (map[string]interface{}, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := CustomerPaymentProfileRequest{authToken, profileID, paymentID}
	input := getCustomerPaymentProfileRequest{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ := SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	fmt.Println(CurrentUser)
	return outgoing["paymentProfile"].(map[string]interface{}), success
}

func UpdateCustomerPaymentProfile(profileID string, paymentID string, creditCard CreditCard, address Address) bool {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	new_billing := UpdatePaymentBillingProfile{Address: address, Payment: Payment{CreditCard:creditCard}, CustomerPaymentProfileId: paymentID}
	profile := updateCustomerPaymentProfileRequest{authToken, profileID, new_billing, testMode}
	input := changeCustomerPaymentProfileRequest{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func DeleteCustomerPaymentProfile(profileID string, paymentID string) bool {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := deleteCustomerPaymentProfile{authToken, profileID, paymentID}
	input := deleteCustomerPaymentProfileRequest{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ :=SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func SendRequest(input string) (map[string]interface{}, interface{}) {
	req, err := http.NewRequest("POST", api_endpoint, bytes.NewBuffer([]byte(input)))
	req.Header.Set("Content-Type", "application/json")
	errors := false
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	body = bytes.TrimPrefix(body, []byte("\xef\xbb\xbf"))
	var dat map[string]interface{}
	//fmt.Printf(string(body))
	err = json.Unmarshal(body, &dat)
	if err!=nil {
		panic(err)
	}
	return dat, errors
}

func CreateTransaction(profileID string, paymentID string, item LineItem, amount string) (map[string]interface{}, bool, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	items := LineItems{LineItem: item}
	subProfile := SubProfile{CustomerPaymentProfileId: paymentID}
	transProfile := TranProfile{CustomerProfileId: profileID, SubProfile: subProfile}
	transaction := TransactionRequest{TransactionType: "authCaptureTransaction", Amount: amount, TranProfile: transProfile, LineItems: items}
	tranxrequest := CreateTransactionRequest{MerchantAuthentication: authToken, RefID: "none33", TransactionRequest: transaction}
	doTranx := DoCreateTransaction{tranxrequest}
	jsoned, _ := json.Marshal(doTranx)
	outgoing, _ := SendRequest(string(jsoned))
	var status, approved bool
	var response map[string]interface{}
	if outgoing["responseCode"]!=nil {
		if outgoing["responseCode"].(string) != "1" {
			approved = false
			status = true
			response = map[string]interface{}{}
		} else {
			status = FindResultCode(outgoing)
			approved = TransactionApproved(outgoing)
			response = outgoing["transactionResponse"].(map[string]interface{})
		}
	} else {
		approved = false
		status = false
		response = map[string]interface{}{}
	}

	return response, approved, status
}

func TestConnection() bool {
	authToken := AuthenticateTestRequest{MerchantAuthentication{Name: apiName, TransactionKey: apiKey}}
	authnettest := AuthorizeNetTest{AuthenticateTestRequest:authToken}
	jsoned, _ := json.Marshal(authnettest)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func CreateShippingAddress(profileID string, address Address) (string, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	customerShipping := CustomerShippingAddress{authToken,profileID,address}
	customerShippingRequest := CustomerShippingAddressRequest{customerShipping}
	jsoned, _ := json.Marshal(customerShippingRequest)
	outgoing, _ := SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	var new_address_id string
	if !success {
		new_address_id = "0"
	} else {
		new_address_id = outgoing["customerAddressId"].(string)
	}
	return new_address_id, success
}

func GetShippingAddress(profileID string, shippingID string) (map[string]interface{}, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	customerShipping := GetCustomerShippingAddress{authToken,profileID,shippingID}
	customerShippingRequest := GetCustomerShippingAddressRequest{customerShipping}
	jsoned, _ := json.Marshal(customerShippingRequest)
	outgoing, _ := SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	return outgoing["address"].(map[string]interface{}), success
}

func DeleteShippingAddress(profileID string, shippingID string) bool {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	customerShipping := GetCustomerShippingAddress{authToken,profileID,shippingID}
	customerShippingRequest := DeleteCustomerShippingAddressRequest{customerShipping}
	jsoned, _ := json.Marshal(customerShippingRequest)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func GetTransactionDetails(tranID string) map[string]interface{} {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	transDetails := TransactionDetails{authToken,tranID}
	transactionRequest := TransactionDetailsRequest{transDetails}
	jsoned, _ := json.Marshal(transactionRequest)
	outgoing, _ := SendRequest(string(jsoned))
	if outgoing["transaction"]!=nil {
		return outgoing["transaction"].(map[string]interface{})
	}
	return map[string]interface{}{}
}

func FindResultCode(incoming map[string]interface{}) bool {
	messages, _ := incoming["messages"].(map[string]interface{})
	if messages!=nil {
		if messages["resultCode"] == "Ok" {
			return true
		}
	}
	return false
}

func TransactionApproved(incoming map[string]interface{}) bool {
	if incoming!=nil {
		messages, _ := incoming["transactionResponse"].(map[string]interface{})
		if messages["responseCode"] == "1" {
			return true
		}
	}
	return false
}

func CreateSubscription(newSubscription Subscription) (string, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	subscriptonSubmit := CreateSubscriptionRequest{ARBCreateSubscription{authToken, newSubscription}}
	jsoned, _ := json.Marshal(subscriptonSubmit)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	if status {
		return outgoing["subscriptionId"].(string), status
	}
	return "0", status
}

func RefundTransactions(){

}

func VoidTransaction(){

}

func DeleteSubscription(){

}

func UpdateSubscription(){

}

func GetSubscriptions(){

}

<commit_msg>fixed, ops, added test and live mode<commit_after>package AuthorizeCIM

import (
	"net/http"
	"bytes"
	"io/ioutil"
	"encoding/json"
	"fmt"
)

var api_endpoint string
var apiName string
var apiKey string
var testMode string

var CurrentUser User

func SetAPIInfo(name string, key string, mode string) {
	apiKey = key
	apiName = name
	if mode == "test" {
		testMode = "testMode"
		api_endpoint = "https://apitest.authorize.net/xml/v1/request.api"
	} else {
		testMode = "liveMode"
		api_endpoint = "https://api.authorize.net/xml/v1/request.api"
	}
}

func MakeUser (userID string) User {
	CurrentUser = User{ID: "55", Email: userID, ProfileID: "0"}
	return CurrentUser
}

func CreateCustomerProfile(userInfo AuthUser) (string, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := Profile{MerchantCustomerID: userInfo.Uuid, Description: userInfo.Description, Email: userInfo.Email}
	request := CreateCustomerProfileRequest{authToken, profile}
	newprofile := NewCustomerProfile{request}
	jsoned, _ := json.Marshal(newprofile)
	outgoing, _ := SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	var new_uuid string
	if success {
		new_uuid = outgoing["customerProfileId"].(string)
		CurrentUser.ProfileID = new_uuid
		fmt.Println(CurrentUser)
	} else {
		new_uuid = "0"
	}
	return new_uuid, success
}

func GetCustomerProfile(profileID string) (map[string]interface{}, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := getCustomerProfileRequest{authToken, profileID}
	input := CustomerProfile{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ :=SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	fmt.Println(outgoing)
	userProfile := outgoing["profile"].(map[string]interface{})
	fmt.Println(userProfile)
	return outgoing, success
}

func GetAllProfiles() []interface{} {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profilerequest := getCustomerProfileIdsRequest{authToken}
	all := AllCustomerProfileIds{profilerequest}
	jsoned, _ := json.Marshal(all)
	outgoing, _ :=SendRequest(string(jsoned))
	return outgoing["ids"].([]interface{})
}

func DeleteCustomerProfile(profileID string) bool {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := deleteCustomerProfileRequest{authToken, profileID}
	input := deleteCustomerProfile{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func CreateCustomerBillingProfile(profileID string, creditCard CreditCard, address Address) (string, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	paymentProfile := PaymentBillingProfile{Address: address, Payment: Payment{CreditCard:creditCard}}
	request := CreateCustomerBillingProfileRequest{authToken, profileID, paymentProfile, testMode}
	newprofile := NewCustomerBillingProfile{request}
	jsoned, _ := json.Marshal(newprofile)
	outgoing, _ :=SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	var new_paymentID string
	if status {
		new_paymentID = outgoing["customerPaymentProfileId"].(string)
		fmt.Println(CurrentUser)
	} else {
		new_paymentID = "0"
	}
	return new_paymentID, status
}

func GetCustomerPaymentProfile(profileID string, paymentID string) (map[string]interface{}, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := CustomerPaymentProfileRequest{authToken, profileID, paymentID}
	input := getCustomerPaymentProfileRequest{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ := SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	fmt.Println(CurrentUser)
	return outgoing["paymentProfile"].(map[string]interface{}), success
}

func UpdateCustomerPaymentProfile(profileID string, paymentID string, creditCard CreditCard, address Address) bool {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	new_billing := UpdatePaymentBillingProfile{Address: address, Payment: Payment{CreditCard:creditCard}, CustomerPaymentProfileId: paymentID}
	profile := updateCustomerPaymentProfileRequest{authToken, profileID, new_billing, testMode}
	input := changeCustomerPaymentProfileRequest{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func DeleteCustomerPaymentProfile(profileID string, paymentID string) bool {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	profile := deleteCustomerPaymentProfile{authToken, profileID, paymentID}
	input := deleteCustomerPaymentProfileRequest{profile}
	jsoned, _ := json.Marshal(input)
	outgoing, _ :=SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func SendRequest(input string) (map[string]interface{}, interface{}) {
	req, err := http.NewRequest("POST", api_endpoint, bytes.NewBuffer([]byte(input)))
	req.Header.Set("Content-Type", "application/json")
	errors := false
	client := &http.Client{}
	resp, err := client.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()
	body, _ := ioutil.ReadAll(resp.Body)
	body = bytes.TrimPrefix(body, []byte("\xef\xbb\xbf"))
	var dat map[string]interface{}
	//fmt.Printf(string(body))
	err = json.Unmarshal(body, &dat)
	if err!=nil {
		panic(err)
	}
	return dat, errors
}

func CreateTransaction(profileID string, paymentID string, item LineItem, amount string) (map[string]interface{}, bool, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	items := LineItems{LineItem: item}
	subProfile := SubProfile{CustomerPaymentProfileId: paymentID}
	transProfile := TranProfile{CustomerProfileId: profileID, SubProfile: subProfile}
	transaction := TransactionRequest{TransactionType: "authCaptureTransaction", Amount: amount, TranProfile: transProfile, LineItems: items}
	tranxrequest := CreateTransactionRequest{MerchantAuthentication: authToken, RefID: "none33", TransactionRequest: transaction}
	doTranx := DoCreateTransaction{tranxrequest}
	jsoned, _ := json.Marshal(doTranx)
	outgoing, _ := SendRequest(string(jsoned))
	var status, approved bool
	var response map[string]interface{}
	if outgoing["responseCode"]!=nil {
		if outgoing["responseCode"].(string) != "1" {
			approved = false
			status = true
			response = map[string]interface{}{}
		} else {
			status = FindResultCode(outgoing)
			approved = TransactionApproved(outgoing)
			response = outgoing["transactionResponse"].(map[string]interface{})
		}
	} else {
		approved = false
		status = false
		response = map[string]interface{}{}
	}

	return response, approved, status
}

func TestConnection() bool {
	authToken := AuthenticateTestRequest{MerchantAuthentication{Name: apiName, TransactionKey: apiKey}}
	authnettest := AuthorizeNetTest{AuthenticateTestRequest:authToken}
	jsoned, _ := json.Marshal(authnettest)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func CreateShippingAddress(profileID string, address Address) (string, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	customerShipping := CustomerShippingAddress{authToken,profileID,address}
	customerShippingRequest := CustomerShippingAddressRequest{customerShipping}
	jsoned, _ := json.Marshal(customerShippingRequest)
	outgoing, _ := SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	var new_address_id string
	if !success {
		new_address_id = "0"
	} else {
		new_address_id = outgoing["customerAddressId"].(string)
	}
	return new_address_id, success
}

func GetShippingAddress(profileID string, shippingID string) (map[string]interface{}, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	customerShipping := GetCustomerShippingAddress{authToken,profileID,shippingID}
	customerShippingRequest := GetCustomerShippingAddressRequest{customerShipping}
	jsoned, _ := json.Marshal(customerShippingRequest)
	outgoing, _ := SendRequest(string(jsoned))
	success := FindResultCode(outgoing)
	return outgoing["address"].(map[string]interface{}), success
}

func DeleteShippingAddress(profileID string, shippingID string) bool {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	customerShipping := GetCustomerShippingAddress{authToken,profileID,shippingID}
	customerShippingRequest := DeleteCustomerShippingAddressRequest{customerShipping}
	jsoned, _ := json.Marshal(customerShippingRequest)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	return status
}

func GetTransactionDetails(tranID string) map[string]interface{} {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	transDetails := TransactionDetails{authToken,tranID}
	transactionRequest := TransactionDetailsRequest{transDetails}
	jsoned, _ := json.Marshal(transactionRequest)
	outgoing, _ := SendRequest(string(jsoned))
	if outgoing["transaction"]!=nil {
		return outgoing["transaction"].(map[string]interface{})
	}
	return map[string]interface{}{}
}

func FindResultCode(incoming map[string]interface{}) bool {
	messages, _ := incoming["messages"].(map[string]interface{})
	if messages!=nil {
		if messages["resultCode"] == "Ok" {
			return true
		}
	}
	return false
}

func TransactionApproved(incoming map[string]interface{}) bool {
	if incoming!=nil {
		messages, _ := incoming["transactionResponse"].(map[string]interface{})
		if messages["responseCode"] == "1" {
			return true
		}
	}
	return false
}

func CreateSubscription(newSubscription Subscription) (string, bool) {
	authToken := MerchantAuthentication{Name: apiName, TransactionKey: apiKey}
	subscriptonSubmit := CreateSubscriptionRequest{ARBCreateSubscription{authToken, newSubscription}}
	jsoned, _ := json.Marshal(subscriptonSubmit)
	outgoing, _ := SendRequest(string(jsoned))
	status := FindResultCode(outgoing)
	if status {
		return outgoing["subscriptionId"].(string), status
	}
	return "0", status
}

func RefundTransactions(){

}

func VoidTransaction(){

}

func DeleteSubscription(){

}

func UpdateSubscription(){

}

func GetSubscriptions(){

}

<|endoftext|>