<commit_before>package query

import (
	"github.com/golang/glog"
	"github.com/tstromberg/campwiz/result"
)

// filter applies post-fetch criteria filtering.
func filter(c Criteria, res result.Results) result.Results {
	glog.V(1).Infof("Filtering %d results ...", len(res))
	var filtered result.Results

	for _, r := range res {
		if c.IncludeGroup && r.Availability[0].Group > 0 {
			filtered = append(filtered, r)
			continue
		}
		if c.IncludeBoatIn && r.Availability[0].Boat > 0 {
			filtered = append(filtered, r)
			continue
		}
		if c.IncludeWalkIn && r.Availability[0].WalkIn > 0 {
			filtered = append(filtered, r)
			continue
		}
		if c.IncludeStandard && r.Availability[0].Standard > 0 {
			filtered = append(filtered, r)
			continue
		}
	}
	return filtered
}
<commit_msg>Implement distance filtering.<commit_after>package query

import (
	"github.com/golang/glog"
	"github.com/labstack/gommon/log"
	"github.com/tstromberg/campwiz/result"
)

// filter applies post-fetch criteria filtering.
func filter(c Criteria, res result.Results) result.Results {
	glog.V(1).Infof("Filtering %d results ...", len(res))
	var filtered result.Results

	for _, r := range res {
		glog.Infof("Filtering %s: %+v against %+v", r.Name, r, c)
		if c.MaxDistance < int(r.Distance) {
			glog.Infof("%s is too far (%.0f miles)", r.Name, r.Distance)
			continue
		}
		if c.IncludeGroup && r.Availability[0].Group > 0 {
			filtered = append(filtered, r)
			log.Infof("Passes group filter: %+v", r.Name)
			continue
		}
		if c.IncludeBoatIn && r.Availability[0].Boat > 0 {
			filtered = append(filtered, r)
			log.Infof("Passes boat filter: %+v", r.Name)
			continue
		}
		if c.IncludeWalkIn && r.Availability[0].WalkIn > 0 {
			filtered = append(filtered, r)
			log.Infof("Passes walk-in filter: %+v", r.Name)
			continue
		}
		if c.IncludeStandard && r.Availability[0].Standard > 0 {
			filtered = append(filtered, r)
			log.Infof("Passes standard filter: %+v", r.Name)
			continue
		}
	}
	return filtered
}
<|endoftext|>


<commit_before>// Copyright 2015 Square Inc.
//
// 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 query contains all the logic to parse
// and execute queries against the underlying metric system.
package query

import (
	"errors"
	"fmt"
	"regexp"
	"runtime"
	"strings"

	"github.com/square/metrics/api"
)

// Parse is the entry point of the parser.
// It does the following:
// * Parses the given query string.
// * Checks for any syntax error (detected by peg during Parse())
// or logic error (detected while traversing the parse tree in Execute()).
// * Returns the final Command resulting from the parsing.
//
// The parsing is done in the following ways.
// 1. Parse() method constructs the abstract syntax tree.
// 2. Execute() method visits each node in-order, executing the
// snippet of code embedded in the grammar definition.
//
// Details on Execute():
//   Execute traverses the AST to generate more refined output
// from the AST. Our final output is Command, representing the
// procedural operation of the query.
// To generate this, we maintain a stack of processed AST nodes
// in our parser object. (AST nodes are abstracted away by PEG.
// Processed nodes are represented by the go interface Node).
//
// * The stack starts empty.
// * Nodes are repeatedly pushed and popped during the traversal.
// * At the end of the run, stack should be empty and a single
// Command object is produced. (Technically Command could've been
// pushed to the stack also)
// * Each AST node can make intelligent assumptions on the current
// state of the node stack. This information is a bit implicit but
// enforced via type assertions throughout the code.
//
// returns either:
// * command
// * SyntaxError (user error - query is invalid)
// * AssertionError (programming error, and is a sign of a bug).
func Parse(query string) (Command, error) {
	p := Parser{Buffer: query}
	p.Init()
	if err := p.Parse(); err != nil {
		// Parsing error - invalid syntax.
		// TODO - return the token where the error is occurring.
		return nil, SyntaxErrors([]SyntaxError{{
			token:   "",
			message: err.Error(),
		}})
	}
	p.Execute()
	if len(p.assertions) > 0 {
		// logic error - an internal constraint is violated.
		// TODO - log this error internally.
		return nil, AssertionError{"Assertion Error: Programming error"}
	}
	if len(p.nodeStack) > 0 {
		return nil, AssertionError{"Assertion Error: Node stack is not empty"}
	}
	if len(p.errors) > 0 {
		// user error - an invalid query is provided.
		return nil, SyntaxErrors(p.errors)
	}
	if p.command == nil {
		// after parsing has finished, there should be a command available.
		return nil, AssertionError{"Assertion Error: No command"}
	}
	return p.command, nil
}

// Error functions
// ===============
// these functions are called to mark that an error has occurred
// while parsing or constructing command.

func (p *Parser) flagSyntaxError(err SyntaxError) {
	p.errors = append(p.errors, err)
}

func (p *Parser) flagAssert(err error) {
	p.assertions = append(p.assertions, err)
}

func (p *Parser) flagTypeAssertion(typeString string) {
	p.flagAssert(fmt.Errorf("[%s] expected %s", functionName(1), typeString))
}

// Generic Stack Operation
// =======================
func (p *Parser) popNode() Node {
	l := len(p.nodeStack)
	if l == 0 {
		p.flagAssert(errors.New("popNode() on an empty stack"))
		return nil
	}
	node := p.nodeStack[l-1]
	p.nodeStack = p.nodeStack[:l-1]
	return node
}

func (p *Parser) peekNode() Node {
	l := len(p.nodeStack)
	if l == 0 {
		p.flagAssert(errors.New("peekNode() on an empty stack"))
		return nil
	}
	node := p.nodeStack[l-1]
	return node
}

func (p *Parser) pushNode(node Node) {
	p.nodeStack = append(p.nodeStack, node)
}

// Modification Operations
// =======================
// These operations are used by the embedded code snippets in language.peg
func (p *Parser) makeDescribe() {
	predicateNode, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
		return
	}
	literalNode, ok := p.popNode().(*literalNode)
	if !ok {
		p.flagTypeAssertion("literalNode")
		return
	}
	p.command = &DescribeCommand{
		metricName: api.MetricKey(literalNode.literal),
		predicate:  predicateNode,
	}
}

func (p *Parser) makeDescribeAll() {
	p.command = &DescribeAllCommand{}
}

func (p *Parser) addLiteralMatcher() {
	literalNode, ok := p.popNode().(*literalNode)
	if !ok {
		p.flagTypeAssertion("literalNode")
		return
	}
	tagNode, ok := p.popNode().(*tagNode)
	if !ok {
		p.flagTypeAssertion("tagNode")
		return
	}
	p.pushNode(&listMatcher{
		tag:     tagNode.tag,
		matches: []string{literalNode.literal},
	})
}

func (p *Parser) addListMatcher() {
	literalNode, ok := p.popNode().(*literalListNode)
	if !ok {
		p.flagTypeAssertion("literalNode")
		return
	}
	tagNode, ok := p.popNode().(*tagNode)
	if !ok {
		p.flagTypeAssertion("tagNode")
		return
	}
	p.pushNode(&listMatcher{
		tag:     tagNode.tag,
		matches: literalNode.literals,
	})
}

func (p *Parser) addRegexMatcher() {
	literalNode, ok := p.popNode().(*literalNode)
	if !ok {
		p.flagTypeAssertion("literalNode")
		return
	}
	tagNode, ok := p.popNode().(*tagNode)
	if !ok {
		p.flagTypeAssertion("tagNode")
		return
	}
	compiled, err := regexp.Compile(literalNode.literal)
	if err != nil {
		// TODO - return more user-friendly error.
		p.flagSyntaxError(SyntaxError{
			token:   literalNode.literal,
			message: fmt.Sprintf("Cannot parse the regex: %s", err.Error()),
		})
	}
	p.pushNode(&regexMatcher{
		tag:   tagNode.tag,
		regex: compiled,
	})
}

func (p *Parser) addTag(tag string) {
	p.pushNode(&tagNode{tag: tag})
}

func (p *Parser) addLiteralListNode() {
	p.pushNode(&literalListNode{make([]string, 0)})
}

func (p *Parser) addLiteralNode(literal string) {
	p.pushNode(&literalNode{literal})
}

func (p *Parser) appendLiteral(literal string) {
	literalNode, ok := p.peekNode().(*literalListNode)
	if ok {
		literalNode.literals = append(literalNode.literals, literal)
	} else {
		p.flagTypeAssertion("literalNode")
	}
}

func (p *Parser) addNotPredicate() {
	predicate, ok := p.popNode().(Predicate)
	if ok {
		p.pushNode(&notPredicate{predicate})
	} else {
		p.flagTypeAssertion("Predicate")
	}
}
func (p *Parser) addOrPredicate() {
	rightPredicate, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
	}
	leftPredicate, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
	}
	p.pushNode(&orPredicate{
		predicates: []Predicate{
			leftPredicate,
			rightPredicate,
		},
	})
}

func (p *Parser) addNullPredicate() {
	p.pushNode(&andPredicate{predicates: []Predicate{}})
}

func (p *Parser) addAndPredicate() {
	rightPredicate, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
	}
	leftPredicate, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
	}
	p.pushNode(&andPredicate{
		predicates: []Predicate{
			leftPredicate,
			rightPredicate,
		},
	})
}

// used to unescape:
// - identifiers (no unescaping required).
// - quoted strings.
func unescapeLiteral(escaped string) string {
	if len(escaped) <= 1 {
		return escaped
	}
	escapedCharacters := []string{
		"'", "`", "\"", "\\",
	}
	processed := escaped
	first := processed[0]
	if first == '\'' || first == '"' || first == '`' {
		processed = processed[1 : len(processed)-1]
		for _, char := range escapedCharacters {
			processed = strings.Replace(processed, `\`+char, char, -1)
		}
	}
	return processed
}

var functionNameRegex = regexp.MustCompile(`[^./]+$`)

// name of the function on the stack.
// depth(0) - name of the function calling functionName(0)
// each additional depth traverses the stack frame further towards the caller.
func functionName(depth int) string {
	pc := make([]uintptr, 1)
	runtime.Callers(depth+2, pc)
	f := runtime.FuncForPC(pc[0])
	return functionNameRegex.FindString(f.Name())
}
<commit_msg>addressing comment<commit_after>// Copyright 2015 Square Inc.
//
// 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 query contains all the logic to parse
// and execute queries against the underlying metric system.
package query

import (
	"errors"
	"fmt"
	"regexp"
	"runtime"
	"strings"

	"github.com/square/metrics/api"
)

// Parse is the entry point of the parser.
// It does the following:
// * Parses the given query string.
// * Checks for any syntax error (detected by peg during Parse())
// or logic error (detected while traversing the parse tree in Execute()).
// * Returns the final Command resulting from the parsing.
//
// The parsing is done in the following ways.
// 1. Parse() method constructs the abstract syntax tree.
// 2. Execute() method visits each node in-order, executing the
// snippet of code embedded in the grammar definition.
//
// Details on Execute():
//   Execute traverses the AST to generate more refined output
// from the AST. Our final output is Command, representing the
// procedural operation of the query.
// To generate this, we maintain a stack of processed AST nodes
// in our parser object. (AST nodes are abstracted away by PEG.
// Processed nodes are represented by the go interface Node).
//
// * The stack starts empty.
// * Nodes are repeatedly pushed and popped during the traversal.
// * At the end of the run, stack should be empty and a single
// Command object is produced. (Technically Command could've been
// pushed to the stack also)
// * Each AST node can make intelligent assumptions on the current
// state of the node stack. This information is a bit implicit but
// enforced via type assertions throughout the code.
//
// returns either:
// * command
// * SyntaxError (user error - query is invalid)
// * AssertionError (programming error, and is a sign of a bug).
func Parse(query string) (Command, error) {
	p := Parser{Buffer: query}
	p.Init()
	if err := p.Parse(); err != nil {
		// Parsing error - invalid syntax.
		// TODO - return the token where the error is occurring.
		return nil, SyntaxErrors([]SyntaxError{{
			token:   "",
			message: err.Error(),
		}})
	}
	p.Execute()
	if len(p.assertions) > 0 {
		// logic error - an internal constraint is violated.
		// TODO - log this error internally.
		return nil, AssertionError{"Programming error"}
	}
	if len(p.nodeStack) > 0 {
		return nil, AssertionError{"Node stack is not empty"}
	}
	if len(p.errors) > 0 {
		// user error - an invalid query is provided.
		return nil, SyntaxErrors(p.errors)
	}
	if p.command == nil {
		// after parsing has finished, there should be a command available.
		return nil, AssertionError{"No command"}
	}
	return p.command, nil
}

// Error functions
// ===============
// these functions are called to mark that an error has occurred
// while parsing or constructing command.

func (p *Parser) flagSyntaxError(err SyntaxError) {
	p.errors = append(p.errors, err)
}

func (p *Parser) flagAssert(err error) {
	p.assertions = append(p.assertions, err)
}

func (p *Parser) flagTypeAssertion(typeString string) {
	p.flagAssert(fmt.Errorf("[%s] expected %s", functionName(1), typeString))
}

// Generic Stack Operation
// =======================
func (p *Parser) popNode() Node {
	l := len(p.nodeStack)
	if l == 0 {
		p.flagAssert(errors.New("popNode() on an empty stack"))
		return nil
	}
	node := p.nodeStack[l-1]
	p.nodeStack = p.nodeStack[:l-1]
	return node
}

func (p *Parser) peekNode() Node {
	l := len(p.nodeStack)
	if l == 0 {
		p.flagAssert(errors.New("peekNode() on an empty stack"))
		return nil
	}
	node := p.nodeStack[l-1]
	return node
}

func (p *Parser) pushNode(node Node) {
	p.nodeStack = append(p.nodeStack, node)
}

// Modification Operations
// =======================
// These operations are used by the embedded code snippets in language.peg
func (p *Parser) makeDescribe() {
	predicateNode, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
		return
	}
	literalNode, ok := p.popNode().(*literalNode)
	if !ok {
		p.flagTypeAssertion("literalNode")
		return
	}
	p.command = &DescribeCommand{
		metricName: api.MetricKey(literalNode.literal),
		predicate:  predicateNode,
	}
}

func (p *Parser) makeDescribeAll() {
	p.command = &DescribeAllCommand{}
}

func (p *Parser) addLiteralMatcher() {
	literalNode, ok := p.popNode().(*literalNode)
	if !ok {
		p.flagTypeAssertion("literalNode")
		return
	}
	tagNode, ok := p.popNode().(*tagNode)
	if !ok {
		p.flagTypeAssertion("tagNode")
		return
	}
	p.pushNode(&listMatcher{
		tag:     tagNode.tag,
		matches: []string{literalNode.literal},
	})
}

func (p *Parser) addListMatcher() {
	literalNode, ok := p.popNode().(*literalListNode)
	if !ok {
		p.flagTypeAssertion("literalNode")
		return
	}
	tagNode, ok := p.popNode().(*tagNode)
	if !ok {
		p.flagTypeAssertion("tagNode")
		return
	}
	p.pushNode(&listMatcher{
		tag:     tagNode.tag,
		matches: literalNode.literals,
	})
}

func (p *Parser) addRegexMatcher() {
	literalNode, ok := p.popNode().(*literalNode)
	if !ok {
		p.flagTypeAssertion("literalNode")
		return
	}
	tagNode, ok := p.popNode().(*tagNode)
	if !ok {
		p.flagTypeAssertion("tagNode")
		return
	}
	compiled, err := regexp.Compile(literalNode.literal)
	if err != nil {
		// TODO - return more user-friendly error.
		p.flagSyntaxError(SyntaxError{
			token:   literalNode.literal,
			message: fmt.Sprintf("Cannot parse the regex: %s", err.Error()),
		})
	}
	p.pushNode(&regexMatcher{
		tag:   tagNode.tag,
		regex: compiled,
	})
}

func (p *Parser) addTag(tag string) {
	p.pushNode(&tagNode{tag: tag})
}

func (p *Parser) addLiteralListNode() {
	p.pushNode(&literalListNode{make([]string, 0)})
}

func (p *Parser) addLiteralNode(literal string) {
	p.pushNode(&literalNode{literal})
}

func (p *Parser) appendLiteral(literal string) {
	literalNode, ok := p.peekNode().(*literalListNode)
	if ok {
		literalNode.literals = append(literalNode.literals, literal)
	} else {
		p.flagTypeAssertion("literalNode")
	}
}

func (p *Parser) addNotPredicate() {
	predicate, ok := p.popNode().(Predicate)
	if ok {
		p.pushNode(&notPredicate{predicate})
	} else {
		p.flagTypeAssertion("Predicate")
	}
}
func (p *Parser) addOrPredicate() {
	rightPredicate, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
	}
	leftPredicate, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
	}
	p.pushNode(&orPredicate{
		predicates: []Predicate{
			leftPredicate,
			rightPredicate,
		},
	})
}

func (p *Parser) addNullPredicate() {
	p.pushNode(&andPredicate{predicates: []Predicate{}})
}

func (p *Parser) addAndPredicate() {
	rightPredicate, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
	}
	leftPredicate, ok := p.popNode().(Predicate)
	if !ok {
		p.flagTypeAssertion("Predicate")
	}
	p.pushNode(&andPredicate{
		predicates: []Predicate{
			leftPredicate,
			rightPredicate,
		},
	})
}

// used to unescape:
// - identifiers (no unescaping required).
// - quoted strings.
func unescapeLiteral(escaped string) string {
	if len(escaped) <= 1 {
		return escaped
	}
	escapedCharacters := []string{
		"'", "`", "\"", "\\",
	}
	processed := escaped
	first := processed[0]
	if first == '\'' || first == '"' || first == '`' {
		processed = processed[1 : len(processed)-1]
		for _, char := range escapedCharacters {
			processed = strings.Replace(processed, `\`+char, char, -1)
		}
	}
	return processed
}

var functionNameRegex = regexp.MustCompile(`[^./]+$`)

// name of the function on the stack.
// depth(0) - name of the function calling functionName(0)
// each additional depth traverses the stack frame further towards the caller.
func functionName(depth int) string {
	pc := make([]uintptr, 1)
	runtime.Callers(depth+2, pc)
	f := runtime.FuncForPC(pc[0])
	return functionNameRegex.FindString(f.Name())
}
<|endoftext|>


<commit_before>// Copyright 2018 The Terraformer 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 provider_wrapper

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"

	"github.com/zclconf/go-cty/cty/gocty"

	"github.com/zclconf/go-cty/cty"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-plugin"
	"github.com/hashicorp/terraform/configs/configschema"
	tfplugin "github.com/hashicorp/terraform/plugin"
	"github.com/hashicorp/terraform/providers"
	"github.com/hashicorp/terraform/terraform"
	"github.com/hashicorp/terraform/version"
)

// DefaultDataDir is the default directory for storing local data.
const DefaultDataDir = ".terraform"

// DefaultPluginVendorDir is the location in the config directory to look for
// user-added plugin binaries. Terraform only reads from this path if it
// exists, it is never created by terraform.
const DefaultPluginVendorDir = "terraform.d/plugins/" + pluginMachineName

// pluginMachineName is the directory name used in new plugin paths.
const pluginMachineName = runtime.GOOS + "_" + runtime.GOARCH

type ProviderWrapper struct {
	Provider     *tfplugin.GRPCProvider
	client       *plugin.Client
	rpcClient    plugin.ClientProtocol
	providerName string
	config       cty.Value
}

func NewProviderWrapper(providerName string, providerConfig cty.Value, verbose bool) (*ProviderWrapper, error) {
	p := &ProviderWrapper{}
	p.providerName = providerName
	p.config = providerConfig
	err := p.initProvider(verbose)
	return p, err
}

func (p *ProviderWrapper) Kill() {
	p.client.Kill()
}

func (p *ProviderWrapper) GetReadOnlyAttributes(resourceTypes []string) (map[string][]string, error) {
	r := p.Provider.GetSchema()

	if r.Diagnostics.HasErrors() {
		return nil, r.Diagnostics.Err()
	}
	readOnlyAttributes := map[string][]string{}
	for resourceName, obj := range r.ResourceTypes {
		if contains(resourceTypes, resourceName) {
			readOnlyAttributes[resourceName] = append(readOnlyAttributes[resourceName], "^id$")
			for k, v := range obj.Block.Attributes {
				if !v.Optional && !v.Required {
					if v.Type.IsListType() || v.Type.IsSetType() {
						readOnlyAttributes[resourceName] = append(readOnlyAttributes[resourceName], "^"+k+".(.*)")
					} else {
						readOnlyAttributes[resourceName] = append(readOnlyAttributes[resourceName], "^"+k+"$")
					}

				}
			}
			readOnlyAttributes[resourceName] = p.readObjBlocks(obj.Block.BlockTypes, readOnlyAttributes[resourceName], "-1")
		}
	}
	return readOnlyAttributes, nil
}

func contains(s []string, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

func (p *ProviderWrapper) readObjBlocks(block map[string]*configschema.NestedBlock, readOnlyAttributes []string, parent string) []string {
	for k, v := range block {
		if len(v.BlockTypes) > 0 {
			readOnlyAttributes = p.readObjBlocks(v.BlockTypes, readOnlyAttributes, k)
		}
		fieldCount := 0
		for key, l := range v.Attributes {
			if !l.Optional && !l.Required {
				fieldCount++
				switch v.Nesting {
				case configschema.NestingList:
					if parent == "-1" {
						readOnlyAttributes = append(readOnlyAttributes, "^"+k+".[0-9]."+key+"($|\\.[0-9]|\\.#)")
					} else {
						readOnlyAttributes = append(readOnlyAttributes, "^"+parent+".(.*)."+key+"$")
					}
				case configschema.NestingSet:
					if parent == "-1" {
						readOnlyAttributes = append(readOnlyAttributes, "^"+k+".[0-9]."+key+"$")
					} else {
						readOnlyAttributes = append(readOnlyAttributes, "^"+parent+".(.*)."+key+"($|\\.(.*))")
					}
				case configschema.NestingMap:
					readOnlyAttributes = append(readOnlyAttributes, parent+"."+key)
				default:
					readOnlyAttributes = append(readOnlyAttributes, parent+"."+key+"$")
				}
			}
		}
		if fieldCount == len(v.Block.Attributes) && fieldCount > 0 {
			readOnlyAttributes = append(readOnlyAttributes, "^"+k)
		}
	}
	return readOnlyAttributes
}

func (p *ProviderWrapper) Refresh(info *terraform.InstanceInfo, state *terraform.InstanceState) (*terraform.InstanceState, error) {
	schema := p.Provider.GetSchema()
	impliedType := schema.ResourceTypes[info.Type].Block.ImpliedType()
	priorState, err := state.AttrsAsObjectValue(impliedType)
	if err != nil {
		return nil, err
	}
	resp := p.Provider.ReadResource(providers.ReadResourceRequest{
		TypeName:   info.Type,
		PriorState: priorState,
		Private:    []byte{},
	})

	if resp.Diagnostics.HasErrors() {
		// retry with different serialization mechanism
		priorState, err = gocty.ToCtyValue(state, impliedType)
		resp = p.Provider.ReadResource(providers.ReadResourceRequest{
			TypeName:   info.Type,
			PriorState: priorState,
			Private:    []byte{},
		})
		if resp.Diagnostics.HasErrors() {
			return nil, resp.Diagnostics.Err()
		}
	}

	if resp.NewState.IsNull() {
		msg := fmt.Sprintf("ERROR: Read resource response is null for resource %s", info.Id)
		return nil, errors.New(msg)
	}

	return terraform.NewInstanceStateShimmedFromValue(resp.NewState, int(schema.Provider.Version)), nil
}

func (p *ProviderWrapper) initProvider(verbose bool) error {
	providerFilePath, err := getProviderFileName(p.providerName)
	if err != nil {
		return err
	}
	options := hclog.LoggerOptions{
		Name:   "plugin",
		Level:  hclog.Error,
		Output: os.Stdout,
	}
	if verbose {
		options.Level = hclog.Trace
	}
	logger := hclog.New(&options)
	p.client = plugin.NewClient(
		&plugin.ClientConfig{
			Cmd:              exec.Command(providerFilePath),
			HandshakeConfig:  tfplugin.Handshake,
			VersionedPlugins: tfplugin.VersionedPlugins,
			Managed:          true,
			Logger:           logger,
			AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
			AutoMTLS:         true,
		})
	p.rpcClient, err = p.client.Client()
	if err != nil {
		return err
	}
	raw, err := p.rpcClient.Dispense(tfplugin.ProviderPluginName)
	if err != nil {
		return err
	}

	p.Provider = raw.(*tfplugin.GRPCProvider)

	config, err := p.Provider.GetSchema().Provider.Block.CoerceValue(p.config)
	if err != nil {
		return err
	}
	p.Provider.Configure(providers.ConfigureRequest{
		TerraformVersion: version.Version,
		Config:           config,
	})

	return nil
}

func getProviderFileName(providerName string) (string, error) {
	defaultDataDir := os.Getenv("TF_DATA_DIR")
	if defaultDataDir == "" {
		defaultDataDir = DefaultDataDir
	}
	pluginPath := defaultDataDir + string(os.PathSeparator) + "plugins" + string(os.PathSeparator) + runtime.GOOS + "_" + runtime.GOARCH
	files, err := ioutil.ReadDir(pluginPath)
	if err != nil {
		pluginPath = os.Getenv("HOME") + string(os.PathSeparator) + "." + DefaultPluginVendorDir
		files, err = ioutil.ReadDir(pluginPath)
		if err != nil {
			return "", err
		}
	}
	providerFilePath := ""
	for _, file := range files {
		if file.IsDir() {
			continue
		}
		if strings.HasPrefix(file.Name(), "terraform-provider-"+providerName) {
			providerFilePath = pluginPath + string(os.PathSeparator) + file.Name()
		}
	}
	return providerFilePath, nil
}

func GetProviderVersion(providerName string) string {
	providerFilePath, err := getProviderFileName(providerName)
	if err != nil {
		log.Println("Can't find provider file path. Ensure that you are following https://www.terraform.io/docs/configuration/providers.html#third-party-plugins.")
		return ""
	}
	t := strings.Split(providerFilePath, string(os.PathSeparator))
	providerFileName := t[len(t)-1]
	providerFileNameParts := strings.Split(providerFileName, "_")
	if len(providerFileNameParts) < 2 {
		log.Println("Can't find provider version. Ensure that you are following https://www.terraform.io/docs/configuration/providers.html#plugin-names-and-versions.")
		return ""
	}
	providerVersion := providerFileNameParts[1]
	return "~>" + providerVersion
}
<commit_msg>fix schema_version from provider<commit_after>// Copyright 2018 The Terraformer 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 provider_wrapper

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"runtime"
	"strings"

	"github.com/zclconf/go-cty/cty/gocty"

	"github.com/zclconf/go-cty/cty"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/go-plugin"
	"github.com/hashicorp/terraform/configs/configschema"
	tfplugin "github.com/hashicorp/terraform/plugin"
	"github.com/hashicorp/terraform/providers"
	"github.com/hashicorp/terraform/terraform"
	"github.com/hashicorp/terraform/version"
)

// DefaultDataDir is the default directory for storing local data.
const DefaultDataDir = ".terraform"

// DefaultPluginVendorDir is the location in the config directory to look for
// user-added plugin binaries. Terraform only reads from this path if it
// exists, it is never created by terraform.
const DefaultPluginVendorDir = "terraform.d/plugins/" + pluginMachineName

// pluginMachineName is the directory name used in new plugin paths.
const pluginMachineName = runtime.GOOS + "_" + runtime.GOARCH

type ProviderWrapper struct {
	Provider     *tfplugin.GRPCProvider
	client       *plugin.Client
	rpcClient    plugin.ClientProtocol
	providerName string
	config       cty.Value
}

func NewProviderWrapper(providerName string, providerConfig cty.Value, verbose bool) (*ProviderWrapper, error) {
	p := &ProviderWrapper{}
	p.providerName = providerName
	p.config = providerConfig
	err := p.initProvider(verbose)
	return p, err
}

func (p *ProviderWrapper) Kill() {
	p.client.Kill()
}

func (p *ProviderWrapper) GetReadOnlyAttributes(resourceTypes []string) (map[string][]string, error) {
	r := p.Provider.GetSchema()

	if r.Diagnostics.HasErrors() {
		return nil, r.Diagnostics.Err()
	}
	readOnlyAttributes := map[string][]string{}
	for resourceName, obj := range r.ResourceTypes {
		if contains(resourceTypes, resourceName) {
			readOnlyAttributes[resourceName] = append(readOnlyAttributes[resourceName], "^id$")
			for k, v := range obj.Block.Attributes {
				if !v.Optional && !v.Required {
					if v.Type.IsListType() || v.Type.IsSetType() {
						readOnlyAttributes[resourceName] = append(readOnlyAttributes[resourceName], "^"+k+".(.*)")
					} else {
						readOnlyAttributes[resourceName] = append(readOnlyAttributes[resourceName], "^"+k+"$")
					}

				}
			}
			readOnlyAttributes[resourceName] = p.readObjBlocks(obj.Block.BlockTypes, readOnlyAttributes[resourceName], "-1")
		}
	}
	return readOnlyAttributes, nil
}

func contains(s []string, e string) bool {
	for _, a := range s {
		if a == e {
			return true
		}
	}
	return false
}

func (p *ProviderWrapper) readObjBlocks(block map[string]*configschema.NestedBlock, readOnlyAttributes []string, parent string) []string {
	for k, v := range block {
		if len(v.BlockTypes) > 0 {
			readOnlyAttributes = p.readObjBlocks(v.BlockTypes, readOnlyAttributes, k)
		}
		fieldCount := 0
		for key, l := range v.Attributes {
			if !l.Optional && !l.Required {
				fieldCount++
				switch v.Nesting {
				case configschema.NestingList:
					if parent == "-1" {
						readOnlyAttributes = append(readOnlyAttributes, "^"+k+".[0-9]."+key+"($|\\.[0-9]|\\.#)")
					} else {
						readOnlyAttributes = append(readOnlyAttributes, "^"+parent+".(.*)."+key+"$")
					}
				case configschema.NestingSet:
					if parent == "-1" {
						readOnlyAttributes = append(readOnlyAttributes, "^"+k+".[0-9]."+key+"$")
					} else {
						readOnlyAttributes = append(readOnlyAttributes, "^"+parent+".(.*)."+key+"($|\\.(.*))")
					}
				case configschema.NestingMap:
					readOnlyAttributes = append(readOnlyAttributes, parent+"."+key)
				default:
					readOnlyAttributes = append(readOnlyAttributes, parent+"."+key+"$")
				}
			}
		}
		if fieldCount == len(v.Block.Attributes) && fieldCount > 0 {
			readOnlyAttributes = append(readOnlyAttributes, "^"+k)
		}
	}
	return readOnlyAttributes
}

func (p *ProviderWrapper) Refresh(info *terraform.InstanceInfo, state *terraform.InstanceState) (*terraform.InstanceState, error) {
	schema := p.Provider.GetSchema()
	impliedType := schema.ResourceTypes[info.Type].Block.ImpliedType()
	priorState, err := state.AttrsAsObjectValue(impliedType)
	if err != nil {
		return nil, err
	}
	resp := p.Provider.ReadResource(providers.ReadResourceRequest{
		TypeName:   info.Type,
		PriorState: priorState,
		Private:    []byte{},
	})

	if resp.Diagnostics.HasErrors() {
		// retry with different serialization mechanism
		priorState, err = gocty.ToCtyValue(state, impliedType)
		resp = p.Provider.ReadResource(providers.ReadResourceRequest{
			TypeName:   info.Type,
			PriorState: priorState,
			Private:    []byte{},
		})
		if resp.Diagnostics.HasErrors() {
			return nil, resp.Diagnostics.Err()
		}
	}

	if resp.NewState.IsNull() {
		msg := fmt.Sprintf("ERROR: Read resource response is null for resource %s", info.Id)
		return nil, errors.New(msg)
	}

	return terraform.NewInstanceStateShimmedFromValue(resp.NewState, int(schema.ResourceTypes[info.Type].Version)), nil
}

func (p *ProviderWrapper) initProvider(verbose bool) error {
	providerFilePath, err := getProviderFileName(p.providerName)
	if err != nil {
		return err
	}
	options := hclog.LoggerOptions{
		Name:   "plugin",
		Level:  hclog.Error,
		Output: os.Stdout,
	}
	if verbose {
		options.Level = hclog.Trace
	}
	logger := hclog.New(&options)
	p.client = plugin.NewClient(
		&plugin.ClientConfig{
			Cmd:              exec.Command(providerFilePath),
			HandshakeConfig:  tfplugin.Handshake,
			VersionedPlugins: tfplugin.VersionedPlugins,
			Managed:          true,
			Logger:           logger,
			AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
			AutoMTLS:         true,
		})
	p.rpcClient, err = p.client.Client()
	if err != nil {
		return err
	}
	raw, err := p.rpcClient.Dispense(tfplugin.ProviderPluginName)
	if err != nil {
		return err
	}

	p.Provider = raw.(*tfplugin.GRPCProvider)

	config, err := p.Provider.GetSchema().Provider.Block.CoerceValue(p.config)
	if err != nil {
		return err
	}
	p.Provider.Configure(providers.ConfigureRequest{
		TerraformVersion: version.Version,
		Config:           config,
	})

	return nil
}

func getProviderFileName(providerName string) (string, error) {
	defaultDataDir := os.Getenv("TF_DATA_DIR")
	if defaultDataDir == "" {
		defaultDataDir = DefaultDataDir
	}
	pluginPath := defaultDataDir + string(os.PathSeparator) + "plugins" + string(os.PathSeparator) + runtime.GOOS + "_" + runtime.GOARCH
	files, err := ioutil.ReadDir(pluginPath)
	if err != nil {
		pluginPath = os.Getenv("HOME") + string(os.PathSeparator) + "." + DefaultPluginVendorDir
		files, err = ioutil.ReadDir(pluginPath)
		if err != nil {
			return "", err
		}
	}
	providerFilePath := ""
	for _, file := range files {
		if file.IsDir() {
			continue
		}
		if strings.HasPrefix(file.Name(), "terraform-provider-"+providerName) {
			providerFilePath = pluginPath + string(os.PathSeparator) + file.Name()
		}
	}
	return providerFilePath, nil
}

func GetProviderVersion(providerName string) string {
	providerFilePath, err := getProviderFileName(providerName)
	if err != nil {
		log.Println("Can't find provider file path. Ensure that you are following https://www.terraform.io/docs/configuration/providers.html#third-party-plugins.")
		return ""
	}
	t := strings.Split(providerFilePath, string(os.PathSeparator))
	providerFileName := t[len(t)-1]
	providerFileNameParts := strings.Split(providerFileName, "_")
	if len(providerFileNameParts) < 2 {
		log.Println("Can't find provider version. Ensure that you are following https://www.terraform.io/docs/configuration/providers.html#plugin-names-and-versions.")
		return ""
	}
	providerVersion := providerFileNameParts[1]
	return "~>" + providerVersion
}
<|endoftext|>


<commit_before>package cfg

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/felixangell/strife"
	"github.com/pelletier/go-toml"
)

// TODO:
// - make the $HOME/.phi-editor folder if it doesn't exist
// - make the $HOME/.phi-editor/config.toml file if it doesn't exist
// - write a default toml file
//

const (
	ConfigDirPath  = "/.phi-editor/"
	ConfigTomlFile = "config.toml"
)

var FontFolder = ""

// this is the absolute path to the
// config.toml file. todo rename/refactor
var ConfigFullPath = ""

// the absolute path to the config directory
// rename/refactor due here too!
var configDirAbsPath = ""

var IconDirPath = ""

// TODO we only had double key combos
// e.g. cmd+s. we want to handle things
// like cmd+alt+s
type shortcutRegister struct {
	Supers   map[string]string
	Controls map[string]string
}

var Shortcuts = &shortcutRegister{
	Supers:   map[string]string{},
	Controls: map[string]string{},
}

func loadSyntaxDef(lang string) *LanguageSyntaxConfig {
	languagePath := filepath.Join(configDirAbsPath, "syntax", lang+".toml")
	log.Println("Loading lang from ", languagePath)

	syntaxTomlData, err := ioutil.ReadFile(languagePath)
	if err != nil {
		log.Println("Failed to load highlighting for language '"+lang+"' from path: ", languagePath)
		return nil
	}

	conf := &LanguageSyntaxConfig{}
	if err := toml.Unmarshal(syntaxTomlData, conf); err != nil {
		panic(err)
	}

	log.Println("Loaded syntax definition for language", lang)
	return conf
}

func findFontFolder() string {
	// TODO
	return "/usr/share/fonts/"
}

func configureAndValidate(conf *TomlConfig) {
	// fonts
	log.Println("Configuring fonts")
	{
		// the font path has not been set
		// so we have to figure out what it is.
		if len(conf.Editor.FontPath) == 0 {
			switch runtime.GOOS {
			case "windows":
				FontFolder = filepath.Join(os.Getenv("WINDIR"), "fonts")
			case "darwin":
				FontFolder = "/Library/Fonts/"
			case "linux":
				FontFolder = findFontFolder()
			}
			// and set it accordingly.
			conf.Editor.FontPath = FontFolder
		}

		// we only support ttf at the moment.
		fontPath := filepath.Join(conf.Editor.FontPath, conf.Editor.FontFace) + ".ttf"
		if _, err := os.Stat(fontPath); os.IsNotExist(err) {
			log.Fatal("No such font '" + fontPath + "'")
		}

		// load the font!
		font, err := strife.LoadFont(fontPath, conf.Editor.FontSize)
		if err != nil {
			panic(err)
		}
		conf.Editor.LoadedFont = font

	}

	// config & validate the keyboard shortcuts
	log.Println("Configuring keyboard shortcuts")
	{
		// keyboard commands
		for commandName, cmd := range conf.Commands {
			shortcut := cmd.Shortcut
			vals := strings.Split(shortcut, "+")

			// TODO handle conflicts

			switch vals[0] {
			case "super":
				Shortcuts.Supers[vals[1]] = commandName
			case "ctrl":
				Shortcuts.Controls[vals[1]] = commandName
			}
		}
	}

	log.Println("Syntax Highlighting")
	{
		var syntaxSet []*LanguageSyntaxConfig
		conf.associations = map[string]*LanguageSyntaxConfig{}

		for lang, extSet := range conf.Associations {
			log.Println(lang, "=>", extSet.Extensions)
			languageConfig := loadSyntaxDef(lang)
			// check for errors here

			syntaxSet = append(syntaxSet, languageConfig)

			for _, ext := range extSet.Extensions {
				log.Println("registering", ext, "as", lang)
				conf.associations[ext] = languageConfig
			}
		}

		// go through each language
		// and store the matches keywords
		// as a hashmap for faster lookup
		// in addition to this we compile any
		// regular expressions if necessary.
		for _, language := range syntaxSet {
			for _, syn := range language.Syntax {
				syn.MatchList = map[string]bool{}

				if syn.Pattern != "" {
					regex, err := regexp.Compile(syn.Pattern)
					if err != nil {
						log.Println(err.Error())
						continue
					}
					syn.CompiledPattern = regex
				} else {
					for _, item := range syn.Match {
						if _, ok := syn.MatchList[item]; ok {
							log.Println("Warning duplicate match item '" + item + "'")
							continue
						}

						syn.MatchList[item] = true
					}
				}

			}
		}
	}
}

func Setup() TomlConfig {
	log.Println("Setting up Phi Editor")

	home := os.Getenv("HOME")
	if runtime.GOOS == "windows" {
		home = os.Getenv("USERPROFILE")
	}

	ConfigDir := filepath.Join(home, ConfigDirPath)
	configDirAbsPath = ConfigDir

	ConfigPath := filepath.Join(ConfigDir, ConfigTomlFile)

	// this folder is where we store all of the language syntax
	SyntaxConfigDir := filepath.Join(ConfigDir, "syntax")

	ConfigFullPath = ConfigPath

	// if the user doesn't have a /.phi-editor
	// directory we create it for them.
	if _, err := os.Stat(ConfigDir); os.IsNotExist(err) {
		if err := os.Mkdir(ConfigDir, 0775); err != nil {
			panic(err)
		}
	}

	// ----
	// downloads the icon from github
	// and puts it into the phi-editor config folder.
	IconDirPath = filepath.Join(ConfigDir, "icons")
	if _, err := os.Stat(IconDirPath); os.IsNotExist(err) {
		if err := os.Mkdir(IconDirPath, 0775); err != nil {
			panic(err)
		}

		log.Println("setting up the icons folder")

		// https://raw.githubusercontent.com/felixangell/phi/gh-pages/images/icon128.png
		downloadIcon := func(iconSize int) {
			log.Println("downloading the phi icon ", iconSize, "x", iconSize, " png image.")

			file, err := os.Create(filepath.Join(IconDirPath, fmt.Sprintf("icon%d.png", iconSize)))
			if err != nil {
				log.Println(err.Error())
				return
			}
			defer func() {
				if err := file.Close(); err != nil {
					panic(err)
				}
			}()

			// Generated by curl-to-Go: https://mholt.github.io/curl-to-go
			resp, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/felixangell/phi/gh-pages/images/icon%d.png", iconSize))
			if err != nil {
				log.Println("Failed to download icon", iconSize, "!", err.Error())
				return
			}
			defer func() {
				if err := resp.Body.Close(); err != nil {
					panic(err)
				}
			}()

			_, err = io.Copy(file, resp.Body)
			if err != nil {
				log.Println(err.Error())
			}
		}

		size := 16
		switch runtime.GOOS {
		case "windows":
			size = 64
		case "darwin":
			size = 512
		case "linux":
			size = 96
		}

		// download the icon and
		// write it to the phi-editor folder.
		downloadIcon(size)
	}

	// try make the syntax config folder.
	if _, err := os.Stat(SyntaxConfigDir); os.IsNotExist(err) {
		if err := os.Mkdir(SyntaxConfigDir, 0775); err != nil {
			panic(err)
		}

		// load all of the default language syntax
		for name, syntaxDef := range DefaultSyntaxSet {
			languagePath := filepath.Join(SyntaxConfigDir, name+".toml")
			if _, err := os.Stat(languagePath); os.IsNotExist(err) {
				file, err := os.Create(languagePath)
				if err != nil {
					panic(err)
				}
				defer func() {
					if err := file.Close(); err != nil {
						panic(err)
					}
				}()

				if _, err := file.Write([]byte(syntaxDef)); err != nil {
					panic(err)
				}
				log.Println("Wrote syntax for language '" + name + "'")
			}
		}
	}

	// make sure a config.toml file exists in the
	// phi-editor directory.
	if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
		configFile, fileCreateErr := os.Create(ConfigPath)
		if fileCreateErr != nil {
			panic(fileCreateErr)
		}
		defer func() {
			if err := configFile.Close(); err != nil {
				panic(err)
			}
		}()

		_, writeErr := configFile.Write([]byte(DEFUALT_TOML_CONFIG))
		if writeErr != nil {
			panic(writeErr)
		}
		if err := configFile.Sync(); err != nil {
			panic(err)
		}
	}

	if _, err := os.Open(ConfigPath); err != nil {
		panic(err)
	}

	configTomlData, err := ioutil.ReadFile(ConfigPath)
	if err != nil {
		panic(err)
	}

	conf := TomlConfig{}
	if err := toml.Unmarshal(configTomlData, &conf); err != nil {
		panic(err)
	}

	configureAndValidate(&conf)
	return conf
}
<commit_msg>cleanups again<commit_after>package cfg

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"regexp"
	"runtime"
	"strings"

	"github.com/felixangell/strife"
	"github.com/pelletier/go-toml"
)

// TODO:
// - make the $HOME/.phi-editor folder if it doesn't exist
// - make the $HOME/.phi-editor/config.toml file if it doesn't exist
// - write a default toml file
//

const (
	ConfigDirPath  = "/.phi-editor/"
	ConfigTomlFile = "config.toml"
)

var FontFolder = ""

// this is the absolute path to the
// config.toml file. todo rename/refactor
var ConfigFullPath = ""

// the absolute path to the config directory
// rename/refactor due here too!
var configDirAbsPath = ""

var IconDirPath = ""

// TODO we only had double key combos
// e.g. cmd+s. we want to handle things
// like cmd+alt+s
type shortcutRegister struct {
	Supers   map[string]string
	Controls map[string]string
}

var Shortcuts = &shortcutRegister{
	Supers:   map[string]string{},
	Controls: map[string]string{},
}

func loadSyntaxDef(lang string) *LanguageSyntaxConfig {
	languagePath := filepath.Join(configDirAbsPath, "syntax", lang+".toml")
	log.Println("Loading lang from ", languagePath)

	syntaxTomlData, err := ioutil.ReadFile(languagePath)
	if err != nil {
		log.Println("Failed to load highlighting for language '"+lang+"' from path: ", languagePath)
		return nil
	}

	conf := &LanguageSyntaxConfig{}
	if err := toml.Unmarshal(syntaxTomlData, conf); err != nil {
		panic(err)
	}

	log.Println("Loaded syntax definition for language", lang)
	return conf
}

func findFontFolder() string {
	// TODO
	return "/usr/share/fonts/"
}

func configureAndValidate(conf *TomlConfig) {
	// fonts
	log.Println("Configuring fonts")
	{
		// the font path has not been set
		// so we have to figure out what it is.
		if len(conf.Editor.FontPath) == 0 {
			switch runtime.GOOS {
			case "windows":
				FontFolder = filepath.Join(os.Getenv("WINDIR"), "fonts")
			case "darwin":
				FontFolder = "/Library/Fonts/"
			case "linux":
				FontFolder = findFontFolder()
			}
			// and set it accordingly.
			conf.Editor.FontPath = FontFolder
		}

		// we only support ttf at the moment.
		fontPath := filepath.Join(conf.Editor.FontPath, conf.Editor.FontFace) + ".ttf"
		if _, err := os.Stat(fontPath); os.IsNotExist(err) {
			log.Fatal("No such font '" + fontPath + "'")
		}

		// load the font!
		font, err := strife.LoadFont(fontPath, conf.Editor.FontSize)
		if err != nil {
			panic(err)
		}
		conf.Editor.LoadedFont = font

	}

	// config & validate the keyboard shortcuts
	log.Println("Configuring keyboard shortcuts")
	{
		// keyboard commands
		for commandName, cmd := range conf.Commands {
			shortcut := cmd.Shortcut
			vals := strings.Split(shortcut, "+")

			// TODO handle conflicts

			switch vals[0] {
			case "super":
				Shortcuts.Supers[vals[1]] = commandName
			case "ctrl":
				Shortcuts.Controls[vals[1]] = commandName
			}
		}
	}

	log.Println("Syntax Highlighting")
	{
		var syntaxSet []*LanguageSyntaxConfig
		conf.associations = map[string]*LanguageSyntaxConfig{}

		for lang, extSet := range conf.Associations {
			log.Println(lang, "=>", extSet.Extensions)
			languageConfig := loadSyntaxDef(lang)
			// check for errors here

			syntaxSet = append(syntaxSet, languageConfig)

			for _, ext := range extSet.Extensions {
				log.Println("registering", ext, "as", lang)
				conf.associations[ext] = languageConfig
			}
		}

		// go through each language
		// and store the matches keywords
		// as a hashmap for faster lookup
		// in addition to this we compile any
		// regular expressions if necessary.
		for _, language := range syntaxSet {
			for _, syn := range language.Syntax {
				syn.MatchList = map[string]bool{}

				if syn.Pattern != "" {
					regex, err := regexp.Compile(syn.Pattern)
					if err != nil {
						log.Println(err.Error())
						continue
					}
					syn.CompiledPattern = regex
				} else {
					for _, item := range syn.Match {
						if _, ok := syn.MatchList[item]; ok {
							log.Println("Warning duplicate match item '" + item + "'")
							continue
						}

						syn.MatchList[item] = true
					}
				}

			}
		}
	}
}

func Setup() TomlConfig {
	log.Println("Setting up Phi Editor")

	home := os.Getenv("HOME")
	if runtime.GOOS == "windows" {
		home = os.Getenv("USERPROFILE")
	}

	ConfigDir := filepath.Join(home, ConfigDirPath)
	configDirAbsPath = ConfigDir

	ConfigPath := filepath.Join(ConfigDir, ConfigTomlFile)

	// this folder is where we store all of the language syntax
	SyntaxConfigDir := filepath.Join(ConfigDir, "syntax")

	ConfigFullPath = ConfigPath

	// if the user doesn't have a /.phi-editor
	// directory we create it for them.
	if _, err := os.Stat(ConfigDir); os.IsNotExist(err) {
		if err := os.Mkdir(ConfigDir, 0775); err != nil {
			panic(err)
		}
	}

	// ----
	// downloads the icon from github
	// and puts it into the phi-editor config folder.
	IconDirPath = filepath.Join(ConfigDir, "icons")
	if _, err := os.Stat(IconDirPath); os.IsNotExist(err) {
		if err := os.Mkdir(IconDirPath, 0775); err != nil {
			panic(err)
		}

		log.Println("setting up the icons folder")

		// https://raw.githubusercontent.com/felixangell/phi/gh-pages/images/icon128.png
		downloadIcon := func(iconSize int) {
			log.Println("downloading the phi icon ", iconSize, "x", iconSize, " png image.")

			file, err := os.Create(filepath.Join(IconDirPath, fmt.Sprintf("icon%d.png", iconSize)))
			if err != nil {
				log.Println(err.Error())
				return
			}
			defer func() {
				if err := file.Close(); err != nil {
					panic(err)
				}
			}()

			// Generated by curl-to-Go: https://mholt.github.io/curl-to-go
			resp, err := http.Get(fmt.Sprintf("https://raw.githubusercontent.com/felixangell/phi/gh-pages/images/icon%d.png", iconSize))
			if err != nil {
				log.Println("Failed to download icon", iconSize, "!", err.Error())
				return
			}
			defer func() {
				if err := resp.Body.Close(); err != nil {
					panic(err)
				}
			}()

			_, err = io.Copy(file, resp.Body)
			if err != nil {
				log.Println(err.Error())
			}
		}

		size := getIconSize()

		// download the icon and
		// write it to the phi-editor folder.
		downloadIcon(size)
	}

	// try make the syntax config folder.
	if _, err := os.Stat(SyntaxConfigDir); os.IsNotExist(err) {
		if err := os.Mkdir(SyntaxConfigDir, 0775); err != nil {
			panic(err)
		}

		// load all of the default language syntax
		for name, syntaxDef := range DefaultSyntaxSet {
			languagePath := filepath.Join(SyntaxConfigDir, name+".toml")
			if _, err := os.Stat(languagePath); os.IsNotExist(err) {
				file, err := os.Create(languagePath)
				if err != nil {
					panic(err)
				}
				if _, err := file.Write([]byte(syntaxDef)); err != nil {
					panic(err)
				}
				log.Println("Wrote syntax for language '" + name + "'")
				if err := file.Close(); err != nil {
					panic(err)
				}
			}
		}
	}

	// make sure a config.toml file exists in the
	// phi-editor directory.
	if _, err := os.Stat(ConfigPath); os.IsNotExist(err) {
		configFile, fileCreateErr := os.Create(ConfigPath)
		if fileCreateErr != nil {
			panic(fileCreateErr)
		}
		defer func() {
			if err := configFile.Close(); err != nil {
				panic(err)
			}
		}()

		_, writeErr := configFile.Write([]byte(DEFUALT_TOML_CONFIG))
		if writeErr != nil {
			panic(writeErr)
		}
		if err := configFile.Sync(); err != nil {
			panic(err)
		}
	}

	if _, err := os.Open(ConfigPath); err != nil {
		panic(err)
	}

	configTomlData, err := ioutil.ReadFile(ConfigPath)
	if err != nil {
		panic(err)
	}

	conf := TomlConfig{}
	if err := toml.Unmarshal(configTomlData, &conf); err != nil {
		panic(err)
	}

	configureAndValidate(&conf)
	return conf
}

func getIconSize() int {
	size := 16
	switch runtime.GOOS {
	case "windows":
		size = 64
	case "darwin":
		size = 512
	case "linux":
		size = 96
	}
	return size
}
<|endoftext|>


<commit_before>package republisher

import (
	"context"
	"errors"
	"time"

	keystore "github.com/ipfs/go-ipfs/keystore"
	namesys "github.com/ipfs/go-ipfs/namesys"
	pb "github.com/ipfs/go-ipfs/namesys/pb"
	path "github.com/ipfs/go-ipfs/path"

	goprocess "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess"
	gpctx "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess/context"
	logging "gx/ipfs/QmTG23dvpBCBjqQwyDxV8CQT6jmS4PSftNr1VqHhE3MLy7/go-log"
	proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
	peer "gx/ipfs/QmcJukH2sAFjY3HdBKq35WDzWoL3UUu2gt9wdfqZTUyM74/go-libp2p-peer"
	ic "gx/ipfs/Qme1knMqwt1hKZbc1BmQFmnm9f36nyQGwXxPGVpVJ9rMK5/go-libp2p-crypto"
	ds "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore"
)

var errNoEntry = errors.New("no previous entry")

var log = logging.Logger("ipns-repub")

// DefaultRebroadcastInterval is the default interval at which we rebroadcast IPNS records
var DefaultRebroadcastInterval = time.Hour * 4

// InitialRebroadcastDelay is the delay before first broadcasting IPNS records on start
var InitialRebroadcastDelay = time.Minute * 1

// FailureRetryInterval is the interval at which we retry IPNS records broadcasts (when they fail)
var FailureRetryInterval = time.Minute * 5

// DefaultRecordLifetime is the default lifetime for IPNS records
const DefaultRecordLifetime = time.Hour * 24

type Republisher struct {
	ns   namesys.Publisher
	ds   ds.Datastore
	self ic.PrivKey
	ks   keystore.Keystore

	Interval time.Duration

	// how long records that are republished should be valid for
	RecordLifetime time.Duration
}

// NewRepublisher creates a new Republisher
func NewRepublisher(ns namesys.Publisher, ds ds.Datastore, self ic.PrivKey, ks keystore.Keystore) *Republisher {
	return &Republisher{
		ns:             ns,
		ds:             ds,
		self:           self,
		ks:             ks,
		Interval:       DefaultRebroadcastInterval,
		RecordLifetime: DefaultRecordLifetime,
	}
}

func (rp *Republisher) Run(proc goprocess.Process) {
	timer := time.NewTimer(InitialRebroadcastDelay)
	defer timer.Stop()
	if rp.Interval < InitialRebroadcastDelay {
		timer.Reset(rp.Interval)
	}

	for {
		select {
		case <-timer.C:
			timer.Reset(rp.Interval)
			err := rp.republishEntries(proc)
			if err != nil {
				log.Error("Republisher failed to republish: ", err)
				if FailureRetryInterval < rp.Interval {
					timer.Reset(FailureRetryInterval)
				}
			}
		case <-proc.Closing():
			return
		}
	}
}

func (rp *Republisher) republishEntries(p goprocess.Process) error {
	ctx, cancel := context.WithCancel(gpctx.OnClosingContext(p))
	defer cancel()

	// TODO: Use rp.ipns.ListPublished(). We can't currently *do* that
	// because:
	// 1. There's no way to get keys from the keystore by ID.
	// 2. We don't actually have access to the IPNS publisher.
	err := rp.republishEntry(ctx, rp.self)
	if err != nil {
		return err
	}

	if rp.ks != nil {
		keyNames, err := rp.ks.List()
		if err != nil {
			return err
		}
		for _, name := range keyNames {
			priv, err := rp.ks.Get(name)
			if err != nil {
				return err
			}
			err = rp.republishEntry(ctx, priv)
			if err != nil {
				return err
			}

		}
	}

	return nil
}

func (rp *Republisher) republishEntry(ctx context.Context, priv ic.PrivKey) error {
	id, err := peer.IDFromPrivateKey(priv)
	if err != nil {
		return err
	}

	log.Debugf("republishing ipns entry for %s", id)

	// Look for it locally only
	p, err := rp.getLastVal(id)
	if err != nil {
		if err == errNoEntry {
			return nil
		}
		return err
	}

	// update record with same sequence number
	eol := time.Now().Add(rp.RecordLifetime)
	return rp.ns.PublishWithEOL(ctx, priv, p, eol)
}

func (rp *Republisher) getLastVal(id peer.ID) (path.Path, error) {
	// Look for it locally only
	vali, err := rp.ds.Get(namesys.IpnsDsKey(id))
	switch err {
	case nil:
	case ds.ErrNotFound:
		return "", errNoEntry
	default:
		return "", err
	}

	val := vali.([]byte)

	e := new(pb.IpnsEntry)
	if err := proto.Unmarshal(val, e); err != nil {
		return "", err
	}
	return path.Path(e.Value), nil
}
<commit_msg>reduce log level when we can't republish<commit_after>package republisher

import (
	"context"
	"errors"
	"time"

	keystore "github.com/ipfs/go-ipfs/keystore"
	namesys "github.com/ipfs/go-ipfs/namesys"
	pb "github.com/ipfs/go-ipfs/namesys/pb"
	path "github.com/ipfs/go-ipfs/path"

	goprocess "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess"
	gpctx "gx/ipfs/QmSF8fPo3jgVBAy8fpdjjYqgG87dkJgUprRBHRd2tmfgpP/goprocess/context"
	logging "gx/ipfs/QmTG23dvpBCBjqQwyDxV8CQT6jmS4PSftNr1VqHhE3MLy7/go-log"
	proto "gx/ipfs/QmZ4Qi3GaRbjcx28Sme5eMH7RQjGkt8wHxt2a65oLaeFEV/gogo-protobuf/proto"
	peer "gx/ipfs/QmcJukH2sAFjY3HdBKq35WDzWoL3UUu2gt9wdfqZTUyM74/go-libp2p-peer"
	ic "gx/ipfs/Qme1knMqwt1hKZbc1BmQFmnm9f36nyQGwXxPGVpVJ9rMK5/go-libp2p-crypto"
	ds "gx/ipfs/QmeiCcJfDW1GJnWUArudsv5rQsihpi4oyddPhdqo3CfX6i/go-datastore"
)

var errNoEntry = errors.New("no previous entry")

var log = logging.Logger("ipns-repub")

// DefaultRebroadcastInterval is the default interval at which we rebroadcast IPNS records
var DefaultRebroadcastInterval = time.Hour * 4

// InitialRebroadcastDelay is the delay before first broadcasting IPNS records on start
var InitialRebroadcastDelay = time.Minute * 1

// FailureRetryInterval is the interval at which we retry IPNS records broadcasts (when they fail)
var FailureRetryInterval = time.Minute * 5

// DefaultRecordLifetime is the default lifetime for IPNS records
const DefaultRecordLifetime = time.Hour * 24

type Republisher struct {
	ns   namesys.Publisher
	ds   ds.Datastore
	self ic.PrivKey
	ks   keystore.Keystore

	Interval time.Duration

	// how long records that are republished should be valid for
	RecordLifetime time.Duration
}

// NewRepublisher creates a new Republisher
func NewRepublisher(ns namesys.Publisher, ds ds.Datastore, self ic.PrivKey, ks keystore.Keystore) *Republisher {
	return &Republisher{
		ns:             ns,
		ds:             ds,
		self:           self,
		ks:             ks,
		Interval:       DefaultRebroadcastInterval,
		RecordLifetime: DefaultRecordLifetime,
	}
}

func (rp *Republisher) Run(proc goprocess.Process) {
	timer := time.NewTimer(InitialRebroadcastDelay)
	defer timer.Stop()
	if rp.Interval < InitialRebroadcastDelay {
		timer.Reset(rp.Interval)
	}

	for {
		select {
		case <-timer.C:
			timer.Reset(rp.Interval)
			err := rp.republishEntries(proc)
			if err != nil {
				log.Info("republisher failed to republish: ", err)
				if FailureRetryInterval < rp.Interval {
					timer.Reset(FailureRetryInterval)
				}
			}
		case <-proc.Closing():
			return
		}
	}
}

func (rp *Republisher) republishEntries(p goprocess.Process) error {
	ctx, cancel := context.WithCancel(gpctx.OnClosingContext(p))
	defer cancel()

	// TODO: Use rp.ipns.ListPublished(). We can't currently *do* that
	// because:
	// 1. There's no way to get keys from the keystore by ID.
	// 2. We don't actually have access to the IPNS publisher.
	err := rp.republishEntry(ctx, rp.self)
	if err != nil {
		return err
	}

	if rp.ks != nil {
		keyNames, err := rp.ks.List()
		if err != nil {
			return err
		}
		for _, name := range keyNames {
			priv, err := rp.ks.Get(name)
			if err != nil {
				return err
			}
			err = rp.republishEntry(ctx, priv)
			if err != nil {
				return err
			}

		}
	}

	return nil
}

func (rp *Republisher) republishEntry(ctx context.Context, priv ic.PrivKey) error {
	id, err := peer.IDFromPrivateKey(priv)
	if err != nil {
		return err
	}

	log.Debugf("republishing ipns entry for %s", id)

	// Look for it locally only
	p, err := rp.getLastVal(id)
	if err != nil {
		if err == errNoEntry {
			return nil
		}
		return err
	}

	// update record with same sequence number
	eol := time.Now().Add(rp.RecordLifetime)
	return rp.ns.PublishWithEOL(ctx, priv, p, eol)
}

func (rp *Republisher) getLastVal(id peer.ID) (path.Path, error) {
	// Look for it locally only
	vali, err := rp.ds.Get(namesys.IpnsDsKey(id))
	switch err {
	case nil:
	case ds.ErrNotFound:
		return "", errNoEntry
	default:
		return "", err
	}

	val := vali.([]byte)

	e := new(pb.IpnsEntry)
	if err := proto.Unmarshal(val, e); err != nil {
		return "", err
	}
	return path.Path(e.Value), nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/pkg/errors"
)

func TestMetricBufferIsMetricWriter(t *testing.T) {
	var i interface{} = NewMetricBuffer(1)
	if _, ok := i.(MetricWriter); !ok {
		t.Fatalf("should be a MetricWriter")
	}
}

func TestMetricBufferIsMetricReader(t *testing.T) {
	var i interface{} = NewMetricBuffer(1)
	if _, ok := i.(MetricReader); !ok {
		t.Fatalf("should be a MetricReader")
	}
}

func TestMetricBufferIsMetricCloser(t *testing.T) {
	var i interface{} = NewMetricBuffer(1)
	if _, ok := i.(MetricCloser); !ok {
		t.Fatalf("should be a MetricCloser")
	}
}

func TestMetricBuffer(t *testing.T) {
	metrics := []Metric{
		{
			Name:  "test.metric.a",
			Kind:  Gauge,
			Value: 1,
		},
		{
			Name:  "test.metric.b",
			Kind:  Gauge,
			Value: 2.5,
		},
	}
	buf := NewMetricBuffer(8)
	t.Run("write slice of metrics to buffer", func(t *testing.T) {
		err := buf.WriteMetrics(metrics)
		if err != nil {
			t.Fatal(err)
		}
	})
	for i := 0; i < len(metrics); i++ {
		t.Run(fmt.Sprintf("Read %dth ('%s') metric from buffer", i, metrics[i].Name), func(t *testing.T) {
			m, err := buf.ReadMetric()
			if err != nil {
				t.Fatal(err)
			}
			if m.Value != metrics[i].Value {
				t.Fatalf("expected metric.Value to be %d got %d", metrics[i].Value, m.Value)
			}
		})
	}
	t.Run("Read should block until closed", func(t *testing.T) {
		go func() {
			time.Sleep(100 * time.Millisecond)
			buf.Close()
		}()
		_, err := buf.ReadMetric()
		if err != EOS {
			t.Fatalf("expected EOS got: %v", err)
		}
	})
}

func TestMetricPoller(t *testing.T) {
	var r MetricReadCloser = NewMetricPoller(100*time.Millisecond, func(w MetricWriter) error {
		return w.WriteMetrics([]Metric{
			{
				Name:  "test.poller",
				Time:  time.Now(),
				Kind:  Gauge,
				Value: 1,
			},
		})

	})
	t.Run("collect 5x intervals worth of data", func(t *testing.T) {
		wait := time.After(100 * 9 * time.Millisecond)
		metrics := []Metric{}
		reading := true
		for reading {
			select {
			case <-wait:
				reading = false
			default:
				m, err := r.ReadMetric()
				if err != nil {
					t.Fatal(err)
				}
				metrics = append(metrics, m)
			}
		}
		if len(metrics) > 11 || len(metrics) < 9 {
			t.Fatal("expected to collect roughly ~10 metrics over ~1 second got %v", len(metrics))
		}
	})
	t.Run("close should end polling", func(t *testing.T) {
		go func() {
			time.Sleep(10 * time.Millisecond)
			r.Close()
		}()
		_, err := r.ReadMetric()
		if err != EOS {
			t.Fatalf("expected EOS got: %v", err)
		}
	})
}

func TestMetricPollerError(t *testing.T) {
	bang := errors.New("BANG!")
	poller := NewMetricPoller(1000*time.Millisecond, func(w MetricWriter) error {
		return bang
	})
	defer poller.Close()
	_, err := poller.ReadMetric()
	if err != bang {
		t.Fatalf("expected poller to return error 'BANG!' got: %v", err)
	}
}

func TestCopyMetrics(t *testing.T) {
	inp := []Metric{
		{Name: "test.a"},
		{Name: "test.b"},
		{Name: "test.c"},
	}
	src := NewMetricBuffer(8)
	go func() {
		if err := src.WriteMetrics(inp); err != nil {
			t.Error(err)
		}
		src.Close()
	}()

	dst := NewMetricBuffer(8)
	go func() {
		if err := CopyMetrics(dst, src); err != nil {
			t.Error(err)
		}
	}()

	out := []Metric{}
	func() {
		for {
			m, err := dst.ReadMetric()
			if err == EOS {
				return
			} else if err != nil {
				t.Fatal(err)
			}
			out = append(out, m)
		}
	}()

	if !reflect.DeepEqual(inp, out) {
		t.Fatalf("expected src == dst, got: %v", out)
	}
}

func TestMultiMetricReader(t *testing.T) {
	buffers := []*MetricBuffer{
		NewMetricBuffer(4),
		NewMetricBuffer(4),
		NewMetricBuffer(4),
	}
	readers := make([]MetricReader, len(buffers))
	for i := 0; i < len(readers); i++ {
		readers[i] = buffers[i]
	}
	multi := NewMultiMetricReader(readers...)

	expectedError := fmt.Errorf("BANG!")
	buffers[0].events <- event{
		metric: Metric{Name: "should not be seen"},
		err:    expectedError,
	}

	for i := 0; i < len(buffers); i++ {
		buffers[i].WriteMetrics([]Metric{
			{Name: fmt.Sprintf("test.multi.buf%d", i)},
		})
	}

	metrics := map[string]bool{}
	errors := []error{}
	for i := 0; i < len(buffers)+1; i++ {
		m, err := multi.ReadMetric()
		if err != nil {
			errors = append(errors, err)
		} else {
			metrics[m.Name] = true
		}
	}

	t.Run("reading", func(t *testing.T) {
		if !reflect.DeepEqual(errors, []error{expectedError}) {
			t.Fatalf("expected error(s) %q got: %q", expectedError, errors)
		}

		if len(metrics) != len(buffers) {
			t.Fatalf("expected to read %d metrics out from the MultiReader got: %v", len(buffers), len(metrics))
		}

		for i := 0; i < len(buffers); i++ {
			name := fmt.Sprintf("test.multi.buf%d", i)
			t.Run(name, func(t *testing.T) {
				if !metrics[name] {
					t.Fatalf("%s metric not read from MultiReader", name)
				}
			})
		}
	})

	t.Run("closing", func(t *testing.T) {
		multi.Close()
		if _, err := multi.ReadMetric(); err != EOS {
			t.Fatal("expected MultiReader to be closed")
		}
		for i := 0; i < len(buffers); i++ {
			if _, err := buffers[i].ReadMetric(); err != EOS {
				t.Fatalf("expected buffer%d to be closed", i)
			}
		}
	})
}
<commit_msg>Fix Fatalf placeholder type and use Fatalf instead of Fatal when placeholder is used<commit_after>package main

import (
	"fmt"
	"reflect"
	"testing"
	"time"

	"github.com/pkg/errors"
)

func TestMetricBufferIsMetricWriter(t *testing.T) {
	var i interface{} = NewMetricBuffer(1)
	if _, ok := i.(MetricWriter); !ok {
		t.Fatalf("should be a MetricWriter")
	}
}

func TestMetricBufferIsMetricReader(t *testing.T) {
	var i interface{} = NewMetricBuffer(1)
	if _, ok := i.(MetricReader); !ok {
		t.Fatalf("should be a MetricReader")
	}
}

func TestMetricBufferIsMetricCloser(t *testing.T) {
	var i interface{} = NewMetricBuffer(1)
	if _, ok := i.(MetricCloser); !ok {
		t.Fatalf("should be a MetricCloser")
	}
}

func TestMetricBuffer(t *testing.T) {
	metrics := []Metric{
		{
			Name:  "test.metric.a",
			Kind:  Gauge,
			Value: 1,
		},
		{
			Name:  "test.metric.b",
			Kind:  Gauge,
			Value: 2.5,
		},
	}
	buf := NewMetricBuffer(8)
	t.Run("write slice of metrics to buffer", func(t *testing.T) {
		err := buf.WriteMetrics(metrics)
		if err != nil {
			t.Fatal(err)
		}
	})
	for i := 0; i < len(metrics); i++ {
		t.Run(fmt.Sprintf("Read %dth ('%s') metric from buffer", i, metrics[i].Name), func(t *testing.T) {
			m, err := buf.ReadMetric()
			if err != nil {
				t.Fatal(err)
			}
			if m.Value != metrics[i].Value {
				t.Fatalf("expected metric.Value to be %f got %f", metrics[i].Value, m.Value)
			}
		})
	}
	t.Run("Read should block until closed", func(t *testing.T) {
		go func() {
			time.Sleep(100 * time.Millisecond)
			buf.Close()
		}()
		_, err := buf.ReadMetric()
		if err != EOS {
			t.Fatalf("expected EOS got: %v", err)
		}
	})
}

func TestMetricPoller(t *testing.T) {
	var r MetricReadCloser = NewMetricPoller(100*time.Millisecond, func(w MetricWriter) error {
		return w.WriteMetrics([]Metric{
			{
				Name:  "test.poller",
				Time:  time.Now(),
				Kind:  Gauge,
				Value: 1,
			},
		})

	})
	t.Run("collect 5x intervals worth of data", func(t *testing.T) {
		wait := time.After(100 * 9 * time.Millisecond)
		metrics := []Metric{}
		reading := true
		for reading {
			select {
			case <-wait:
				reading = false
			default:
				m, err := r.ReadMetric()
				if err != nil {
					t.Fatal(err)
				}
				metrics = append(metrics, m)
			}
		}
		if len(metrics) > 11 || len(metrics) < 9 {
			t.Fatalf("expected to collect roughly ~10 metrics over ~1 second got %d", len(metrics))
		}
	})
	t.Run("close should end polling", func(t *testing.T) {
		go func() {
			time.Sleep(10 * time.Millisecond)
			r.Close()
		}()
		_, err := r.ReadMetric()
		if err != EOS {
			t.Fatalf("expected EOS got: %v", err)
		}
	})
}

func TestMetricPollerError(t *testing.T) {
	bang := errors.New("BANG!")
	poller := NewMetricPoller(1000*time.Millisecond, func(w MetricWriter) error {
		return bang
	})
	defer poller.Close()
	_, err := poller.ReadMetric()
	if err != bang {
		t.Fatalf("expected poller to return error 'BANG!' got: %v", err)
	}
}

func TestCopyMetrics(t *testing.T) {
	inp := []Metric{
		{Name: "test.a"},
		{Name: "test.b"},
		{Name: "test.c"},
	}
	src := NewMetricBuffer(8)
	go func() {
		if err := src.WriteMetrics(inp); err != nil {
			t.Error(err)
		}
		src.Close()
	}()

	dst := NewMetricBuffer(8)
	go func() {
		if err := CopyMetrics(dst, src); err != nil {
			t.Error(err)
		}
	}()

	out := []Metric{}
	func() {
		for {
			m, err := dst.ReadMetric()
			if err == EOS {
				return
			} else if err != nil {
				t.Fatal(err)
			}
			out = append(out, m)
		}
	}()

	if !reflect.DeepEqual(inp, out) {
		t.Fatalf("expected src == dst, got: %v", out)
	}
}

func TestMultiMetricReader(t *testing.T) {
	buffers := []*MetricBuffer{
		NewMetricBuffer(4),
		NewMetricBuffer(4),
		NewMetricBuffer(4),
	}
	readers := make([]MetricReader, len(buffers))
	for i := 0; i < len(readers); i++ {
		readers[i] = buffers[i]
	}
	multi := NewMultiMetricReader(readers...)

	expectedError := fmt.Errorf("BANG!")
	buffers[0].events <- event{
		metric: Metric{Name: "should not be seen"},
		err:    expectedError,
	}

	for i := 0; i < len(buffers); i++ {
		buffers[i].WriteMetrics([]Metric{
			{Name: fmt.Sprintf("test.multi.buf%d", i)},
		})
	}

	metrics := map[string]bool{}
	errors := []error{}
	for i := 0; i < len(buffers)+1; i++ {
		m, err := multi.ReadMetric()
		if err != nil {
			errors = append(errors, err)
		} else {
			metrics[m.Name] = true
		}
	}

	t.Run("reading", func(t *testing.T) {
		if !reflect.DeepEqual(errors, []error{expectedError}) {
			t.Fatalf("expected error(s) %q got: %q", expectedError, errors)
		}

		if len(metrics) != len(buffers) {
			t.Fatalf("expected to read %d metrics out from the MultiReader got: %v", len(buffers), len(metrics))
		}

		for i := 0; i < len(buffers); i++ {
			name := fmt.Sprintf("test.multi.buf%d", i)
			t.Run(name, func(t *testing.T) {
				if !metrics[name] {
					t.Fatalf("%s metric not read from MultiReader", name)
				}
			})
		}
	})

	t.Run("closing", func(t *testing.T) {
		multi.Close()
		if _, err := multi.ReadMetric(); err != EOS {
			t.Fatal("expected MultiReader to be closed")
		}
		for i := 0; i < len(buffers); i++ {
			if _, err := buffers[i].ReadMetric(); err != EOS {
				t.Fatalf("expected buffer%d to be closed", i)
			}
		}
	})
}
<|endoftext|>


<commit_before>package network

import (
	"testing"
)

func buildNetwork() *Network {
	all_nodes := []*NNode{
		NewNNode(1, InputNeuron),
		NewNNode(2, InputNeuron),
		NewNNode(3, InputNeuron),
		NewNNode(4, HiddenNeuron),
		NewNNode(5, HiddenNeuron),
		NewNNode(6, HiddenNeuron),
		NewNNode(7, OutputNeuron),
		NewNNode(8, OutputNeuron),
	}

	// HIDDEN 4
	all_nodes[3].addIncoming(all_nodes[0], 15.0)
	all_nodes[3].addIncoming(all_nodes[1], 10.0)
	// HIDDEN 5
	all_nodes[4].addIncoming(all_nodes[1], 5.0)
	all_nodes[4].addIncoming(all_nodes[2], 1.0)
	// HIDDEN 6
	all_nodes[5].addIncoming(all_nodes[4], 17.0)
	// OUTPUT 7
	all_nodes[6].addIncoming(all_nodes[3], 7.0)
	all_nodes[6].addIncoming(all_nodes[5], 4.5)
	// OUTPUT 8
	all_nodes[7].addIncoming(all_nodes[5], 13.0)

	return NewNetwork(all_nodes[0:3], all_nodes[6:8], all_nodes, 0)
}

func buildModularNetwork() *Network {
	all_nodes := []*NNode{
		NewNNode(1, InputNeuron),
		NewNNode(2, InputNeuron),
		NewNNode(3, BiasNeuron),
		NewNNode(4, HiddenNeuron),
		NewNNode(5, HiddenNeuron),
		NewNNode(7, HiddenNeuron),
		NewNNode(8, OutputNeuron),
		NewNNode(9, OutputNeuron),
	}
	control_nodes := []*NNode{
		NewNNode(6, HiddenNeuron),
	}
	// HIDDEN 6
	control_nodes[0].ActivationType = MultiplyModuleActivation
	control_nodes[0].addIncoming(all_nodes[3], 17.0)
	control_nodes[0].addIncoming(all_nodes[4], 17.0)
	control_nodes[0].addOutgoing(all_nodes[5], 17.0)

	// HIDDEN 4
	all_nodes[3].ActivationType = LinearActivation
	all_nodes[3].addIncoming(all_nodes[0], 15.0)
	all_nodes[3].addIncoming(all_nodes[2], 10.0)
	// HIDDEN 5
	all_nodes[4].ActivationType = LinearActivation
	all_nodes[4].addIncoming(all_nodes[1], 5.0)
	all_nodes[4].addIncoming(all_nodes[2], 1.0)

	// HIDDEN 7
	all_nodes[5].ActivationType = NullActivation

	// OUTPUT 8
	all_nodes[6].addIncoming(all_nodes[5], 4.5)
	all_nodes[6].ActivationType = LinearActivation
	// OUTPUT 9
	all_nodes[7].addIncoming(all_nodes[5], 13.0)
	all_nodes[7].ActivationType = LinearActivation

	return NewModularNetwork(all_nodes[0:2], all_nodes[6:8], all_nodes, control_nodes, 0)
}

func TestModularNetwork_Activate(t *testing.T) {
	netw := buildModularNetwork()
	data := []float64{1.0, 2.0}
	netw.LoadSensors(data)

	for i := 0; i < 5; i++ {
		res, err := netw.Activate()
		if err != nil {
			t.Error(err)
			return
		}
		if !res {
			t.Error("activation failed unexpectedly")
		}
	}
	if netw.Outputs[0].Activation != 6.750000e+002 {
		t.Error("netw.Outputs[0].Activation != 6.750000e+002", netw.Outputs[0].Activation)
	}
	if netw.Outputs[1].Activation != 1.950000e+003 {
		t.Error("netw.Outputs[1].Activation != 1.950000e+003", netw.Outputs[1].Activation)
	}
}

// Tests Network MaxDepth
func TestNetwork_MaxDepth(t *testing.T) {
	netw := buildNetwork()

	depth, err := netw.MaxDepth()
	if err != nil {
		t.Error(err)
	}
	if depth != 3 {
		t.Error("MaxDepth", 3, depth)
	}
}

// Tests Network OutputIsOff
func TestNetwork_OutputIsOff(t *testing.T) {
	netw := buildNetwork()

	res := netw.OutputIsOff()
	if !res {
		t.Error("OutputIsOff", res)
	}
}

// Tests Network Activate
func TestNetwork_Activate(t *testing.T) {
	netw := buildNetwork()

	res, err := netw.Activate()
	if err != nil {
		t.Error(err)
	}
	if !res {
		t.Error("Failed to activate")
	}
	// check activation
	for _, node := range netw.AllNodes() {
		if node.IsNeuron() {
			if node.ActivationsCount == 0 {
				t.Error("ActivationsCount not set", node.ActivationsCount, node)
			}
			if node.Activation == 0 {
				t.Error("Activation not set", node.Activation, node)
			}
			// Check activation and time delayed activation
			if node.GetActiveOut() == 0 {
				t.Error("GetActiveOut not set", node.GetActiveOut(), node)
			}
		}
	}
}

// Test Network LoadSensors
func TestNetwork_LoadSensors(t *testing.T) {
	netw := buildNetwork()

	sensors := []float64{1.0, 3.4, 5.6}

	netw.LoadSensors(sensors)
	counter := 0
	for _, node := range netw.AllNodes() {
		if node.IsSensor() {
			if node.Activation != sensors[counter] {
				t.Error("Sensor value wrong", sensors[counter], node.Activation)
			}
			if node.ActivationsCount != 1 {
				t.Error("Sensor activations count wrong", 1, node.ActivationsCount)
			}
			counter++
		}
	}
}

// Test Network Flush
func TestNetwork_Flush(t *testing.T) {
	netw := buildNetwork()

	// activate and check state
	res, err := netw.Activate()
	if err != nil {
		t.Error(err)
	}
	if !res {
		t.Error("Failed to activate")
	}
	netw.Activate()

	// flush and check
	res, err = netw.Flush()
	if err != nil {
		t.Error(err)
		return
	}
	if !res {
		t.Error("Network flush failed")
		return
	}

	for _, node := range netw.AllNodes() {
		if node.ActivationsCount != 0 {
			t.Error("ActivationsCount", 0, node.ActivationsCount)
		}
		if node.Activation != 0 {
			t.Error("Activation", 0, node.Activation)
		}
		// Check activation and time delayed activation
		if node.GetActiveOut() != 0 {
			t.Error("GetActiveOut", 0, node.GetActiveOut())
		}
		if node.GetActiveOutTd() != 0 {
			t.Error("GetActiveOutTd", 0, node.GetActiveOutTd())
		}
	}
}

// Tests Network NodeCount
func TestNetwork_NodeCount(t *testing.T) {
	netw := buildNetwork()

	count := netw.NodeCount()
	if count != 8 {
		t.Error("Wrong network's node count", 8, count)
	}
}

// Tests Network LinkCount
func TestNetwork_LinkCount(t *testing.T) {
	netw := buildNetwork()

	count := netw.LinkCount()
	if count != 8 {
		t.Error("Wrong network's link count", 8, count)
	}
}

// Tests Network IsRecurrent
func TestNetwork_IsRecurrent(t *testing.T) {
	netw := buildNetwork()

	nodes := netw.AllNodes()

	count := 0
	recur := netw.IsRecurrent(nodes[0], nodes[7], &count, 32)
	if recur {
		t.Error("Network is not recurrent")
	}

	// Introduce recurrence
	nodes[4].addIncoming(nodes[7], 3.0)

	recur = netw.IsRecurrent(nodes[5], nodes[7], &count, 32)
	if !recur {
		t.Error("Network is actually recurrent now")
	}
}

// test fast network solver generation
func TestNetwork_FastNetworkSolver(t *testing.T) {
	netw := buildModularNetwork()

	solver, err := netw.FastNetworkSolver()
	if err != nil {
		t.Error(err)
		return
	}

	// check solver
	if solver.NodeCount() != netw.NodeCount() {
		t.Error("solver.NodeCount() != netw.NodeCount()", solver.NodeCount(), netw.NodeCount())
	}
	if solver.LinkCount() != netw.LinkCount() {
		t.Error("solver.LinkCount() != netw.LinkCount()", solver.LinkCount(), netw.LinkCount())
	}
}
<commit_msg>Fixed test networks definitions to include proper BIAS. Fixed forward propagation results.<commit_after>package network

import (
	"testing"
)

func buildNetwork() *Network {
	all_nodes := []*NNode{
		NewNNode(1, InputNeuron),
		NewNNode(2, InputNeuron),
		NewNNode(3, BiasNeuron),
		NewNNode(4, HiddenNeuron),
		NewNNode(5, HiddenNeuron),
		NewNNode(6, HiddenNeuron),
		NewNNode(7, OutputNeuron),
		NewNNode(8, OutputNeuron),
	}

	// HIDDEN 4
	all_nodes[3].addIncoming(all_nodes[0], 15.0)
	all_nodes[3].addIncoming(all_nodes[1], 10.0)
	// HIDDEN 5
	all_nodes[4].addIncoming(all_nodes[1], 5.0)
	all_nodes[4].addIncoming(all_nodes[2], 1.0)
	// HIDDEN 6
	all_nodes[5].addIncoming(all_nodes[4], 17.0)
	// OUTPUT 7
	all_nodes[6].addIncoming(all_nodes[3], 7.0)
	all_nodes[6].addIncoming(all_nodes[5], 4.5)
	// OUTPUT 8
	all_nodes[7].addIncoming(all_nodes[5], 13.0)

	return NewNetwork(all_nodes[0:3], all_nodes[6:8], all_nodes, 0)
}

func buildModularNetwork() *Network {
	all_nodes := []*NNode{
		NewNNode(1, InputNeuron),
		NewNNode(2, InputNeuron),
		NewNNode(3, BiasNeuron),
		NewNNode(4, HiddenNeuron),
		NewNNode(5, HiddenNeuron),
		NewNNode(7, HiddenNeuron),
		NewNNode(8, OutputNeuron),
		NewNNode(9, OutputNeuron),
	}
	control_nodes := []*NNode{
		NewNNode(6, HiddenNeuron),
	}
	// HIDDEN 6
	control_nodes[0].ActivationType = MultiplyModuleActivation
	control_nodes[0].addIncoming(all_nodes[3], 1.0)
	control_nodes[0].addIncoming(all_nodes[4], 1.0)
	control_nodes[0].addOutgoing(all_nodes[5], 1.0)

	// HIDDEN 4
	all_nodes[3].ActivationType = LinearActivation
	all_nodes[3].addIncoming(all_nodes[0], 15.0)
	all_nodes[3].addIncoming(all_nodes[2], 10.0)
	// HIDDEN 5
	all_nodes[4].ActivationType = LinearActivation
	all_nodes[4].addIncoming(all_nodes[1], 5.0)
	all_nodes[4].addIncoming(all_nodes[2], 1.0)

	// HIDDEN 7
	all_nodes[5].ActivationType = NullActivation

	// OUTPUT 8
	all_nodes[6].addIncoming(all_nodes[5], 4.5)
	all_nodes[6].ActivationType = LinearActivation
	// OUTPUT 9
	all_nodes[7].addIncoming(all_nodes[5], 13.0)
	all_nodes[7].ActivationType = LinearActivation

	return NewModularNetwork(all_nodes[0:3], all_nodes[6:8], all_nodes, control_nodes, 0)
}

func TestModularNetwork_Activate(t *testing.T) {
	netw := buildModularNetwork()
	data := []float64{1.0, 2.0, 0.5}
	netw.LoadSensors(data)

	for i := 0; i < 5; i++ {
		if res, err := netw.Activate(); err != nil {
			t.Error(err)
			return
		} else if !res {
			t.Error("failed to activate")
			return
		}
	}
	if netw.Outputs[0].Activation != 945 {
		t.Error("netw.Outputs[0].Activation != 945", netw.Outputs[0].Activation)
	}
	if netw.Outputs[1].Activation != 2730 {
		t.Error("netw.Outputs[1].Activation != 2730", netw.Outputs[1].Activation)
	}
}

// Tests Network MaxDepth
func TestNetwork_MaxDepth(t *testing.T) {
	netw := buildNetwork()

	depth, err := netw.MaxDepth()
	if err != nil {
		t.Error(err)
	}
	if depth != 3 {
		t.Error("MaxDepth", 3, depth)
	}
}

// Tests Network OutputIsOff
func TestNetwork_OutputIsOff(t *testing.T) {
	netw := buildNetwork()

	res := netw.OutputIsOff()
	if !res {
		t.Error("OutputIsOff", res)
	}
}

// Tests Network Activate
func TestNetwork_Activate(t *testing.T) {
	netw := buildNetwork()

	res, err := netw.Activate()
	if err != nil {
		t.Error(err)
	}
	if !res {
		t.Error("Failed to activate")
	}
	// check activation
	for _, node := range netw.AllNodes() {
		if node.IsNeuron() {
			if node.ActivationsCount == 0 {
				t.Error("ActivationsCount not set", node.ActivationsCount, node)
			}
			if node.Activation == 0 {
				t.Error("Activation not set", node.Activation, node)
			}
			// Check activation and time delayed activation
			if node.GetActiveOut() == 0 {
				t.Error("GetActiveOut not set", node.GetActiveOut(), node)
			}
		}
	}
}

// Test Network LoadSensors
func TestNetwork_LoadSensors(t *testing.T) {
	netw := buildNetwork()

	sensors := []float64{1.0, 3.4, 5.6}

	netw.LoadSensors(sensors)
	counter := 0
	for _, node := range netw.AllNodes() {
		if node.IsSensor() {
			if node.Activation != sensors[counter] {
				t.Error("Sensor value wrong", sensors[counter], node.Activation)
			}
			if node.ActivationsCount != 1 {
				t.Error("Sensor activations count wrong", 1, node.ActivationsCount)
			}
			counter++
		}
	}
}

// Test Network Flush
func TestNetwork_Flush(t *testing.T) {
	netw := buildNetwork()

	// activate and check state
	res, err := netw.Activate()
	if err != nil {
		t.Error(err)
	}
	if !res {
		t.Error("Failed to activate")
	}
	netw.Activate()

	// flush and check
	res, err = netw.Flush()
	if err != nil {
		t.Error(err)
		return
	}
	if !res {
		t.Error("Network flush failed")
		return
	}

	for _, node := range netw.AllNodes() {
		if node.ActivationsCount != 0 {
			t.Error("ActivationsCount", 0, node.ActivationsCount)
		}
		if node.Activation != 0 {
			t.Error("Activation", 0, node.Activation)
		}
		// Check activation and time delayed activation
		if node.GetActiveOut() != 0 {
			t.Error("GetActiveOut", 0, node.GetActiveOut())
		}
		if node.GetActiveOutTd() != 0 {
			t.Error("GetActiveOutTd", 0, node.GetActiveOutTd())
		}
	}
}

// Tests Network NodeCount
func TestNetwork_NodeCount(t *testing.T) {
	netw := buildNetwork()

	count := netw.NodeCount()
	if count != 8 {
		t.Error("Wrong network's node count", 8, count)
	}
}

// Tests Network LinkCount
func TestNetwork_LinkCount(t *testing.T) {
	netw := buildNetwork()

	count := netw.LinkCount()
	if count != 8 {
		t.Error("Wrong network's link count", 8, count)
	}
}

// Tests Network IsRecurrent
func TestNetwork_IsRecurrent(t *testing.T) {
	netw := buildNetwork()

	nodes := netw.AllNodes()

	count := 0
	recur := netw.IsRecurrent(nodes[0], nodes[7], &count, 32)
	if recur {
		t.Error("Network is not recurrent")
	}

	// Introduce recurrence
	nodes[4].addIncoming(nodes[7], 3.0)

	recur = netw.IsRecurrent(nodes[5], nodes[7], &count, 32)
	if !recur {
		t.Error("Network is actually recurrent now")
	}
}

// test fast network solver generation
func TestNetwork_FastNetworkSolver(t *testing.T) {
	netw := buildModularNetwork()

	solver, err := netw.FastNetworkSolver()
	if err != nil {
		t.Error(err)
		return
	}

	// check solver
	if solver.NodeCount() != netw.NodeCount() {
		t.Error("solver.NodeCount() != netw.NodeCount()", solver.NodeCount(), netw.NodeCount())
	}
	if solver.LinkCount() != netw.LinkCount() {
		t.Error("solver.LinkCount() != netw.LinkCount()", solver.LinkCount(), netw.LinkCount())
	}
}
<|endoftext|>


<commit_before>package webserver

// Copyright (c) 2019, Mitchell Cooper

import (
	"encoding/json"
	"errors"
	"html"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/cooper/quiki/wikifier"
)

var templateDirs string
var templates = make(map[string]wikiTemplate)

var templateFuncs = map[string]interface{}{
	"even": func(i int) bool {
		return i%2 == 0
	},
	"odd": func(i int) bool {
		return i%2 != 0
	},
}

type wikiTemplate struct {
	path       string             // template directory path
	template   *template.Template // master HTML template
	staticPath string             // static file directory path, if any
	staticRoot string             // static file directory HTTP root, if any
	manifest   struct {

		// human-readable template name
		// Name   string

		// template author's name
		// Author string

		// URL to template code on the web, such as GitHub repository
		// Code   string

		// wiki logo info
		Logo struct {

			// ideally one of these dimensions will be specified and the other
			// not. used for the logo specified by the wiki 'logo' directive.
			// usually the height is specified. if both are present, the
			// logo will be generated in those exact dimensions.
			Height int
			Width  int
		}
	}
}

// search all template directories for a template by its name
func findTemplate(name string) (wikiTemplate, error) {

	// template is already cached
	if t, ok := templates[name]; ok {
		return t, nil
	}

	for _, templateDir := range strings.Split(templateDirs, ",") {
		templatePath := templateDir + "/" + name
		t, err := loadTemplate(name, templatePath)

		// an error occurred in loading the template
		if err != nil {
			return t, err
		}

		// no template but no error means try the next directory
		if t.template == nil {
			continue
		}

		return t, nil
	}

	// never found a template
	return wikiTemplate{}, errors.New("unable to find template " + name)
}

// load a template from its known path
func loadTemplate(name, templatePath string) (wikiTemplate, error) {
	var t wikiTemplate
	var tryNextDirectory bool

	// template is already cached
	if t, ok := templates[name]; ok {
		return t, nil
	}

	// parse HTML templates
	tmpl := template.New("")
	err := filepath.Walk(templatePath, func(filePath string, info os.FileInfo, err error) error {

		// walk error, probably missing template
		if err != nil {
			tryNextDirectory = true
			return err
		}

		// found template file
		if strings.HasSuffix(filePath, ".tpl") {

			// error in parsing
			subTmpl, err := tmpl.ParseFiles(filePath)
			if err != nil {
				return err
			}

			// add funcs
			subTmpl.Funcs(templateFuncs)
		}

		// found static content directory
		if info.IsDir() && info.Name() == "static" {
			t.staticPath = filePath
			t.staticRoot = "/tmpl/" + name
			fileServer := http.FileServer(http.Dir(filePath))
			pfx := t.staticRoot + "/"
			mux.Handle(pfx, http.StripPrefix(pfx, fileServer))
			log.Printf("[%s] template registered: %s", name, pfx)
		}

		// found manifest
		if info.Name() == "manifest.json" {

			// couldn't read manifest
			contents, err := ioutil.ReadFile(filePath)
			if err != nil {
				return err
			}

			// couldn't parse manifest
			if err := json.Unmarshal(contents, &t.manifest); err != nil {
				return err
			}
		}

		return err
	})

	// not found
	if tryNextDirectory {
		return t, nil
	}

	// other error
	if err != nil {
		return t, err
	}

	// cache the template
	t.path = templatePath
	t.template = tmpl
	templates[name] = t

	return t, nil
}

type wikiPage struct {
	File        string                       // page name, with extension
	Name        string                       // page name, without extension
	WholeTitle  string                       // optional, shown in <title> as-is
	Title       string                       // page title
	WikiTitle   string                       // wiki titled
	WikiLogo    string                       // path to wiki logo image (deprecated, use Logo)
	WikiRoot    string                       // wiki HTTP root (deprecated, use Root.Wiki)
	Root        wikifier.PageOptRoot         // all roots
	StaticRoot  string                       // path to static resources
	Pages       []wikiPage                   // more pages for category posts
	Message     string                       // message for error page
	Navigation  []wikifier.PageOptNavigation // slice of nav items
	PageN       int                          // for category posts, the page number (first page = 1)
	NumPages    int                          // for category posts, the number of pages
	PageCSS     template.CSS                 // css
	HTMLContent template.HTML                // html
	retina      []int                        // retina scales for logo
}

func (p wikiPage) VisibleTitle() string {
	if p.WholeTitle != "" {
		return p.WholeTitle
	}
	if p.Title == p.WikiTitle || p.Title == "" {
		return p.WikiTitle
	}
	return p.Title + " - " + p.WikiTitle
}

func (p wikiPage) Scripts() []string {
	return []string{
		"/static/mootools.min.js",
		"/static/quiki.js",
		"https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js",
	}
}

// for category posts, the page numbers available.
// if there is only one page, this is nothing
func (p wikiPage) PageNumbers() []int {
	if p.NumPages == 1 {
		return nil
	}
	numbers := make([]int, p.NumPages)
	for i := 1; i <= p.NumPages; i++ {
		numbers[i-1] = i
	}
	return numbers
}

func (p wikiPage) Logo() template.HTML {
	if p.WikiLogo == "" {
		return template.HTML("")
	}
	h := `<img alt="` + html.EscapeString(p.WikiTitle) + `" src="` + p.WikiLogo + `"`

	// retina
	srcset := ""
	if len(p.retina) != 0 {

		// find image name and extension
		imageName, ext := p.WikiLogo, ""
		if lastDot := strings.LastIndexByte(p.WikiLogo, '.'); lastDot != -1 {
			imageName = p.WikiLogo[:lastDot]
			ext = p.WikiLogo[lastDot:]
		}

		// rewrite a.jpg to a@2x.jpg
		scales := make([]string, len(p.retina))
		for i, scale := range p.retina {
			scaleStr := strconv.Itoa(scale) + "x"
			scales[i] = imageName + "@" + scaleStr + ext + " " + scaleStr
		}

		srcset = strings.Join(scales, ", ")
		h += ` srcset="` + srcset + `"`
	}

	return template.HTML(h)
}
<commit_msg>close img<commit_after>package webserver

// Copyright (c) 2019, Mitchell Cooper

import (
	"encoding/json"
	"errors"
	"html"
	"html/template"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/cooper/quiki/wikifier"
)

var templateDirs string
var templates = make(map[string]wikiTemplate)

var templateFuncs = map[string]interface{}{
	"even": func(i int) bool {
		return i%2 == 0
	},
	"odd": func(i int) bool {
		return i%2 != 0
	},
}

type wikiTemplate struct {
	path       string             // template directory path
	template   *template.Template // master HTML template
	staticPath string             // static file directory path, if any
	staticRoot string             // static file directory HTTP root, if any
	manifest   struct {

		// human-readable template name
		// Name   string

		// template author's name
		// Author string

		// URL to template code on the web, such as GitHub repository
		// Code   string

		// wiki logo info
		Logo struct {

			// ideally one of these dimensions will be specified and the other
			// not. used for the logo specified by the wiki 'logo' directive.
			// usually the height is specified. if both are present, the
			// logo will be generated in those exact dimensions.
			Height int
			Width  int
		}
	}
}

// search all template directories for a template by its name
func findTemplate(name string) (wikiTemplate, error) {

	// template is already cached
	if t, ok := templates[name]; ok {
		return t, nil
	}

	for _, templateDir := range strings.Split(templateDirs, ",") {
		templatePath := templateDir + "/" + name
		t, err := loadTemplate(name, templatePath)

		// an error occurred in loading the template
		if err != nil {
			return t, err
		}

		// no template but no error means try the next directory
		if t.template == nil {
			continue
		}

		return t, nil
	}

	// never found a template
	return wikiTemplate{}, errors.New("unable to find template " + name)
}

// load a template from its known path
func loadTemplate(name, templatePath string) (wikiTemplate, error) {
	var t wikiTemplate
	var tryNextDirectory bool

	// template is already cached
	if t, ok := templates[name]; ok {
		return t, nil
	}

	// parse HTML templates
	tmpl := template.New("")
	err := filepath.Walk(templatePath, func(filePath string, info os.FileInfo, err error) error {

		// walk error, probably missing template
		if err != nil {
			tryNextDirectory = true
			return err
		}

		// found template file
		if strings.HasSuffix(filePath, ".tpl") {

			// error in parsing
			subTmpl, err := tmpl.ParseFiles(filePath)
			if err != nil {
				return err
			}

			// add funcs
			subTmpl.Funcs(templateFuncs)
		}

		// found static content directory
		if info.IsDir() && info.Name() == "static" {
			t.staticPath = filePath
			t.staticRoot = "/tmpl/" + name
			fileServer := http.FileServer(http.Dir(filePath))
			pfx := t.staticRoot + "/"
			mux.Handle(pfx, http.StripPrefix(pfx, fileServer))
			log.Printf("[%s] template registered: %s", name, pfx)
		}

		// found manifest
		if info.Name() == "manifest.json" {

			// couldn't read manifest
			contents, err := ioutil.ReadFile(filePath)
			if err != nil {
				return err
			}

			// couldn't parse manifest
			if err := json.Unmarshal(contents, &t.manifest); err != nil {
				return err
			}
		}

		return err
	})

	// not found
	if tryNextDirectory {
		return t, nil
	}

	// other error
	if err != nil {
		return t, err
	}

	// cache the template
	t.path = templatePath
	t.template = tmpl
	templates[name] = t

	return t, nil
}

type wikiPage struct {
	File        string                       // page name, with extension
	Name        string                       // page name, without extension
	WholeTitle  string                       // optional, shown in <title> as-is
	Title       string                       // page title
	WikiTitle   string                       // wiki titled
	WikiLogo    string                       // path to wiki logo image (deprecated, use Logo)
	WikiRoot    string                       // wiki HTTP root (deprecated, use Root.Wiki)
	Root        wikifier.PageOptRoot         // all roots
	StaticRoot  string                       // path to static resources
	Pages       []wikiPage                   // more pages for category posts
	Message     string                       // message for error page
	Navigation  []wikifier.PageOptNavigation // slice of nav items
	PageN       int                          // for category posts, the page number (first page = 1)
	NumPages    int                          // for category posts, the number of pages
	PageCSS     template.CSS                 // css
	HTMLContent template.HTML                // html
	retina      []int                        // retina scales for logo
}

func (p wikiPage) VisibleTitle() string {
	if p.WholeTitle != "" {
		return p.WholeTitle
	}
	if p.Title == p.WikiTitle || p.Title == "" {
		return p.WikiTitle
	}
	return p.Title + " - " + p.WikiTitle
}

func (p wikiPage) Scripts() []string {
	return []string{
		"/static/mootools.min.js",
		"/static/quiki.js",
		"https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js",
	}
}

// for category posts, the page numbers available.
// if there is only one page, this is nothing
func (p wikiPage) PageNumbers() []int {
	if p.NumPages == 1 {
		return nil
	}
	numbers := make([]int, p.NumPages)
	for i := 1; i <= p.NumPages; i++ {
		numbers[i-1] = i
	}
	return numbers
}

func (p wikiPage) Logo() template.HTML {
	if p.WikiLogo == "" {
		return template.HTML("")
	}
	h := `<img alt="` + html.EscapeString(p.WikiTitle) + `" src="` + p.WikiLogo + `"`

	// retina
	srcset := ""
	if len(p.retina) != 0 {

		// find image name and extension
		imageName, ext := p.WikiLogo, ""
		if lastDot := strings.LastIndexByte(p.WikiLogo, '.'); lastDot != -1 {
			imageName = p.WikiLogo[:lastDot]
			ext = p.WikiLogo[lastDot:]
		}

		// rewrite a.jpg to a@2x.jpg
		scales := make([]string, len(p.retina))
		for i, scale := range p.retina {
			scaleStr := strconv.Itoa(scale) + "x"
			scales[i] = imageName + "@" + scaleStr + ext + " " + scaleStr
		}

		srcset = strings.Join(scales, ", ")
		h += ` srcset="` + srcset + `"`
	}

	h += ` />`
	return template.HTML(h)
}
<|endoftext|>