<commit_before>package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/user"
)

type Config struct {
	WebhookUrl string `json:"webhook_url"`
}

func ReadConfig() (*Config, error) {
	homeDir := ""
	usr, err := user.Current()
	if err == nil {
		homeDir = usr.HomeDir
	}

	for _, path := range []string{"/etc/slackcat.conf", homeDir + "/.slackcat.conf", "./slackcat.conf"} {
		file, err := os.Open(path)
		if os.IsNotExist(err) {
			continue
		}
		if err != nil {
			return nil, err
		}

		json.NewDecoder(file)
		conf := Config{}
		err = json.NewDecoder(file).Decode(&conf)
		if err != nil {
			return nil, err
		}
		return &conf, nil
	}
	return &Config{}, nil
}

type SlackMsg struct {
	Username string `json:"username"`
	Text     string `json:"text"`
	Parse    string `json:"parse"`
}

func (m SlackMsg) Encode() (string, error) {
	b, err := json.Marshal(m)
	if err != nil {
		return "", err
	}
	return string(b), nil
}

func (m SlackMsg) Post(WebhookURL string) error {
	encoded, err := m.Encode()
	if err != nil {
		return err
	}

	resp, err := http.PostForm(WebhookURL, url.Values{"payload": {encoded}})
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return errors.New("Not OK")
	}
	return nil
}

func username() string {
	username := "<unknown>"
	usr, err := user.Current()
	if err == nil {
		username = usr.Username
	}

	hostname := "<unknown>"
	host, err := os.Hostname()
	if err == nil {
		hostname = host
	}
	return fmt.Sprintf("%s@%s", username, hostname)
}

func main() {

	cfg, err := ReadConfig()
	if err != nil {
		log.Fatalf("Coult not read config: %v", err)
	}

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		msg := SlackMsg{
			Parse:    "full",
			Username: username(),
			Text:     scanner.Text(),
		}

		err = msg.Post(cfg.WebhookUrl)
		if err != nil {
			log.Fatalf("Post failed: %v", err)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatalf("Error reading: %v", err)
	}
}
<commit_msg>Channel options<commit_after>package main

import (
	"bufio"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"os/user"

	"github.com/ogier/pflag"
)

type Config struct {
	WebhookUrl string `json:"webhook_url"`
	Channel    string `json:"channel"`
}

func ReadConfig() (*Config, error) {
	homeDir := ""
	usr, err := user.Current()
	if err == nil {
		homeDir = usr.HomeDir
	}

	for _, path := range []string{"/etc/slackcat.conf", homeDir + "/.slackcat.conf", "./slackcat.conf"} {
		file, err := os.Open(path)
		if os.IsNotExist(err) {
			continue
		}
		if err != nil {
			return nil, err
		}

		json.NewDecoder(file)
		conf := Config{}
		err = json.NewDecoder(file).Decode(&conf)
		if err != nil {
			return nil, err
		}
		return &conf, nil
	}
	return &Config{}, nil
}

type SlackMsg struct {
	Channel  string `json:"channel"`
	Username string `json:"username"`
	Text     string `json:"text"`
	Parse    string `json:"parse"`
}

func (m SlackMsg) Encode() (string, error) {
	b, err := json.Marshal(m)
	if err != nil {
		return "", err
	}
	return string(b), nil
}

func (m SlackMsg) Post(WebhookURL string) error {
	encoded, err := m.Encode()
	if err != nil {
		return err
	}

	resp, err := http.PostForm(WebhookURL, url.Values{"payload": {encoded}})
	if err != nil {
		return err
	}

	if resp.StatusCode != http.StatusOK {
		return errors.New("Not OK")
	}
	return nil
}

func username() string {
	username := "<unknown>"
	usr, err := user.Current()
	if err == nil {
		username = usr.Username
	}

	hostname := "<unknown>"
	host, err := os.Hostname()
	if err == nil {
		hostname = host
	}
	return fmt.Sprintf("%s@%s", username, hostname)
}

func main() {

	cfg, err := ReadConfig()
	if err != nil {
		log.Fatalf("Coult not read config: %v", err)
	}

	channel := pflag.StringP("channel", "c", "", "channel")
	pflag.Parse()
	if *channel == "" {
		channel = &cfg.Channel
	}

	scanner := bufio.NewScanner(os.Stdin)
	for scanner.Scan() {
		msg := SlackMsg{
			Channel:  *channel,
			Parse:    "full",
			Username: username(),
			Text:     scanner.Text(),
		}

		err = msg.Post(cfg.WebhookUrl)
		if err != nil {
			log.Fatalf("Post failed: %v", err)
		}
	}
	if err := scanner.Err(); err != nil {
		log.Fatalf("Error reading: %v", err)
	}
}
<|endoftext|>


<commit_before>package eval

import (
	"github.com/xiaq/elvish/parse"
	"github.com/xiaq/elvish/util"
)

// Checker performs static checking on an Elvish AST. It also annotates the AST
// with static information that is useful during evaluation.
type Checker struct {
	name, text string
	scopes     []map[string]Type
	enclosed   map[string]Type
}

func NewChecker() *Checker {
	return &Checker{}
}

func (ch *Checker) Check(name, text string, n *parse.ChunkNode, scope map[string]Type) (err error) {
	ch.name = name
	ch.text = text
	ch.scopes = []map[string]Type{scope}
	ch.enclosed = make(map[string]Type)

	defer util.Recover(&err)
	ch.checkChunk(n)
	return nil
}

func (ch *Checker) pushScope() {
	ch.scopes = append(ch.scopes, make(map[string]Type))
}

func (ch *Checker) popScope() {
	ch.scopes[len(ch.scopes)-1] = nil
	ch.scopes = ch.scopes[:len(ch.scopes)-1]
}

func (ch *Checker) pushVar(name string, t Type) {
	ch.scopes[len(ch.scopes)-1][name] = t
}

func (ch *Checker) hasVarOnThisScope(name string) bool {
	_, ok := ch.scopes[len(ch.scopes)-1][name]
	return ok
}

func (ch *Checker) errorf(n parse.Node, format string, args ...interface{}) {
	util.Panic(util.NewContextualError(ch.name, ch.text, int(n.Position()), format, args...))
}

// checkChunk checks a ChunkNode by checking all pipelines it contains.
func (ch *Checker) checkChunk(cn *parse.ChunkNode) {
	for _, pn := range cn.Nodes {
		ch.checkPipeline(pn)
	}
}

// checkClosure checks a ClosureNode by checking the chunk it contains.
// TODO(xiaq): Check that all pipelines have coherent IO ports.
func (ch *Checker) checkClosure(cn *parse.ClosureNode) *closureAnnotation {
	ch.pushScope()
	annotation := &closureAnnotation{}
	cn.Annotation = annotation

	bounds := [2]StreamType{unusedStream, unusedStream}
	for _, pn := range cn.Chunk.Nodes {
		annotation := ch.checkPipeline(pn)
		var ok bool
		bounds[0], ok = bounds[0].commonType(annotation.bounds[0])
		if !ok {
			ch.errorf(pn, "Pipeline input stream incompatible with previous ones")
		}
		bounds[1], ok = bounds[1].commonType(annotation.bounds[1])
		if !ok {
			ch.errorf(pn, "Pipeline output stream incompatible with previous ones")
		}
	}
	annotation.bounds = bounds

	annotation.enclosed = ch.enclosed
	ch.enclosed = make(map[string]Type)
	ch.popScope()
	return annotation
}

// checkPipeline checks a PipelineNode by checking all forms and checking that
// all connected ports are compatible. It also annotates the node.
func (ch *Checker) checkPipeline(pn *parse.PipelineNode) *pipelineAnnotation {
	for _, fn := range pn.Nodes {
		ch.checkForm(fn)
	}
	annotation := &pipelineAnnotation{}
	pn.Annotation = annotation
	annotation.bounds[0] = pn.Nodes[0].Annotation.(*formAnnotation).streamTypes[0]
	annotation.bounds[1] = pn.Nodes[len(pn.Nodes)-1].Annotation.(*formAnnotation).streamTypes[1]
	return annotation
}

func (ch *Checker) resolveVar(name string, n *parse.FactorNode) Type {
	if t := ch.tryResolveVar(name); t != nil {
		return t
	}
	ch.errorf(n, "undefined variable $%q", name)
	return nil
}

func (ch *Checker) tryResolveVar(name string) Type {
	// XXX(xiaq): Variables in outer scopes ("enclosed variables") are resolved
	// correctly by the checker by not by the evaluator.
	thisScope := len(ch.scopes) - 1
	for i := thisScope; i >= 0; i-- {
		if t := ch.scopes[i][name]; t != nil {
			if i < thisScope {
				ch.enclosed[name] = t
			}
			return t
		}
	}
	return nil
}

func (ch *Checker) resolveCommand(name string, fa *formAnnotation) {
	if ch.tryResolveVar("fn-"+name) != nil {
		// Defined function
		// XXX(xiaq): Assume fdStream IO for closures
		fa.commandType = commandDefinedFunction
	} else if bi, ok := builtinSpecials[name]; ok {
		// Builtin special
		fa.commandType = commandBuiltinSpecial
		fa.streamTypes = bi.streamTypes
		fa.builtinSpecial = &bi
	} else if bi, ok := builtinFuncs[name]; ok {
		// Builtin func
		fa.commandType = commandBuiltinFunction
		fa.streamTypes = bi.streamTypes
		fa.builtinFunc = &bi
	} else {
		// External command
		fa.commandType = commandExternal
		// Just use zero value (fdStream) for fa.streamTypes
	}
}

// checkForm checks a FormNode by resolving the command statically and checking
// all terms. Special forms are then processed case by case. It also annotates
// the node.
func (ch *Checker) checkForm(fn *parse.FormNode) {
	// TODO(xiaq): Allow more interesting terms to be used as commands
	msg := "command must be a string or closure"
	if len(fn.Command.Nodes) != 1 {
		ch.errorf(fn.Command, msg)
	}
	command := fn.Command.Nodes[0]
	ch.checkFactor(command)

	annotation := &formAnnotation{}
	fn.Annotation = annotation
	switch command.Typ {
	case parse.StringFactor:
		ch.resolveCommand(command.Node.(*parse.StringNode).Text, annotation)
	case parse.ClosureFactor:
		// XXX(xiaq): Assume fdStream IO for closures
	default:
		ch.errorf(fn.Command, msg)
	}

	for _, rd := range fn.Redirs {
		if rd.Fd() <= 1 {
			annotation.streamTypes[rd.Fd()] = unusedStream
		}
	}

	if annotation.commandType == commandBuiltinSpecial {
		annotation.specialAnnotation = annotation.builtinSpecial.check(ch, fn)
	} else {
		ch.checkTermList(fn.Args)
	}
}

func (ch *Checker) checkTerms(tns []*parse.TermNode) {
	for _, tn := range tns {
		ch.checkTerm(tn)
	}
}

// checkTermList checks a TermListNode by checking all terms it contains.
func (ch *Checker) checkTermList(ln *parse.TermListNode) {
	ch.checkTerms(ln.Nodes)
}

// checkTerm checks a TermNode by checking all factors it contains.
func (ch *Checker) checkTerm(tn *parse.TermNode) {
	for _, fn := range tn.Nodes {
		ch.checkFactor(fn)
	}
}

// checkFactor checks a FactorNode by analyzing different factor types case by
// case. A StringFactor is not checked at all. A VariableFactor is resolved
// statically. The other composite factor types are checked recursively.
func (ch *Checker) checkFactor(fn *parse.FactorNode) {
	switch fn.Typ {
	case parse.StringFactor:
	case parse.VariableFactor:
		ch.resolveVar(fn.Node.(*parse.StringNode).Text, fn)
	case parse.TableFactor:
		table := fn.Node.(*parse.TableNode)
		for _, tn := range table.List {
			ch.checkTerm(tn)
		}
		for _, tp := range table.Dict {
			ch.checkTerm(tp.Key)
			ch.checkTerm(tp.Value)
		}
	case parse.ClosureFactor:
		ca := ch.checkClosure(fn.Node.(*parse.ClosureNode))
		for name, typ := range ca.enclosed {
			if !ch.hasVarOnThisScope(name) {
				ch.enclosed[name] = typ
			}
		}
	case parse.ListFactor:
		ch.checkTermList(fn.Node.(*parse.TermListNode))
	case parse.OutputCaptureFactor, parse.StatusCaptureFactor:
		ch.checkPipeline(fn.Node.(*parse.PipelineNode))
	}
}
<commit_msg>eval: Utilize closure annotation to determine form annotation<commit_after>package eval

import (
	"github.com/xiaq/elvish/parse"
	"github.com/xiaq/elvish/util"
)

// Checker performs static checking on an Elvish AST. It also annotates the AST
// with static information that is useful during evaluation.
type Checker struct {
	name, text string
	scopes     []map[string]Type
	enclosed   map[string]Type
}

func NewChecker() *Checker {
	return &Checker{}
}

func (ch *Checker) Check(name, text string, n *parse.ChunkNode, scope map[string]Type) (err error) {
	ch.name = name
	ch.text = text
	ch.scopes = []map[string]Type{scope}
	ch.enclosed = make(map[string]Type)

	defer util.Recover(&err)
	ch.checkChunk(n)
	return nil
}

func (ch *Checker) pushScope() {
	ch.scopes = append(ch.scopes, make(map[string]Type))
}

func (ch *Checker) popScope() {
	ch.scopes[len(ch.scopes)-1] = nil
	ch.scopes = ch.scopes[:len(ch.scopes)-1]
}

func (ch *Checker) pushVar(name string, t Type) {
	ch.scopes[len(ch.scopes)-1][name] = t
}

func (ch *Checker) hasVarOnThisScope(name string) bool {
	_, ok := ch.scopes[len(ch.scopes)-1][name]
	return ok
}

func (ch *Checker) errorf(n parse.Node, format string, args ...interface{}) {
	util.Panic(util.NewContextualError(ch.name, ch.text, int(n.Position()), format, args...))
}

// checkChunk checks a ChunkNode by checking all pipelines it contains.
func (ch *Checker) checkChunk(cn *parse.ChunkNode) {
	for _, pn := range cn.Nodes {
		ch.checkPipeline(pn)
	}
}

// checkClosure checks a ClosureNode by checking the chunk it contains.
// TODO(xiaq): Check that all pipelines have coherent IO ports.
func (ch *Checker) checkClosure(cn *parse.ClosureNode) *closureAnnotation {
	ch.pushScope()
	annotation := &closureAnnotation{}
	cn.Annotation = annotation

	bounds := [2]StreamType{unusedStream, unusedStream}
	for _, pn := range cn.Chunk.Nodes {
		annotation := ch.checkPipeline(pn)
		var ok bool
		bounds[0], ok = bounds[0].commonType(annotation.bounds[0])
		if !ok {
			ch.errorf(pn, "Pipeline input stream incompatible with previous ones")
		}
		bounds[1], ok = bounds[1].commonType(annotation.bounds[1])
		if !ok {
			ch.errorf(pn, "Pipeline output stream incompatible with previous ones")
		}
	}
	annotation.bounds = bounds

	annotation.enclosed = ch.enclosed
	ch.enclosed = make(map[string]Type)
	ch.popScope()
	return annotation
}

// checkPipeline checks a PipelineNode by checking all forms and checking that
// all connected ports are compatible. It also annotates the node.
func (ch *Checker) checkPipeline(pn *parse.PipelineNode) *pipelineAnnotation {
	for _, fn := range pn.Nodes {
		ch.checkForm(fn)
	}
	annotation := &pipelineAnnotation{}
	pn.Annotation = annotation
	annotation.bounds[0] = pn.Nodes[0].Annotation.(*formAnnotation).streamTypes[0]
	annotation.bounds[1] = pn.Nodes[len(pn.Nodes)-1].Annotation.(*formAnnotation).streamTypes[1]
	return annotation
}

func (ch *Checker) resolveVar(name string, n *parse.FactorNode) Type {
	if t := ch.tryResolveVar(name); t != nil {
		return t
	}
	ch.errorf(n, "undefined variable $%q", name)
	return nil
}

func (ch *Checker) tryResolveVar(name string) Type {
	// XXX(xiaq): Variables in outer scopes ("enclosed variables") are resolved
	// correctly by the checker by not by the evaluator.
	thisScope := len(ch.scopes) - 1
	for i := thisScope; i >= 0; i-- {
		if t := ch.scopes[i][name]; t != nil {
			if i < thisScope {
				ch.enclosed[name] = t
			}
			return t
		}
	}
	return nil
}

func (ch *Checker) resolveCommand(name string, fa *formAnnotation) {
	if ch.tryResolveVar("fn-"+name) != nil {
		// Defined function
		// XXX(xiaq): Assume fdStream IO for closures
		fa.commandType = commandDefinedFunction
	} else if bi, ok := builtinSpecials[name]; ok {
		// Builtin special
		fa.commandType = commandBuiltinSpecial
		fa.streamTypes = bi.streamTypes
		fa.builtinSpecial = &bi
	} else if bi, ok := builtinFuncs[name]; ok {
		// Builtin func
		fa.commandType = commandBuiltinFunction
		fa.streamTypes = bi.streamTypes
		fa.builtinFunc = &bi
	} else {
		// External command
		fa.commandType = commandExternal
		// Just use zero value (fdStream) for fa.streamTypes
	}
}

// checkForm checks a FormNode by resolving the command statically and checking
// all terms. Special forms are then processed case by case. It also annotates
// the node.
func (ch *Checker) checkForm(fn *parse.FormNode) {
	// TODO(xiaq): Allow more interesting terms to be used as commands
	msg := "command must be a string or closure"
	if len(fn.Command.Nodes) != 1 {
		ch.errorf(fn.Command, msg)
	}
	command := fn.Command.Nodes[0]
	ch.checkFactor(command)

	annotation := &formAnnotation{}
	fn.Annotation = annotation
	switch command.Typ {
	case parse.StringFactor:
		ch.resolveCommand(command.Node.(*parse.StringNode).Text, annotation)
	case parse.ClosureFactor:
		ca := command.Node.(*parse.ClosureNode).Annotation.(*closureAnnotation)
		annotation.streamTypes = ca.bounds
	default:
		ch.errorf(fn.Command, msg)
	}

	for _, rd := range fn.Redirs {
		if rd.Fd() <= 1 {
			annotation.streamTypes[rd.Fd()] = unusedStream
		}
	}

	if annotation.commandType == commandBuiltinSpecial {
		annotation.specialAnnotation = annotation.builtinSpecial.check(ch, fn)
	} else {
		ch.checkTermList(fn.Args)
	}
}

func (ch *Checker) checkTerms(tns []*parse.TermNode) {
	for _, tn := range tns {
		ch.checkTerm(tn)
	}
}

// checkTermList checks a TermListNode by checking all terms it contains.
func (ch *Checker) checkTermList(ln *parse.TermListNode) {
	ch.checkTerms(ln.Nodes)
}

// checkTerm checks a TermNode by checking all factors it contains.
func (ch *Checker) checkTerm(tn *parse.TermNode) {
	for _, fn := range tn.Nodes {
		ch.checkFactor(fn)
	}
}

// checkFactor checks a FactorNode by analyzing different factor types case by
// case. A StringFactor is not checked at all. A VariableFactor is resolved
// statically. The other composite factor types are checked recursively.
func (ch *Checker) checkFactor(fn *parse.FactorNode) {
	switch fn.Typ {
	case parse.StringFactor:
	case parse.VariableFactor:
		ch.resolveVar(fn.Node.(*parse.StringNode).Text, fn)
	case parse.TableFactor:
		table := fn.Node.(*parse.TableNode)
		for _, tn := range table.List {
			ch.checkTerm(tn)
		}
		for _, tp := range table.Dict {
			ch.checkTerm(tp.Key)
			ch.checkTerm(tp.Value)
		}
	case parse.ClosureFactor:
		ca := ch.checkClosure(fn.Node.(*parse.ClosureNode))
		for name, typ := range ca.enclosed {
			if !ch.hasVarOnThisScope(name) {
				ch.enclosed[name] = typ
			}
		}
	case parse.ListFactor:
		ch.checkTermList(fn.Node.(*parse.TermListNode))
	case parse.OutputCaptureFactor, parse.StatusCaptureFactor:
		ch.checkPipeline(fn.Node.(*parse.PipelineNode))
	}
}
<|endoftext|>


<commit_before>package ghapi

type GitHubEventType string

// Refer to https://developer.github.com/webhooks/#events
const (
	// Any time a Commit is commented on.
	CommitCommentEventType GitHubEventType = "commit_comment"
	// Any time a Branch or Tag is created.
	CreateEventType GitHubEventType = "create"
	// Any time a Branch or Tag is deleted.
	DeleteEventType GitHubEventType = "delete"
	// Any time a Repository has a new deployment created from the API.
	DeploymentEventType GitHubEventType = "deployment"
	// Any time a deployment for a Repository has a status update from the API.
	DeploymentStatusEventType GitHubEventType = "deployment_status"
	// Any time a Repository is forked.
	ForkEventType GitHubEventType = "fork"
	// Any time a Wiki page is updated.
	GollumEventType GitHubEventType = "gollum"
	// Any time an Issue or Pull Request is commented on.
	IssueCommentEventType GitHubEventType = "issue_comment"
	// Any time an Issue is assigned, unassigned, labeled, unlabeled, opened,
	// closed, or reopened.
	IssuesEventType GitHubEventType = "issues"
	// Any time a User is added as a collaborator to a non-Organization
	// Repository.
	MemberEventType GitHubEventType = "member"
	// Any time a User is added or removed from a team. Organization hooks only.
	MembershipEventType GitHubEventType = "membership"
	// Any time a Pages site is built or results in a failed build.
	PageBuildEventType GitHubEventType = "page_build"
	// Any time a Repository changes from private to public.
	PublicEventType GitHubEventType = "public"
	// Any time a comment is created on a portion of the unified diff of a pull
	// request (the Files Changed tab).
	PullRequestReviewCommentEventType GitHubEventType = "pull_request_review_comment"
	// Any time a Pull Request is assigned, unassigned, labeled, unlabeled,
	// opened, closed, reopened, or synchronized (updated due to a new push in
	// the branch that the pull request is tracking).
	PullRequestEventType GitHubEventType = "pull_request"
	// Any Git push to a Repository, including editing tags or branches.
	// Commits via API actions that update references are also counted.
	PushEventType GitHubEventType = "push"
	// Any time a Repository is created. Organization hooks only.
	RepositoryEventType GitHubEventType = "repository"
	// Any time a Release is published in a Repository.
	ReleaseEventType GitHubEventType = "release"
	// Any time a Repository has a status update from the API.
	StatusEventType GitHubEventType = "status"
	// Any time a team is added or modified on a Repository.
	TeamAddEventType GitHubEventType = "team_add"
	// Any time a User watches a Repository.
	WatchEventType GitHubEventType = "watch"
	// When you create a new webhook, we’ll send you a simple ping event to let
	// you know you’ve set up the webhook correctly. This event isn’t stored so
	// it isn’t retrievable via the Events API. You can trigger a ping again by
	// calling the ping endpoint.
	PingEventType GitHubEventType = "ping"
)
<commit_msg>add more eventtypes<commit_after>package ghapi

// GitHubEventType represents the value of the "X-Github-Event" header from a GitHub event.
// See https://developer.github.com/webhooks/#events.
type GitHubEventType string

const (
	// CommitCommentEventType occurs when a Commit is commented on.
	CommitCommentEventType GitHubEventType = "commit_comment"
	// CreateEventType occurs when a Branch or Tag is created.
	CreateEventType GitHubEventType = "create"
	// DeleteEventType occurs when a Branch or Tag is deleted.
	DeleteEventType GitHubEventType = "delete"
	// DeploymentEventType occurs when a Repository has a new deployment created from the API.
	DeploymentEventType GitHubEventType = "deployment"
	// DeploymentStatusEventType occurs when a deployment for a Repository has a status update from the API.
	DeploymentStatusEventType GitHubEventType = "deployment_status"
	// ForkEventType occurs when a Repository is forked.
	ForkEventType GitHubEventType = "fork"
	// GollumEventType occurs when a Wiki page is updated.
	GollumEventType GitHubEventType = "gollum"
	// IssueCommentEventType occurs when an Issue or Pull Request is commented on.
	IssueCommentEventType GitHubEventType = "issue_comment"
	// IssuesEventType occurs when an Issue is assigned, unassigned, labeled, unlabeled, opened,
	// closed, or reopened.
	IssuesEventType GitHubEventType = "issues"
	// LabelEventType occurs when a label is created, edited, or deleted.
	LabelEventType GitHubEventType = "label"
	// MemberEventType occurs when a User is added or removed as a collaborator to a non-Organization Repository.
	MemberEventType GitHubEventType = "member"
	// MembershipEventType occurs when a User is added or removed from a team. Organization hooks only.
	MembershipEventType GitHubEventType = "membership"
	// MilestoneEventType occurs when a Milestone is created, closed, opened, edited, or deleted.
	MilestoneEventType GitHubEventType = "milestone"
	// PageBuildEventType occurs when a Pages site is built or results in a failed build.
	PageBuildEventType GitHubEventType = "page_build"
	// PublicEventType occurs when a Repository changes from private to public.
	PublicEventType GitHubEventType = "public"
	// PullRequestReviewCommentEventType occurs when a comment is created, edited, or deleted (in the Files
	// Changed tab).
	PullRequestReviewCommentEventType GitHubEventType = "pull_request_review_comment"
	// PullRequestReviewEventType occurs when a Pull Request Review is submitted.
	PullRequestReviewEventType GitHubEventType = "pull_request_review"
	// PullRequestEventType occurs when a Pull Request is assigned, unassigned, labeled, unlabeled, opened, closed,
	// reopened, or synchronized (updated due to a new push inthe branch that the pull request is tracking).
	PullRequestEventType GitHubEventType = "pull_request"
	// PushEventType occurs when any Git push to a Repository occurs, including editing tags or branches.
	// Commits via API actions that update references are also counted.
	PushEventType GitHubEventType = "push"
	// RepositoryEventType occurs when a Repository is created. Organization hooks only.
	RepositoryEventType GitHubEventType = "repository"
	// ReleaseEventType occurs when a Release is published in a Repository.
	ReleaseEventType GitHubEventType = "release"
	// StatusEventType occurs when a Repository has a status update from the API.
	StatusEventType GitHubEventType = "status"
	// TeamEventType occurs when a team is created, deleted, modified, or added to or removed from a repository.
	// Organization hooks only.
	TeamEventType GitHubEventType = "team"
	// TeamAddEventType occurs when a team is added or modified on a Repository.
	TeamAddEventType GitHubEventType = "team_add"
	// WatchEventType occurs any time a User watches a Repository.
	WatchEventType GitHubEventType = "watch"
	// PingEventType occurs when you create a new webhook. GitHub will send you a simple ping event to letyou know
	// you've set up the webhook correctly. This event isn't stored so it isn't retrievable via the Events API. You
	// can trigger a ping again by calling the ping endpoint. See https://developer.github.com/webhooks/#ping-event
	// and https://developer.github.com/v3/repos/hooks/#ping-a-hook.
	PingEventType GitHubEventType = "ping"
)
<|endoftext|>


<commit_before>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ssa

import (
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// needwb returns whether we need write barrier for store op v.
// v must be Store/Move/Zero.
func needwb(v *Value) bool {
	t, ok := v.Aux.(*types.Type)
	if !ok {
		v.Fatalf("store aux is not a type: %s", v.LongString())
	}
	if !t.HasPointer() {
		return false
	}
	if IsStackAddr(v.Args[0]) {
		return false // write on stack doesn't need write barrier
	}
	return true
}

// writebarrier pass inserts write barriers for store ops (Store, Move, Zero)
// when necessary (the condition above). It rewrites store ops to branches
// and runtime calls, like
//
// if writeBarrier.enabled {
//   writebarrierptr(ptr, val)
// } else {
//   *ptr = val
// }
//
// A sequence of WB stores for many pointer fields of a single type will
// be emitted together, with a single branch.
func writebarrier(f *Func) {
	if !f.fe.UseWriteBarrier() {
		return
	}

	var sb, sp, wbaddr, const0 *Value
	var writebarrierptr, typedmemmove, typedmemclr *obj.LSym
	var stores, after []*Value
	var sset *sparseSet
	var storeNumber []int32

	for _, b := range f.Blocks { // range loop is safe since the blocks we added contain no stores to expand
		// first, identify all the stores that need to insert a write barrier.
		// mark them with WB ops temporarily. record presence of WB ops.
		hasStore := false
		for _, v := range b.Values {
			switch v.Op {
			case OpStore, OpMove, OpZero:
				if needwb(v) {
					switch v.Op {
					case OpStore:
						v.Op = OpStoreWB
					case OpMove:
						v.Op = OpMoveWB
					case OpZero:
						v.Op = OpZeroWB
					}
					hasStore = true
				}
			}
		}
		if !hasStore {
			continue
		}

		if wbaddr == nil {
			// lazily initialize global values for write barrier test and calls
			// find SB and SP values in entry block
			initpos := f.Entry.Pos
			for _, v := range f.Entry.Values {
				if v.Op == OpSB {
					sb = v
				}
				if v.Op == OpSP {
					sp = v
				}
				if sb != nil && sp != nil {
					break
				}
			}
			if sb == nil {
				sb = f.Entry.NewValue0(initpos, OpSB, f.Config.Types.Uintptr)
			}
			if sp == nil {
				sp = f.Entry.NewValue0(initpos, OpSP, f.Config.Types.Uintptr)
			}
			wbsym := f.fe.Syslook("writeBarrier")
			wbaddr = f.Entry.NewValue1A(initpos, OpAddr, f.Config.Types.UInt32Ptr, wbsym, sb)
			writebarrierptr = f.fe.Syslook("writebarrierptr")
			typedmemmove = f.fe.Syslook("typedmemmove")
			typedmemclr = f.fe.Syslook("typedmemclr")
			const0 = f.ConstInt32(initpos, f.Config.Types.UInt32, 0)

			// allocate auxiliary data structures for computing store order
			sset = f.newSparseSet(f.NumValues())
			defer f.retSparseSet(sset)
			storeNumber = make([]int32, f.NumValues())
		}

		// order values in store order
		b.Values = storeOrder(b.Values, sset, storeNumber)

	again:
		// find the start and end of the last contiguous WB store sequence.
		// a branch will be inserted there. values after it will be moved
		// to a new block.
		var last *Value
		var start, end int
		values := b.Values
	FindSeq:
		for i := len(values) - 1; i >= 0; i-- {
			w := values[i]
			switch w.Op {
			case OpStoreWB, OpMoveWB, OpZeroWB:
				start = i
				if last == nil {
					last = w
					end = i + 1
				}
			case OpVarDef, OpVarLive, OpVarKill:
				continue
			default:
				if last == nil {
					continue
				}
				break FindSeq
			}
		}
		stores = append(stores[:0], b.Values[start:end]...) // copy to avoid aliasing
		after = append(after[:0], b.Values[end:]...)
		b.Values = b.Values[:start]

		// find the memory before the WB stores
		mem := stores[0].MemoryArg()
		pos := stores[0].Pos
		bThen := f.NewBlock(BlockPlain)
		bElse := f.NewBlock(BlockPlain)
		bEnd := f.NewBlock(b.Kind)
		bThen.Pos = pos
		bElse.Pos = pos
		bEnd.Pos = b.Pos
		b.Pos = pos

		// set up control flow for end block
		bEnd.SetControl(b.Control)
		bEnd.Likely = b.Likely
		for _, e := range b.Succs {
			bEnd.Succs = append(bEnd.Succs, e)
			e.b.Preds[e.i].b = bEnd
		}

		// set up control flow for write barrier test
		// load word, test word, avoiding partial register write from load byte.
		cfgtypes := &f.Config.Types
		flag := b.NewValue2(pos, OpLoad, cfgtypes.UInt32, wbaddr, mem)
		flag = b.NewValue2(pos, OpNeq32, cfgtypes.Bool, flag, const0)
		b.Kind = BlockIf
		b.SetControl(flag)
		b.Likely = BranchUnlikely
		b.Succs = b.Succs[:0]
		b.AddEdgeTo(bThen)
		b.AddEdgeTo(bElse)
		bThen.AddEdgeTo(bEnd)
		bElse.AddEdgeTo(bEnd)

		// for each write barrier store, append write barrier version to bThen
		// and simple store version to bElse
		memThen := mem
		memElse := mem
		for _, w := range stores {
			ptr := w.Args[0]
			pos := w.Pos

			var fn *obj.LSym
			var typ *obj.LSym
			var val *Value
			switch w.Op {
			case OpStoreWB:
				fn = writebarrierptr
				val = w.Args[1]
			case OpMoveWB:
				fn = typedmemmove
				val = w.Args[1]
				typ = w.Aux.(*types.Type).Symbol()
			case OpZeroWB:
				fn = typedmemclr
				typ = w.Aux.(*types.Type).Symbol()
			case OpVarDef, OpVarLive, OpVarKill:
			}

			// then block: emit write barrier call
			switch w.Op {
			case OpStoreWB, OpMoveWB, OpZeroWB:
				volatile := w.Op == OpMoveWB && isVolatile(val)
				memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, volatile)
			case OpVarDef, OpVarLive, OpVarKill:
				memThen = bThen.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, memThen)
			}

			// else block: normal store
			switch w.Op {
			case OpStoreWB:
				memElse = bElse.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, memElse)
			case OpMoveWB:
				memElse = bElse.NewValue3I(pos, OpMove, types.TypeMem, w.AuxInt, ptr, val, memElse)
				memElse.Aux = w.Aux
			case OpZeroWB:
				memElse = bElse.NewValue2I(pos, OpZero, types.TypeMem, w.AuxInt, ptr, memElse)
				memElse.Aux = w.Aux
			case OpVarDef, OpVarLive, OpVarKill:
				memElse = bElse.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, memElse)
			}

			if fn != nil {
				// Note that we set up a writebarrier function call.
				if !f.WBPos.IsKnown() {
					f.WBPos = pos
				}
				if f.fe.Debug_wb() {
					f.Warnl(pos, "write barrier")
				}
			}
		}

		// merge memory
		// Splice memory Phi into the last memory of the original sequence,
		// which may be used in subsequent blocks. Other memories in the
		// sequence must be dead after this block since there can be only
		// one memory live.
		bEnd.Values = append(bEnd.Values, last)
		last.Block = bEnd
		last.reset(OpPhi)
		last.Type = types.TypeMem
		last.AddArg(memThen)
		last.AddArg(memElse)
		for _, w := range stores {
			if w != last {
				w.resetArgs()
			}
		}
		for _, w := range stores {
			if w != last {
				f.freeValue(w)
			}
		}

		// put values after the store sequence into the end block
		bEnd.Values = append(bEnd.Values, after...)
		for _, w := range after {
			w.Block = bEnd
		}

		// if we have more stores in this block, do this block again
		// check from end to beginning, to avoid quadratic behavior; issue 13554
		// TODO: track the final value to avoid any looping here at all
		for i := len(b.Values) - 1; i >= 0; i-- {
			switch b.Values[i].Op {
			case OpStoreWB, OpMoveWB, OpZeroWB:
				goto again
			}
		}
	}
}

// wbcall emits write barrier runtime call in b, returns memory.
// if valIsVolatile, it moves val into temp space before making the call.
func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value {
	config := b.Func.Config

	var tmp GCNode
	if valIsVolatile {
		// Copy to temp location if the source is volatile (will be clobbered by
		// a function call). Marshaling the args to typedmemmove might clobber the
		// value we're trying to move.
		t := val.Type.ElemType()
		tmp = b.Func.fe.Auto(val.Pos, t)
		mem = b.NewValue1A(pos, OpVarDef, types.TypeMem, tmp, mem)
		tmpaddr := b.NewValue1A(pos, OpAddr, t.PtrTo(), tmp, sp)
		siz := t.Size()
		mem = b.NewValue3I(pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem)
		mem.Aux = t
		val = tmpaddr
	}

	// put arguments on stack
	off := config.ctxt.FixedFrameSize()

	if typ != nil { // for typedmemmove
		taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
		off = round(off, taddr.Type.Alignment())
		arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp)
		mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, taddr, mem)
		off += taddr.Type.Size()
	}

	off = round(off, ptr.Type.Alignment())
	arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp)
	mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, ptr, mem)
	off += ptr.Type.Size()

	if val != nil {
		off = round(off, val.Type.Alignment())
		arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp)
		mem = b.NewValue3A(pos, OpStore, types.TypeMem, val.Type, arg, val, mem)
		off += val.Type.Size()
	}
	off = round(off, config.PtrSize)

	// issue call
	mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, fn, mem)
	mem.AuxInt = off - config.ctxt.FixedFrameSize()

	if valIsVolatile {
		mem = b.NewValue1A(pos, OpVarKill, types.TypeMem, tmp, mem) // mark temp dead
	}

	return mem
}

// round to a multiple of r, r is a power of 2
func round(o int64, r int64) int64 {
	return (o + r - 1) &^ (r - 1)
}

// IsStackAddr returns whether v is known to be an address of a stack slot
func IsStackAddr(v *Value) bool {
	for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
		v = v.Args[0]
	}
	switch v.Op {
	case OpSP:
		return true
	case OpAddr:
		return v.Args[0].Op == OpSP
	}
	return false
}

// isVolatile returns whether v is a pointer to argument region on stack which
// will be clobbered by a function call.
func isVolatile(v *Value) bool {
	for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
		v = v.Args[0]
	}
	return v.Op == OpSP
}
<commit_msg>cmd/compile: use a counter to track whether writebarrier rewriting is done<commit_after>// Copyright 2016 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ssa

import (
	"cmd/compile/internal/types"
	"cmd/internal/obj"
	"cmd/internal/src"
)

// needwb returns whether we need write barrier for store op v.
// v must be Store/Move/Zero.
func needwb(v *Value) bool {
	t, ok := v.Aux.(*types.Type)
	if !ok {
		v.Fatalf("store aux is not a type: %s", v.LongString())
	}
	if !t.HasPointer() {
		return false
	}
	if IsStackAddr(v.Args[0]) {
		return false // write on stack doesn't need write barrier
	}
	return true
}

// writebarrier pass inserts write barriers for store ops (Store, Move, Zero)
// when necessary (the condition above). It rewrites store ops to branches
// and runtime calls, like
//
// if writeBarrier.enabled {
//   writebarrierptr(ptr, val)
// } else {
//   *ptr = val
// }
//
// A sequence of WB stores for many pointer fields of a single type will
// be emitted together, with a single branch.
func writebarrier(f *Func) {
	if !f.fe.UseWriteBarrier() {
		return
	}

	var sb, sp, wbaddr, const0 *Value
	var writebarrierptr, typedmemmove, typedmemclr *obj.LSym
	var stores, after []*Value
	var sset *sparseSet
	var storeNumber []int32

	for _, b := range f.Blocks { // range loop is safe since the blocks we added contain no stores to expand
		// first, identify all the stores that need to insert a write barrier.
		// mark them with WB ops temporarily. record presence of WB ops.
		nWBops := 0 // count of temporarily created WB ops remaining to be rewritten in the current block
		for _, v := range b.Values {
			switch v.Op {
			case OpStore, OpMove, OpZero:
				if needwb(v) {
					switch v.Op {
					case OpStore:
						v.Op = OpStoreWB
					case OpMove:
						v.Op = OpMoveWB
					case OpZero:
						v.Op = OpZeroWB
					}
					nWBops++
				}
			}
		}
		if nWBops == 0 {
			continue
		}

		if wbaddr == nil {
			// lazily initialize global values for write barrier test and calls
			// find SB and SP values in entry block
			initpos := f.Entry.Pos
			for _, v := range f.Entry.Values {
				if v.Op == OpSB {
					sb = v
				}
				if v.Op == OpSP {
					sp = v
				}
				if sb != nil && sp != nil {
					break
				}
			}
			if sb == nil {
				sb = f.Entry.NewValue0(initpos, OpSB, f.Config.Types.Uintptr)
			}
			if sp == nil {
				sp = f.Entry.NewValue0(initpos, OpSP, f.Config.Types.Uintptr)
			}
			wbsym := f.fe.Syslook("writeBarrier")
			wbaddr = f.Entry.NewValue1A(initpos, OpAddr, f.Config.Types.UInt32Ptr, wbsym, sb)
			writebarrierptr = f.fe.Syslook("writebarrierptr")
			typedmemmove = f.fe.Syslook("typedmemmove")
			typedmemclr = f.fe.Syslook("typedmemclr")
			const0 = f.ConstInt32(initpos, f.Config.Types.UInt32, 0)

			// allocate auxiliary data structures for computing store order
			sset = f.newSparseSet(f.NumValues())
			defer f.retSparseSet(sset)
			storeNumber = make([]int32, f.NumValues())
		}

		// order values in store order
		b.Values = storeOrder(b.Values, sset, storeNumber)

	again:
		// find the start and end of the last contiguous WB store sequence.
		// a branch will be inserted there. values after it will be moved
		// to a new block.
		var last *Value
		var start, end int
		values := b.Values
	FindSeq:
		for i := len(values) - 1; i >= 0; i-- {
			w := values[i]
			switch w.Op {
			case OpStoreWB, OpMoveWB, OpZeroWB:
				start = i
				if last == nil {
					last = w
					end = i + 1
				}
			case OpVarDef, OpVarLive, OpVarKill:
				continue
			default:
				if last == nil {
					continue
				}
				break FindSeq
			}
		}
		stores = append(stores[:0], b.Values[start:end]...) // copy to avoid aliasing
		after = append(after[:0], b.Values[end:]...)
		b.Values = b.Values[:start]

		// find the memory before the WB stores
		mem := stores[0].MemoryArg()
		pos := stores[0].Pos
		bThen := f.NewBlock(BlockPlain)
		bElse := f.NewBlock(BlockPlain)
		bEnd := f.NewBlock(b.Kind)
		bThen.Pos = pos
		bElse.Pos = pos
		bEnd.Pos = b.Pos
		b.Pos = pos

		// set up control flow for end block
		bEnd.SetControl(b.Control)
		bEnd.Likely = b.Likely
		for _, e := range b.Succs {
			bEnd.Succs = append(bEnd.Succs, e)
			e.b.Preds[e.i].b = bEnd
		}

		// set up control flow for write barrier test
		// load word, test word, avoiding partial register write from load byte.
		cfgtypes := &f.Config.Types
		flag := b.NewValue2(pos, OpLoad, cfgtypes.UInt32, wbaddr, mem)
		flag = b.NewValue2(pos, OpNeq32, cfgtypes.Bool, flag, const0)
		b.Kind = BlockIf
		b.SetControl(flag)
		b.Likely = BranchUnlikely
		b.Succs = b.Succs[:0]
		b.AddEdgeTo(bThen)
		b.AddEdgeTo(bElse)
		bThen.AddEdgeTo(bEnd)
		bElse.AddEdgeTo(bEnd)

		// for each write barrier store, append write barrier version to bThen
		// and simple store version to bElse
		memThen := mem
		memElse := mem
		for _, w := range stores {
			ptr := w.Args[0]
			pos := w.Pos

			var fn *obj.LSym
			var typ *obj.LSym
			var val *Value
			switch w.Op {
			case OpStoreWB:
				fn = writebarrierptr
				val = w.Args[1]
				nWBops--
			case OpMoveWB:
				fn = typedmemmove
				val = w.Args[1]
				typ = w.Aux.(*types.Type).Symbol()
				nWBops--
			case OpZeroWB:
				fn = typedmemclr
				typ = w.Aux.(*types.Type).Symbol()
				nWBops--
			case OpVarDef, OpVarLive, OpVarKill:
			}

			// then block: emit write barrier call
			switch w.Op {
			case OpStoreWB, OpMoveWB, OpZeroWB:
				volatile := w.Op == OpMoveWB && isVolatile(val)
				memThen = wbcall(pos, bThen, fn, typ, ptr, val, memThen, sp, sb, volatile)
			case OpVarDef, OpVarLive, OpVarKill:
				memThen = bThen.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, memThen)
			}

			// else block: normal store
			switch w.Op {
			case OpStoreWB:
				memElse = bElse.NewValue3A(pos, OpStore, types.TypeMem, w.Aux, ptr, val, memElse)
			case OpMoveWB:
				memElse = bElse.NewValue3I(pos, OpMove, types.TypeMem, w.AuxInt, ptr, val, memElse)
				memElse.Aux = w.Aux
			case OpZeroWB:
				memElse = bElse.NewValue2I(pos, OpZero, types.TypeMem, w.AuxInt, ptr, memElse)
				memElse.Aux = w.Aux
			case OpVarDef, OpVarLive, OpVarKill:
				memElse = bElse.NewValue1A(pos, w.Op, types.TypeMem, w.Aux, memElse)
			}

			if fn != nil {
				// Note that we set up a writebarrier function call.
				if !f.WBPos.IsKnown() {
					f.WBPos = pos
				}
				if f.fe.Debug_wb() {
					f.Warnl(pos, "write barrier")
				}
			}
		}

		// merge memory
		// Splice memory Phi into the last memory of the original sequence,
		// which may be used in subsequent blocks. Other memories in the
		// sequence must be dead after this block since there can be only
		// one memory live.
		bEnd.Values = append(bEnd.Values, last)
		last.Block = bEnd
		last.reset(OpPhi)
		last.Type = types.TypeMem
		last.AddArg(memThen)
		last.AddArg(memElse)
		for _, w := range stores {
			if w != last {
				w.resetArgs()
			}
		}
		for _, w := range stores {
			if w != last {
				f.freeValue(w)
			}
		}

		// put values after the store sequence into the end block
		bEnd.Values = append(bEnd.Values, after...)
		for _, w := range after {
			w.Block = bEnd
		}

		// if we have more stores in this block, do this block again
		if nWBops > 0 {
			goto again
		}
	}
}

// wbcall emits write barrier runtime call in b, returns memory.
// if valIsVolatile, it moves val into temp space before making the call.
func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Value, valIsVolatile bool) *Value {
	config := b.Func.Config

	var tmp GCNode
	if valIsVolatile {
		// Copy to temp location if the source is volatile (will be clobbered by
		// a function call). Marshaling the args to typedmemmove might clobber the
		// value we're trying to move.
		t := val.Type.ElemType()
		tmp = b.Func.fe.Auto(val.Pos, t)
		mem = b.NewValue1A(pos, OpVarDef, types.TypeMem, tmp, mem)
		tmpaddr := b.NewValue1A(pos, OpAddr, t.PtrTo(), tmp, sp)
		siz := t.Size()
		mem = b.NewValue3I(pos, OpMove, types.TypeMem, siz, tmpaddr, val, mem)
		mem.Aux = t
		val = tmpaddr
	}

	// put arguments on stack
	off := config.ctxt.FixedFrameSize()

	if typ != nil { // for typedmemmove
		taddr := b.NewValue1A(pos, OpAddr, b.Func.Config.Types.Uintptr, typ, sb)
		off = round(off, taddr.Type.Alignment())
		arg := b.NewValue1I(pos, OpOffPtr, taddr.Type.PtrTo(), off, sp)
		mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, taddr, mem)
		off += taddr.Type.Size()
	}

	off = round(off, ptr.Type.Alignment())
	arg := b.NewValue1I(pos, OpOffPtr, ptr.Type.PtrTo(), off, sp)
	mem = b.NewValue3A(pos, OpStore, types.TypeMem, ptr.Type, arg, ptr, mem)
	off += ptr.Type.Size()

	if val != nil {
		off = round(off, val.Type.Alignment())
		arg = b.NewValue1I(pos, OpOffPtr, val.Type.PtrTo(), off, sp)
		mem = b.NewValue3A(pos, OpStore, types.TypeMem, val.Type, arg, val, mem)
		off += val.Type.Size()
	}
	off = round(off, config.PtrSize)

	// issue call
	mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, fn, mem)
	mem.AuxInt = off - config.ctxt.FixedFrameSize()

	if valIsVolatile {
		mem = b.NewValue1A(pos, OpVarKill, types.TypeMem, tmp, mem) // mark temp dead
	}

	return mem
}

// round to a multiple of r, r is a power of 2
func round(o int64, r int64) int64 {
	return (o + r - 1) &^ (r - 1)
}

// IsStackAddr returns whether v is known to be an address of a stack slot
func IsStackAddr(v *Value) bool {
	for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
		v = v.Args[0]
	}
	switch v.Op {
	case OpSP:
		return true
	case OpAddr:
		return v.Args[0].Op == OpSP
	}
	return false
}

// isVolatile returns whether v is a pointer to argument region on stack which
// will be clobbered by a function call.
func isVolatile(v *Value) bool {
	for v.Op == OpOffPtr || v.Op == OpAddPtr || v.Op == OpPtrIndex || v.Op == OpCopy {
		v = v.Args[0]
	}
	return v.Op == OpSP
}
<|endoftext|>


<commit_before>package dax

import (
	"fmt"
	"image"
	"image/png"
	"os"

	"github.com/go-gl/gl/v3.3-core/gl"
	"github.com/go-gl/glfw/v3.1/glfw"
)

type Window struct {
	app           *Application
	name          string
	width, height int
	fb            Framebuffer
	scene         Scener
	glfwWindow    *glfw.Window
}

func newWindow(app *Application, name string, width, height int) *Window {
	window := new(Window)
	window.app = app
	window.name = name
	window.width = width
	window.height = height

	glfw.WindowHint(glfw.ContextVersionMajor, 3)
	glfw.WindowHint(glfw.ContextVersionMinor, 3)
	glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
	glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

	glfwWindow, err := glfw.CreateWindow(width, height, name, nil, nil)
	if err != nil {
		panic(err)
	}
	window.glfwWindow = glfwWindow
	glfwWindow.MakeContextCurrent()

	glfw.SwapInterval(1)

	// create OnScreen object
	window.fb = newOnScreen(width, height)

	// window events
	glfwWindow.SetCloseCallback(onClose)
	glfwWindow.SetSizeCallback(onResize)

	// key events
	glfwWindow.SetKeyCallback(onKeyEvent)
	glfwWindow.SetCharCallback(onRuneEvent)

	// mouse events
	glfwWindow.SetMouseButtonCallback(OnMouseButton)
	glfwWindow.SetCursorPosCallback(OnMouseMoved)

	// Install the default scene
	window.SetScene(new(Scene))

	return window
}

func (w *Window) Update() {
	w.scene.Update()
}

func (w *Window) Draw() {
	c := w.scene.BackgroundColor()

	gl.ClearColor(c.R, c.G, c.B, c.A)
	gl.Clear(gl.COLOR_BUFFER_BIT)
	w.scene.Draw(w.fb)
}

func (w *Window) Close() {
	w.glfwWindow.SetShouldClose(true)
}

func onResize(w *glfw.Window, width, height int) {
	window := getWindow(w)
	window.width = width
	window.height = height
	window.scene.OnResize(window.fb, width, height)
}

func onClose(w *glfw.Window) {
	window := getWindow(w)
	window.scene.TearDown()
}

func (w *Window) doScreenshot() {
	var filename string
	n := 0

	for {
		filename = fmt.Sprintf("%s - %04d.png", w.name, n)
		_, err := os.Stat(filename)
		if err == nil {
			n++
			continue
		}
		break
	}

	if n > 9999 {
		fmt.Errorf("Too many Screenshots!")
		return
	}

	w.ScreenshotToFile(filename)
}

func onKeyEvent(w *glfw.Window, key glfw.Key, scancode int,
	action glfw.Action, mods glfw.ModifierKey) {
	window := getWindow(w)

	if action == glfw.Press {
		switch key {
		case glfw.KeyF12:
			window.doScreenshot()
		}

		window.scene.OnKeyPressed()
	} else if action == glfw.Release {
		window.scene.OnKeyReleased()
	}
}

func OnMouseMoved(w *glfw.Window, x, y float64) {
	window := getWindow(w)
	window.scene.OnMouseMoved(float32(x), float32(y))
}

func OnMouseButton(w *glfw.Window, button glfw.MouseButton,
	action glfw.Action, mod glfw.ModifierKey) {
	window := getWindow(w)
	x, y := w.GetCursorPos()
	if action == glfw.Press {
		window.scene.OnMouseButtonPressed(MouseButton(button), float32(x), float32(y))
	} else if action == glfw.Release {
		window.scene.OnMouseButtonReleased(MouseButton(button), float32(x), float32(y))
	}
}

func onRuneEvent(w *glfw.Window, r rune) {
	window := getWindow(w)
	window.scene.OnRuneEntered(r)
}

func (w *Window) SetScene(s Scener) {
	if w.scene != nil {
		w.scene.TearDown()
	}

	if s != nil {
		w.scene = s
	} else {
		// fallback to the default scene, maintaining the invariant
		// that we always have valid scene
		w.scene = new(Scene)
	}
	sceneSetup(w.scene)
	w.scene.OnResize(w.fb, w.width, w.height)
}

func (w *Window) Screenshot() *image.RGBA {
	return w.fb.Screenshot()
}

func (w *Window) ScreenshotToFile(filename string) {
	img := w.fb.Screenshot()

	file, err := os.Create(filename)
	if err != nil {
		fmt.Println(err.Error())
	}
	defer file.Close()

	err = png.Encode(file, img)
	if err != nil {
		fmt.Println(err.Error())
	}
}
<commit_msg>window: Hide internal callback functions<commit_after>package dax

import (
	"fmt"
	"image"
	"image/png"
	"os"

	"github.com/go-gl/gl/v3.3-core/gl"
	"github.com/go-gl/glfw/v3.1/glfw"
)

type Window struct {
	app           *Application
	name          string
	width, height int
	fb            Framebuffer
	scene         Scener
	glfwWindow    *glfw.Window
}

func newWindow(app *Application, name string, width, height int) *Window {
	window := new(Window)
	window.app = app
	window.name = name
	window.width = width
	window.height = height

	glfw.WindowHint(glfw.ContextVersionMajor, 3)
	glfw.WindowHint(glfw.ContextVersionMinor, 3)
	glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
	glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)

	glfwWindow, err := glfw.CreateWindow(width, height, name, nil, nil)
	if err != nil {
		panic(err)
	}
	window.glfwWindow = glfwWindow
	glfwWindow.MakeContextCurrent()

	glfw.SwapInterval(1)

	// create OnScreen object
	window.fb = newOnScreen(width, height)

	// window events
	glfwWindow.SetCloseCallback(onClose)
	glfwWindow.SetSizeCallback(onResize)

	// key events
	glfwWindow.SetKeyCallback(onKeyEvent)
	glfwWindow.SetCharCallback(onRuneEvent)

	// mouse events
	glfwWindow.SetMouseButtonCallback(onMouseButton)
	glfwWindow.SetCursorPosCallback(onMouseMoved)

	// Install the default scene
	window.SetScene(new(Scene))

	return window
}

func (w *Window) Update() {
	w.scene.Update()
}

func (w *Window) Draw() {
	c := w.scene.BackgroundColor()

	gl.ClearColor(c.R, c.G, c.B, c.A)
	gl.Clear(gl.COLOR_BUFFER_BIT)
	w.scene.Draw(w.fb)
}

func (w *Window) Close() {
	w.glfwWindow.SetShouldClose(true)
}

func onResize(w *glfw.Window, width, height int) {
	window := getWindow(w)
	window.width = width
	window.height = height
	window.scene.OnResize(window.fb, width, height)
}

func onClose(w *glfw.Window) {
	window := getWindow(w)
	window.scene.TearDown()
}

func (w *Window) doScreenshot() {
	var filename string
	n := 0

	for {
		filename = fmt.Sprintf("%s - %04d.png", w.name, n)
		_, err := os.Stat(filename)
		if err == nil {
			n++
			continue
		}
		break
	}

	if n > 9999 {
		fmt.Errorf("Too many Screenshots!")
		return
	}

	w.ScreenshotToFile(filename)
}

func onKeyEvent(w *glfw.Window, key glfw.Key, scancode int,
	action glfw.Action, mods glfw.ModifierKey) {
	window := getWindow(w)

	if action == glfw.Press {
		switch key {
		case glfw.KeyF12:
			window.doScreenshot()
		}

		window.scene.OnKeyPressed()
	} else if action == glfw.Release {
		window.scene.OnKeyReleased()
	}
}

func onMouseMoved(w *glfw.Window, x, y float64) {
	window := getWindow(w)
	window.scene.OnMouseMoved(float32(x), float32(y))
}

func onMouseButton(w *glfw.Window, button glfw.MouseButton,
	action glfw.Action, mod glfw.ModifierKey) {
	window := getWindow(w)
	x, y := w.GetCursorPos()
	if action == glfw.Press {
		window.scene.OnMouseButtonPressed(MouseButton(button), float32(x), float32(y))
	} else if action == glfw.Release {
		window.scene.OnMouseButtonReleased(MouseButton(button), float32(x), float32(y))
	}
}

func onRuneEvent(w *glfw.Window, r rune) {
	window := getWindow(w)
	window.scene.OnRuneEntered(r)
}

func (w *Window) SetScene(s Scener) {
	if w.scene != nil {
		w.scene.TearDown()
	}

	if s != nil {
		w.scene = s
	} else {
		// fallback to the default scene, maintaining the invariant
		// that we always have valid scene
		w.scene = new(Scene)
	}
	sceneSetup(w.scene)
	w.scene.OnResize(w.fb, w.width, w.height)
}

func (w *Window) Screenshot() *image.RGBA {
	return w.fb.Screenshot()
}

func (w *Window) ScreenshotToFile(filename string) {
	img := w.fb.Screenshot()

	file, err := os.Create(filename)
	if err != nil {
		fmt.Println(err.Error())
	}
	defer file.Close()

	err = png.Encode(file, img)
	if err != nil {
		fmt.Println(err.Error())
	}
}
<|endoftext|>


<commit_before>// Copyright 2020 The gVisor 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 root

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/cenkalti/backoff"
	"golang.org/x/sys/unix"
	"gvisor.dev/gvisor/runsc/specutils"
	"gvisor.dev/gvisor/runsc/testutil"
)

// TestDoKill checks that when "runsc do..." is killed, the sandbox process is
// also terminated. This ensures that parent death signal is propagate to the
// sandbox process correctly.
func TestDoKill(t *testing.T) {
	// Make the sandbox process be reparented here when it's killed, so we can
	// wait for it.
	if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); err != nil {
		t.Fatalf("prctl(PR_SET_CHILD_SUBREAPER): %v", err)
	}

	cmd := exec.Command(specutils.ExePath, "do", "sleep", "10000")
	buf := &bytes.Buffer{}
	cmd.Stdout = buf
	cmd.Stderr = buf
	cmd.Start()

	var pid int
	findSandbox := func() error {
		var err error
		pid, err = sandboxPid(cmd.Process.Pid)
		if err != nil {
			return &backoff.PermanentError{Err: err}
		}
		if pid == 0 {
			return fmt.Errorf("sandbox process not found")
		}
		return nil
	}
	if err := testutil.Poll(findSandbox, 10*time.Second); err != nil {
		t.Fatalf("failed to find sandbox: %v", err)
	}
	t.Logf("Found sandbox, pid: %d", pid)

	if err := cmd.Process.Kill(); err != nil {
		t.Fatalf("failed to kill run process: %v", err)
	}
	cmd.Wait()
	t.Logf("Parent process killed (%d). Output: %s", cmd.Process.Pid, buf.String())

	ch := make(chan struct{})
	go func() {
		defer func() { ch <- struct{}{} }()
		t.Logf("Waiting for sandbox process (%d) termination", pid)
		if _, err := unix.Wait4(pid, nil, 0, nil); err != nil {
			t.Errorf("error waiting for sandbox process (%d): %v", pid, err)
		}
	}()
	select {
	case <-ch:
		// Done
	case <-time.After(5 * time.Second):
		t.Fatalf("timeout waiting for sandbox process (%d) to exit", pid)
	}
}

// sandboxPid looks for the sandbox process inside the process tree starting
// from "pid". It returns 0 and no error if no sandbox process is found. It
// returns error if anything failed.
func sandboxPid(pid int) (int, error) {
	cmd := exec.Command("pgrep", "-P", strconv.Itoa(pid))
	buf := &bytes.Buffer{}
	cmd.Stdout = buf
	if err := cmd.Start(); err != nil {
		return 0, err
	}
	ps, err := cmd.Process.Wait()
	if err != nil {
		return 0, err
	}
	if ps.ExitCode() == 1 {
		// pgrep returns 1 when no process is found.
		return 0, nil
	}

	var children []int
	for _, line := range strings.Split(buf.String(), "\n") {
		if len(line) == 0 {
			continue
		}
		child, err := strconv.Atoi(line)
		if err != nil {
			return 0, err
		}

		cmdline, err := ioutil.ReadFile(filepath.Join("/proc", line, "cmdline"))
		if err != nil {
			return 0, err
		}
		args := strings.SplitN(string(cmdline), "\x00", 2)
		if len(args) == 0 {
			return 0, fmt.Errorf("malformed cmdline file: %q", cmdline)
		}
		// The sandbox process has the first argument set to "runsc-sandbox".
		if args[0] == "runsc-sandbox" {
			return child, nil
		}

		children = append(children, child)
	}

	// Sandbox process wasn't found, try another level down.
	for _, pid := range children {
		sand, err := sandboxPid(pid)
		if err != nil {
			return 0, err
		}
		if sand != 0 {
			return sand, nil
		}
		// Not found, continue the search.
	}
	return 0, nil
}
<commit_msg>Skip process if it has exited<commit_after>// Copyright 2020 The gVisor 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 root

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"
	"testing"
	"time"

	"github.com/cenkalti/backoff"
	"golang.org/x/sys/unix"
	"gvisor.dev/gvisor/runsc/specutils"
	"gvisor.dev/gvisor/runsc/testutil"
)

// TestDoKill checks that when "runsc do..." is killed, the sandbox process is
// also terminated. This ensures that parent death signal is propagate to the
// sandbox process correctly.
func TestDoKill(t *testing.T) {
	// Make the sandbox process be reparented here when it's killed, so we can
	// wait for it.
	if err := unix.Prctl(unix.PR_SET_CHILD_SUBREAPER, 1, 0, 0, 0); err != nil {
		t.Fatalf("prctl(PR_SET_CHILD_SUBREAPER): %v", err)
	}

	cmd := exec.Command(specutils.ExePath, "do", "sleep", "10000")
	buf := &bytes.Buffer{}
	cmd.Stdout = buf
	cmd.Stderr = buf
	cmd.Start()

	var pid int
	findSandbox := func() error {
		var err error
		pid, err = sandboxPid(cmd.Process.Pid)
		if err != nil {
			return &backoff.PermanentError{Err: err}
		}
		if pid == 0 {
			return fmt.Errorf("sandbox process not found")
		}
		return nil
	}
	if err := testutil.Poll(findSandbox, 10*time.Second); err != nil {
		t.Fatalf("failed to find sandbox: %v", err)
	}
	t.Logf("Found sandbox, pid: %d", pid)

	if err := cmd.Process.Kill(); err != nil {
		t.Fatalf("failed to kill run process: %v", err)
	}
	cmd.Wait()
	t.Logf("Parent process killed (%d). Output: %s", cmd.Process.Pid, buf.String())

	ch := make(chan struct{})
	go func() {
		defer func() { ch <- struct{}{} }()
		t.Logf("Waiting for sandbox process (%d) termination", pid)
		if _, err := unix.Wait4(pid, nil, 0, nil); err != nil {
			t.Errorf("error waiting for sandbox process (%d): %v", pid, err)
		}
	}()
	select {
	case <-ch:
		// Done
	case <-time.After(5 * time.Second):
		t.Fatalf("timeout waiting for sandbox process (%d) to exit", pid)
	}
}

// sandboxPid looks for the sandbox process inside the process tree starting
// from "pid". It returns 0 and no error if no sandbox process is found. It
// returns error if anything failed.
func sandboxPid(pid int) (int, error) {
	cmd := exec.Command("pgrep", "-P", strconv.Itoa(pid))
	buf := &bytes.Buffer{}
	cmd.Stdout = buf
	if err := cmd.Start(); err != nil {
		return 0, err
	}
	ps, err := cmd.Process.Wait()
	if err != nil {
		return 0, err
	}
	if ps.ExitCode() == 1 {
		// pgrep returns 1 when no process is found.
		return 0, nil
	}

	var children []int
	for _, line := range strings.Split(buf.String(), "\n") {
		if len(line) == 0 {
			continue
		}
		child, err := strconv.Atoi(line)
		if err != nil {
			return 0, err
		}

		cmdline, err := ioutil.ReadFile(filepath.Join("/proc", line, "cmdline"))
		if err != nil {
			if os.IsNotExist(err) {
				// Raced with process exit.
				continue
			}
			return 0, err
		}
		args := strings.SplitN(string(cmdline), "\x00", 2)
		if len(args) == 0 {
			return 0, fmt.Errorf("malformed cmdline file: %q", cmdline)
		}
		// The sandbox process has the first argument set to "runsc-sandbox".
		if args[0] == "runsc-sandbox" {
			return child, nil
		}

		children = append(children, child)
	}

	// Sandbox process wasn't found, try another level down.
	for _, pid := range children {
		sand, err := sandboxPid(pid)
		if err != nil {
			return 0, err
		}
		if sand != 0 {
			return sand, nil
		}
		// Not found, continue the search.
	}
	return 0, nil
}
<|endoftext|>


<commit_before>// Copyright 2015 Eryx <evorui аt gmаil dοt cοm>, All rights reserved.
//
// 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 frontend

import (
	"fmt"
	"path/filepath"
	"strings"
	"time"

	"github.com/hooto/httpsrv"
	"github.com/hooto/iam/iamapi"
	"github.com/hooto/iam/iamclient"
	"github.com/lessos/lessgo/crypto/idhash"
	"github.com/lessos/lessgo/types"
	"github.com/lessos/lessgo/x/webui"

	"github.com/hooto/hpress/api"
	"github.com/hooto/hpress/config"
	"github.com/hooto/hpress/datax"
	"github.com/hooto/hpress/store"
)

type Index struct {
	*httpsrv.Controller
	renderSkip bool
	hookPosts  []func()
	us         iamapi.UserSession
}

func (c *Index) Init() int {
	c.us, _ = iamclient.SessionInstance(c.Session)
	return 0
}

func (c Index) filter(rt []string, spec *api.Spec) (string, string, bool) {

	for _, route := range spec.Router.Routes {

		matlen, params := 0, map[string]string{}

		for i, node := range route.Tree {

			if len(node) < 1 || i >= len(rt) {
				break
			}

			if node[0] == ':' {

				params[node[1:]] = rt[i]

				matlen++

			} else if node == rt[i] {

				matlen++
			}
		}

		if matlen == len(route.Tree) {

			for k, v := range params {
				c.Params.Values[k] = append(c.Params.Values[k], v)
			}

			return route.DataAction, route.Template, true
		}
	}

	for _, route := range spec.Router.Routes {
		if route.Default {
			return route.DataAction, route.Template, true
		}
	}

	return "", "", false
}

var (
	srvname_default = "core-genereal"
	uris_default    = []string{"core-general"}
)

func (c Index) IndexAction() {

	c.AutoRender = false
	start := time.Now().UnixNano()

	if v := config.SysConfigList.FetchString("http_h_ac_allow_origin"); v != "" {
		c.Response.Out.Header().Set("Access-Control-Allow-Origin", v)
	}

	var (
		reqpath = filepath.Clean("/" + c.Request.RequestPath)
		uris    = []string{}
	)
	if reqpath == "" || reqpath == "." {
		reqpath = "/"
	}
	if len(reqpath) > 0 && reqpath != "/" {
		uris = strings.Split(strings.Trim(reqpath, "/"), "/")
	}

	if len(uris) < 1 {
		if config.RouterBasepathDefault != "/" {
			reqpath = config.RouterBasepathDefault
			uris = config.RouterBasepathDefaults
		} else {
			uris = uris_default
		}
	}
	srvname := uris[0]

	if len(uris) < 2 {
		uris = append(uris, "")
	}
	// fmt.Println(uris, srvname, c.Params.Get("referid"), c.Params.Get("id"))

	mod, ok := config.Modules[srvname]
	if !ok {
		srvname = srvname_default
		mod, ok = config.Modules[srvname]
		if !ok {
			return
		}
	}

	dataAction, template, mat := c.filter(uris[1:], mod)
	if !mat {
		if uris[1] == "" {
			template = "index.tpl"
		} else {
			template = "404.tpl"
		}
	}

	if lang, ok := c.Data["LANG"]; ok {
		c.Data["LANG"] = strings.ToLower(lang.(string))
	} else {
		c.Data["LANG"] = ""
	}

	if len(config.Languages) > 1 {
		c.Data["frontend_langs"] = config.Languages
	}

	// if session, err := c.Session.Instance(); err == nil {
	// 	c.Data["session"] = session
	// }

	c.Data["baseuri"] = "/" + srvname
	c.Data["http_request_path"] = reqpath
	c.Data["srvname"] = srvname
	c.Data["modname"] = mod.Meta.Name
	c.Data["sys_version_sign"] = config.SysVersionSign
	if c.us.IsLogin() {
		c.Data["s_user"] = c.us.UserName
	}

	if dataAction != "" {

		for _, action := range mod.Actions {

			if action.Name != dataAction {
				continue
			}

			for _, datax := range action.Datax {
				c.dataRender(srvname, action.Name, datax)
				c.Data["__datax_table__"] = datax.Query.Table
			}

			break
		}
	}

	if !c.renderSkip {
		// render_start := time.Now()
		c.Render(mod.Meta.Name, template)

		// fmt.Println("render in-time", mod.Meta.Name, template, time.Since(render_start))

		c.RenderString(fmt.Sprintf("<!-- rt-time/db+render : %d ms -->", (time.Now().UnixNano()-start)/1e6))
	}

	// fmt.Println("hookPosts", len(c.hookPosts))
	for _, fn := range c.hookPosts {
		fn()
	}
}

var staticImages = types.ArrayString([]string{
	"png", "jpg", "jpeg", "gif", "webp", "svg",
})

func (c *Index) dataRender(srvname, action_name string, ad api.ActionData) {

	mod, ok := config.Modules[srvname]
	if !ok {
		return
	}

	qry := datax.NewQuery(mod.Meta.Name, ad.Query.Table)
	if ad.Query.Limit > 0 {
		qry.Limit(ad.Query.Limit)
	}

	if ad.Query.Order != "" {
		qry.Order(ad.Query.Order)
	}

	qry.Filter("status", 1)

	qry.Pager = ad.Pager

	switch ad.Type {

	case "node.list":

		for _, modNode := range mod.NodeModels {

			if ad.Query.Table != modNode.Meta.Name {
				continue
			}

			for _, term := range modNode.Terms {

				if termVal := c.Params.Get("term_" + term.Meta.Name); termVal != "" {

					switch term.Type {

					case api.TermTaxonomy:

						if idxs := datax.TermTaxonomyCacheIndexes(mod.Meta.Name, term.Meta.Name, termVal); len(idxs) > 1 {
							args := []interface{}{}
							for _, idx := range idxs {
								args = append(args, idx)
							}
							qry.Filter("term_"+term.Meta.Name+".in", args...)
						} else {
							qry.Filter("term_"+term.Meta.Name, termVal)
						}

						c.Data["term_"+term.Meta.Name] = termVal

					case api.TermTag:
						// TOPO
						qry.Filter("term_"+term.Meta.Name+".like", "%"+termVal+"%")
						c.Data["term_"+term.Meta.Name] = termVal
					}
				}
			}

			break
		}

		page := c.Params.Int64("page")
		if page > 1 {
			qry.Offset(ad.Query.Limit * (page - 1))
		}

		if c.Params.Get("qry_text") != "" {
			qry.Filter("field_title.like", "%"+c.Params.Get("qry_text")+"%")
			c.Data["qry_text"] = c.Params.Get("qry_text")
		}

		var ls api.NodeList
		qryhash := qry.Hash()

		if ad.CacheTTL > 0 && (!c.us.IsLogin() || c.us.UserName != config.Config.AppInstance.Meta.User) {
			if rs := store.DataLocal.NewReader([]byte(qryhash)).Query(); rs.OK() {
				rs.Decode(&ls)
			}
		}

		if len(ls.Items) == 0 {

			if c.Params.Get("qry_text") != "" {
				ls = qry.NodeListSearch(c.Params.Get("qry_text"))
				if ls.Error != nil {
					ls = qry.NodeList([]string{}, []string{})
				}
			} else {
				ls = qry.NodeList([]string{}, []string{})
			}
			// fmt.Println("index node.list")
			if ad.CacheTTL > 0 && len(ls.Items) > 0 {
				c.hookPosts = append(
					c.hookPosts,
					func() {
						store.DataLocal.NewWriter([]byte(qryhash), ls).ExpireSet(ad.CacheTTL).Commit()
					},
				)
			}
		}

		c.Data[ad.Name] = ls

		if qry.Pager {
			pager := webui.NewPager(uint64(page),
				uint64(ls.Meta.TotalResults),
				uint64(ls.Meta.ItemsPerList),
				10)
			pager.CurrentPageNumber = uint64(page)
			c.Data[ad.Name+"_pager"] = pager
		}

	case "node.entry":

		id := c.Params.Get(ad.Name + "_id")
		if id == "" {
			id = c.Params.Get("id")
			if id == "" {
				return
			}
		}
		// fmt.Println("node.entry", ad.Name+"_id", id)

		nodeModel, err := config.SpecNodeModel(mod.Meta.Name, ad.Query.Table)
		if err != nil {
			return
		}

		nodeRefer := ""
		if nodeModel.Extensions.NodeRefer != "" {
			if mv, ok := c.Data[action_name+"_nsr_"+nodeModel.Extensions.NodeRefer]; ok {
				nodeRefer = mv.(string)
			}
		}

		id_ext := ""
		if mod.Meta.Name == "core/gdoc" {
			if ad.Query.Table == "page" {
				id = strings.ToLower(c.Request.UrlPathExtra)
			} else if ad.Query.Table == "doc" && api.NodeIdReg.MatchString(id) {
				id_ext = "html"
			}
		}
		if i := strings.LastIndex(id, "."); i > 0 {
			id_ext = id[i+1:]
			id = id[:i]
		}

		if id_ext == "html" {
			qry.Filter("id", id)
		} else if staticImages.Has(id_ext) {
			if mod.Meta.Name == "core/gdoc" && ad.Query.Table == "page" {

				pid := datax.GdocNodeId(c.Params.Get("doc_entry_id"))
				if pid != "" {
					// fmt.Println(fmt.Sprintf("%s/var/vcs/%s/%s", config.Prefix, pid, c.Request.UrlPathExtra))
					s2Server(c.Controller, c.Request.UrlPathExtra,
						fmt.Sprintf("%s/var/vcs/%s/%s", config.Prefix, pid, c.Request.UrlPathExtra))
					c.renderSkip = true
				}
			}
			return

		} else if nodeModel.Extensions.Permalink != "" {
			if nodeModel.Extensions.NodeRefer != "" && nodeRefer == "" {
				return
			}
			qry.Filter("ext_permalink_idx", idhash.HashToHexString([]byte(nodeRefer+id), 12))
		} else {
			return
		}

		var entry api.Node
		qryhash := qry.Hash()
		if ad.CacheTTL > 0 && (!c.us.IsLogin() || c.us.UserName != config.Config.AppInstance.Meta.User) {
			if rs := store.DataLocal.NewReader([]byte(qryhash)).Query(); rs.OK() {
				rs.Decode(&entry)
			}
		}

		if entry.ID == "" {
			entry = qry.NodeEntry()
			if ad.CacheTTL > 0 && entry.Title != "" {
				c.hookPosts = append(
					c.hookPosts,
					func() {
						store.DataLocal.NewWriter([]byte(qryhash), entry).ExpireSet(ad.CacheTTL).Commit()
					},
				)
			}
		}

		if entry.ID == "" {
			return
		}

		if nodeModel.Extensions.AccessCounter {

			if ips := strings.Split(c.Request.RemoteAddr, ":"); len(ips) > 1 {

				table := fmt.Sprintf("hpn_%s_%s", idhash.HashToHexString([]byte(mod.Meta.Name), 12), ad.Query.Table)
				store.DataLocal.NewWriter([]byte("access_counter/"+table+"/"+ips[0]+"/"+entry.ID), "1").Commit()
			}
		}

		if nodeModel.Extensions.NodeSubRefer != "" {
			// fmt.Println("setting", action_name, ad.Query.Table, nodeModel.Extensions.NodeSubRefer, "_id", entry.ID)
			c.Data[action_name+"_nsr_"+ad.Query.Table] = entry.ID
		}

		if entry.Title != "" {
			c.Data["__html_head_title__"] = datax.StringSub(datax.TextHtml2Str(entry.Title), 0, 50)
		}

		c.Data[ad.Name] = entry

	case "term.list":

		var ls api.TermList
		qryhash := qry.Hash()
		if ad.CacheTTL > 0 {
			if rs := store.DataLocal.NewReader([]byte(qryhash)).Query(); rs.OK() {
				rs.Decode(&ls)
			}
		}

		if len(ls.Items) == 0 {
			ls = qry.TermList()
			if ad.CacheTTL > 0 && len(ls.Items) > 0 {
				store.DataLocal.NewWriter([]byte(qryhash), ls).ExpireSet(ad.CacheTTL).Commit()
			}
		}

		c.Data[ad.Name] = ls

		if qry.Pager {
			c.Data[ad.Name+"_pager"] = webui.NewPager(0,
				uint64(ls.Meta.TotalResults),
				uint64(ls.Meta.ItemsPerList),
				10)
		}

	case "term.entry":

		var entry api.Term
		qryhash := qry.Hash()

		if ad.CacheTTL > 0 {
			if rs := store.DataLocal.NewReader([]byte(qryhash)).Query(); rs.OK() {
				rs.Decode(&entry)
			}
		}

		if entry.Title == "" {
			entry = qry.TermEntry()
			if ad.CacheTTL > 0 && entry.Title != "" {
				store.DataLocal.NewWriter([]byte(qryhash), entry).ExpireSet(ad.CacheTTL).Commit()
			}
		}

		c.Data[ad.Name] = entry
	}
}
<commit_msg>[BUG] fix dataRender() NotFound issue<commit_after>// Copyright 2015 Eryx <evorui аt gmаil dοt cοm>, All rights reserved.
//
// 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 frontend

import (
	"fmt"
	"path/filepath"
	"strings"
	"time"

	"github.com/hooto/httpsrv"
	"github.com/hooto/iam/iamapi"
	"github.com/hooto/iam/iamclient"
	"github.com/lessos/lessgo/crypto/idhash"
	"github.com/lessos/lessgo/types"
	"github.com/lessos/lessgo/x/webui"

	"github.com/hooto/hpress/api"
	"github.com/hooto/hpress/config"
	"github.com/hooto/hpress/datax"
	"github.com/hooto/hpress/store"
)

type Index struct {
	*httpsrv.Controller
	hookPosts []func()
	us        iamapi.UserSession
}

func (c *Index) Init() int {
	c.us, _ = iamclient.SessionInstance(c.Session)
	return 0
}

func (c Index) filter(rt []string, spec *api.Spec) (string, string, bool) {

	for _, route := range spec.Router.Routes {

		matlen, params := 0, map[string]string{}

		for i, node := range route.Tree {

			if len(node) < 1 || i >= len(rt) {
				break
			}

			if node[0] == ':' {

				params[node[1:]] = rt[i]

				matlen++

			} else if node == rt[i] {

				matlen++
			}
		}

		if matlen == len(route.Tree) {

			for k, v := range params {
				c.Params.Values[k] = append(c.Params.Values[k], v)
			}

			return route.DataAction, route.Template, true
		}
	}

	for _, route := range spec.Router.Routes {
		if route.Default {
			return route.DataAction, route.Template, true
		}
	}

	return "", "", false
}

var (
	srvnameDefault     = "core-genereal"
	urisDefault        = []string{"core-general"}
	dataRenderOK       = 0
	dataRenderNotFound = 1
	dataRenderSkip     = 2
	staticImages       = types.ArrayString([]string{
		"png", "jpg", "jpeg", "gif", "webp", "svg",
	})
)

func (c Index) IndexAction() {

	c.AutoRender = false
	start := time.Now().UnixNano()

	if v := config.SysConfigList.FetchString("http_h_ac_allow_origin"); v != "" {
		c.Response.Out.Header().Set("Access-Control-Allow-Origin", v)
	}

	var (
		reqpath = filepath.Clean("/" + c.Request.RequestPath)
		uris    = []string{}
	)
	if reqpath == "" || reqpath == "." {
		reqpath = "/"
	}
	if len(reqpath) > 0 && reqpath != "/" {
		uris = strings.Split(strings.Trim(reqpath, "/"), "/")
	}

	if len(uris) < 1 {
		if config.RouterBasepathDefault != "/" {
			reqpath = config.RouterBasepathDefault
			uris = config.RouterBasepathDefaults
		} else {
			uris = urisDefault
		}
	}
	srvname := uris[0]

	if len(uris) < 2 {
		uris = append(uris, "")
	}
	// fmt.Println(uris, srvname, c.Params.Get("referid"), c.Params.Get("id"))

	mod, ok := config.Modules[srvname]
	if !ok {
		srvname = srvnameDefault
		mod, ok = config.Modules[srvname]
		if !ok {
			return
		}
	}

	dataAction, template, mat := c.filter(uris[1:], mod)
	if !mat {
		if uris[1] == "" {
			template = "index.tpl"
		} else {
			template = "404.tpl"
		}
	}

	if lang, ok := c.Data["LANG"]; ok {
		c.Data["LANG"] = strings.ToLower(lang.(string))
	} else {
		c.Data["LANG"] = ""
	}

	if len(config.Languages) > 1 {
		c.Data["frontend_langs"] = config.Languages
	}

	// if session, err := c.Session.Instance(); err == nil {
	// 	c.Data["session"] = session
	// }

	c.Data["baseuri"] = "/" + srvname
	c.Data["http_request_path"] = reqpath
	c.Data["srvname"] = srvname
	c.Data["modname"] = mod.Meta.Name
	c.Data["sys_version_sign"] = config.SysVersionSign
	if c.us.IsLogin() {
		c.Data["s_user"] = c.us.UserName
	}

	drs := dataRenderNotFound

	if dataAction != "" {

		for _, action := range mod.Actions {

			if action.Name != dataAction {
				continue
			}

			for _, datax := range action.Datax {
				drs = c.dataRender(srvname, action.Name, datax)
				c.Data["__datax_table__"] = datax.Query.Table
			}

			break
		}
	}

	switch drs {
	case dataRenderOK:

		// render_start := time.Now()
		c.Render(mod.Meta.Name, template)

		// fmt.Println("render in-time", mod.Meta.Name, template, time.Since(render_start))

		c.RenderString(fmt.Sprintf("<!-- rt-time/db+render : %d ms -->", (time.Now().UnixNano()-start)/1e6))

		// fmt.Println("hookPosts", len(c.hookPosts))
		for _, fn := range c.hookPosts {
			fn()
		}

	case dataRenderNotFound:
		c.RenderError(404, "Page Not Found")
	}
}

func (c *Index) dataRender(srvname, action_name string, ad api.ActionData) int {

	mod, ok := config.Modules[srvname]
	if !ok {
		return dataRenderNotFound
	}

	qry := datax.NewQuery(mod.Meta.Name, ad.Query.Table)
	if ad.Query.Limit > 0 {
		qry.Limit(ad.Query.Limit)
	}

	if ad.Query.Order != "" {
		qry.Order(ad.Query.Order)
	}

	qry.Filter("status", 1)

	qry.Pager = ad.Pager

	switch ad.Type {

	case "node.list":

		for _, modNode := range mod.NodeModels {

			if ad.Query.Table != modNode.Meta.Name {
				continue
			}

			for _, term := range modNode.Terms {

				if termVal := c.Params.Get("term_" + term.Meta.Name); termVal != "" {

					switch term.Type {

					case api.TermTaxonomy:

						if idxs := datax.TermTaxonomyCacheIndexes(mod.Meta.Name, term.Meta.Name, termVal); len(idxs) > 1 {
							args := []interface{}{}
							for _, idx := range idxs {
								args = append(args, idx)
							}
							qry.Filter("term_"+term.Meta.Name+".in", args...)
						} else {
							qry.Filter("term_"+term.Meta.Name, termVal)
						}

						c.Data["term_"+term.Meta.Name] = termVal

					case api.TermTag:
						// TOPO
						qry.Filter("term_"+term.Meta.Name+".like", "%"+termVal+"%")
						c.Data["term_"+term.Meta.Name] = termVal
					}
				}
			}

			break
		}

		page := c.Params.Int64("page")
		if page > 1 {
			qry.Offset(ad.Query.Limit * (page - 1))
		}

		if c.Params.Get("qry_text") != "" {
			qry.Filter("field_title.like", "%"+c.Params.Get("qry_text")+"%")
			c.Data["qry_text"] = c.Params.Get("qry_text")
		}

		var ls api.NodeList
		qryhash := qry.Hash()

		if ad.CacheTTL > 0 && (!c.us.IsLogin() || c.us.UserName != config.Config.AppInstance.Meta.User) {
			if rs := store.DataLocal.NewReader([]byte(qryhash)).Query(); rs.OK() {
				rs.Decode(&ls)
			}
		}

		if len(ls.Items) == 0 {

			if c.Params.Get("qry_text") != "" {
				ls = qry.NodeListSearch(c.Params.Get("qry_text"))
				if ls.Error != nil {
					ls = qry.NodeList([]string{}, []string{})
				}
			} else {
				ls = qry.NodeList([]string{}, []string{})
			}
			// fmt.Println("index node.list")
			if ad.CacheTTL > 0 && len(ls.Items) > 0 {
				c.hookPosts = append(
					c.hookPosts,
					func() {
						store.DataLocal.NewWriter([]byte(qryhash), ls).ExpireSet(ad.CacheTTL).Commit()
					},
				)
			}
		}

		c.Data[ad.Name] = ls

		if qry.Pager {
			pager := webui.NewPager(uint64(page),
				uint64(ls.Meta.TotalResults),
				uint64(ls.Meta.ItemsPerList),
				10)
			pager.CurrentPageNumber = uint64(page)
			c.Data[ad.Name+"_pager"] = pager
		}

	case "node.entry":

		nodeId := c.Params.Get(ad.Name + "_id")
		if nodeId == "" {
			nodeId = c.Params.Get("id")
			if nodeId == "" {
				return dataRenderNotFound
			}
		}
		// fmt.Println("node.entry", ad.Name+"_id", nodeId)

		nodeModel, err := config.SpecNodeModel(mod.Meta.Name, ad.Query.Table)
		if err != nil {
			return dataRenderNotFound
		}

		nodeRefer := ""
		if nodeModel.Extensions.NodeRefer != "" {
			if mv, ok := c.Data[action_name+"_nsr_"+nodeModel.Extensions.NodeRefer]; ok {
				nodeRefer = mv.(string)
			}
		}

		nodeExt := ""
		if mod.Meta.Name == "core/gdoc" {
			if ad.Query.Table == "page" {
				nodeId = strings.ToLower(c.Request.UrlPathExtra)
			} else if ad.Query.Table == "doc" && api.NodeIdReg.MatchString(nodeId) {
				nodeExt = "html"
			}
		}
		if i := strings.LastIndex(nodeId, "."); i > 0 {
			nodeExt = nodeId[i+1:]
			nodeId = nodeId[:i]
		}

		if nodeExt == "html" {
			qry.Filter("id", nodeId)
		} else if staticImages.Has(nodeExt) {
			if mod.Meta.Name == "core/gdoc" && ad.Query.Table == "page" {

				pid := datax.GdocNodeId(c.Params.Get("doc_entry_id"))
				if pid != "" {
					// fmt.Println(fmt.Sprintf("%s/var/vcs/%s/%s", config.Prefix, pid, c.Request.UrlPathExtra))
					s2Server(c.Controller, c.Request.UrlPathExtra,
						fmt.Sprintf("%s/var/vcs/%s/%s", config.Prefix, pid, c.Request.UrlPathExtra))
				}
			}
			return dataRenderSkip

		} else if nodeModel.Extensions.Permalink != "" {
			if nodeModel.Extensions.NodeRefer != "" && nodeRefer == "" {
				return dataRenderNotFound
			}
			qry.Filter("ext_permalink_idx", idhash.HashToHexString([]byte(nodeRefer+nodeId), 12))
		} else {
			return dataRenderNotFound
		}

		var entry api.Node
		qryhash := qry.Hash()
		if ad.CacheTTL > 0 && (!c.us.IsLogin() || c.us.UserName != config.Config.AppInstance.Meta.User) {
			if rs := store.DataLocal.NewReader([]byte(qryhash)).Query(); rs.OK() {
				rs.Decode(&entry)
			}
		}

		if entry.ID == "" {
			entry = qry.NodeEntry()
			if ad.CacheTTL > 0 && entry.Title != "" {
				c.hookPosts = append(
					c.hookPosts,
					func() {
						store.DataLocal.NewWriter([]byte(qryhash), entry).ExpireSet(ad.CacheTTL).Commit()
					},
				)
			}
		}

		if entry.ID == "" {
			return dataRenderNotFound
		}

		if nodeModel.Extensions.AccessCounter {

			if ips := strings.Split(c.Request.RemoteAddr, ":"); len(ips) > 1 {

				table := fmt.Sprintf("hpn_%s_%s", idhash.HashToHexString([]byte(mod.Meta.Name), 12), ad.Query.Table)
				store.DataLocal.NewWriter([]byte("access_counter/"+table+"/"+ips[0]+"/"+entry.ID), "1").Commit()
			}
		}

		if nodeModel.Extensions.NodeSubRefer != "" {
			// fmt.Println("setting", action_name, ad.Query.Table, nodeModel.Extensions.NodeSubRefer, "_id", entry.ID)
			c.Data[action_name+"_nsr_"+ad.Query.Table] = entry.ID
		}

		if entry.Title != "" {
			c.Data["__html_head_title__"] = datax.StringSub(datax.TextHtml2Str(entry.Title), 0, 50)
		}

		c.Data[ad.Name] = entry

	case "term.list":

		var ls api.TermList
		qryhash := qry.Hash()
		if ad.CacheTTL > 0 {
			if rs := store.DataLocal.NewReader([]byte(qryhash)).Query(); rs.OK() {
				rs.Decode(&ls)
			}
		}

		if len(ls.Items) == 0 {
			ls = qry.TermList()
			if ad.CacheTTL > 0 && len(ls.Items) > 0 {
				store.DataLocal.NewWriter([]byte(qryhash), ls).ExpireSet(ad.CacheTTL).Commit()
			}
		}

		c.Data[ad.Name] = ls

		if qry.Pager {
			c.Data[ad.Name+"_pager"] = webui.NewPager(0,
				uint64(ls.Meta.TotalResults),
				uint64(ls.Meta.ItemsPerList),
				10)
		}

	case "term.entry":

		var entry api.Term
		qryhash := qry.Hash()

		if ad.CacheTTL > 0 {
			if rs := store.DataLocal.NewReader([]byte(qryhash)).Query(); rs.OK() {
				rs.Decode(&entry)
			}
		}

		if entry.Title == "" {
			entry = qry.TermEntry()
			if ad.CacheTTL > 0 && entry.Title != "" {
				store.DataLocal.NewWriter([]byte(qryhash), entry).ExpireSet(ad.CacheTTL).Commit()
			}
		}

		c.Data[ad.Name] = entry
	}

	return dataRenderOK
}
<|endoftext|>


<commit_before>package serverlog

/*
The MIT License (MIT)
Copyright (c) 2015 Mladen Kajic
See https://github.com/CanOpener/serverlog
*/

import (
	"fmt"
	"github.com/mgutz/ansi"
	"io/ioutil"
	"log"
	"os"
	"path"
	"sort"
	"strings"
	"syscall"
	"time"
)

var (
	logToConsol = false // should log to consol
	logToFile   = false // should log to file
	logDir      = ""    // directory in which to store logfiles
	maxDays     = -1    // total number of logfiles at any time
	// if exeeded will delete oldest

	//log queue
	logChan     = make(chan logItem, 100)
	logNameChan = make(chan string, 2)
	killChan    = make(chan bool, 1)

	//colour functions
	startupColourFunc = ansi.ColorFunc("green+b")
	fatalColourFunc   = ansi.ColorFunc("red+b")
	generalColourFunc = ansi.ColorFunc("blue+b")
	warningColourFunc = ansi.ColorFunc("yellow+b")
)

// The clolour type enumerator
const (
	startupColour int = iota
	fatalColour
	generalColour
	warningColour
)

// Init initialises the srvlog package. if either consoleLog or fileLog
// is true it will start the logger in another gorutine ready to log
func Init(consolLog, fileLog bool, maxLogDays int, pathToLogDir string) {
	logToConsol = consolLog
	logToFile = fileLog
	logDir = pathToLogDir
	maxDays = maxLogDays

	if logToFile {
		// make sure log directory exists
		info, err := os.Stat(logDir)
		if err != nil {
			if os.IsNotExist(err) {
				log.Fatalln("The directory specified to serverlog does not exist.")
			}
			log.Fatalln(err)
		}
		if !info.IsDir() {
			log.Fatalln("The path specified to serverlog is not a directory.")
		}

		// make sure have premissions to log directory
		err = syscall.Access(logDir, syscall.O_RDWR)
		if err != nil {
			log.Fatalln("Serverlog needs read and write premissions to the specified directory.")
		}

		// manage logfile names and number of logfiles at any one time
		// only needed if logging to files
		go logFileOverseer()
	}

	go listen()
}

// Startup used to log the startup message
// example "Startup("Server listening on port:", PORT)"
func Startup(args ...interface{}) {
	logItem := logItem{
		prefix:           "STARTUP:",
		prefixColourFunc: startupColour,
		time:             time.Now(),
		content:          args,
	}
	logChan <- logItem
}

// Fatal is used to log something a server killing circumstance
// same as log.Fatalln()
// This will terminate the process with an exit code of 1
func Fatal(args ...interface{}) {
	logItem := logItem{
		prefix:           "FATAL:  ",
		prefixColourFunc: fatalColour,
		time:             time.Now(),
		content:          args,
	}
	logChan <- logItem
}

// General is used to log general stuff
func General(args ...interface{}) {
	logItem := logItem{
		prefix:           "GENERAL:",
		prefixColourFunc: generalColour,
		time:             time.Now(),
		content:          args,
	}
	logChan <- logItem
}

// Warning is used to log warnings
func Warning(args ...interface{}) {
	logItem := logItem{
		prefix:           "WARNING:",
		prefixColourFunc: warningColour,
		time:             time.Now(),
		content:          args,
	}
	logChan <- logItem
}

// Kill will terminate the listener and logFileOverseer
func Kill() {
	killChan <- false
	logToConsol = false
	logToFile = false
}

// logitem is the struct passed to the logger function
type logItem struct {
	prefix           string
	prefixColourFunc int
	time             time.Time
	content          []interface{}
}

// listen is the listener which runs in its own gorutine and logs messages
func listen() {
	currentLogPath := path.Join(logDir, time.Now().Format("2016-02-27.crsv.log"))

	for {
		select {
		case item := <-logChan:
			writeToConsole(item)
			writeToFile(item, currentLogPath)
			if item.prefixColourFunc == fatalColour {
				os.Exit(1)
			}
		case newLogPath := <-logNameChan:
			currentLogPath = newLogPath
		case <-killChan:
			return
		}
	}
}

// writeToFile logs a message to the logfile
func writeToFile(item logItem, logPath string) {
	if logToFile {
		file, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
		defer file.Close()
		if err != nil {
			log.Fatalln(err)
		}

		line := item.time.Format("15:04:05") + " " + item.prefix + " " + fmt.Sprintln(item.content...)
		_, err = file.WriteString(line)
		if err != nil {
			log.Fatalln(err)
		}
	}
}

// writeToConsole logs a message to the console
func writeToConsole(item logItem) {
	if logToConsol {
		fmt.Print(item.time.Format("15:04:05"), " ", colourInText(item.prefix, item.prefixColourFunc), " ", fmt.Sprintln(item.content...))
	}
}

// logFileOverseer makes sure that there are never more logfiles for each day than
// maxLogDays. also makes sure to update the listener as to new logfile names
// for each day.
func logFileOverseer() {
	for {
		in24Hr := time.Now().AddDate(0, 0, 1) // AddDate is used in case the next day is next month or year
		tomorrow := time.Date(in24Hr.Year(), in24Hr.Month(), in24Hr.Day(), 0, 0, 0, 0, in24Hr.Location())
		timeToWait := tomorrow.Sub(time.Now())
		newDay := time.After(timeToWait)

		select {
		case <-newDay:
			newLogFile := path.Join(logDir, tomorrow.Format("2016-02-27.crsv.log"))

			// create new logfile
			_, err := os.Create(newLogFile)
			if err != nil {
				Warning("Serverlog failed to create new logfile:", newLogFile, ":", err)
				break
			}

			// tell listener new logfile name.
			logNameChan <- newLogFile

			// check number of logfiles
			if maxDays > 0 {
				files, err := ioutil.ReadDir(logDir)
				if err != nil {
					Warning("Server log failed to read from log directory :", err)
					break
				}

				logs := make([]string, 0, maxDays*2)
				index := 0
				for _, file := range files {
					if strings.Contains(file.Name(), "{csrv}") {
						logs[index] = file.Name()
						index++
					}
				}
				sort.Strings(logs)

				numberLogsLeft := len(logs)
				for i := 0; (numberLogsLeft > maxDays) && (i < len(logs)); i++ {
					logToDelete := path.Join(logDir, logs[i])
					err := os.Remove(logToDelete)
					if err != nil {
						Warning("Server log failed to delete logfile :", logToDelete, ":", err)
						break
					}
					numberLogsLeft--
				}
			}
		case <-killChan:
			return
		}
	}
}

// colourInText returns the text coloured in based on the colour func enumerator
func colourInText(text string, colourFunc int) string {
	switch colourFunc {
	case startupColour:
		return startupColourFunc(text)
	case fatalColour:
		return fatalColourFunc(text)
	case generalColour:
		return generalColourFunc(text)
	case warningColour:
		return warningColourFunc(text)
	default:
		return text
	}
}
<commit_msg>Fixed formatting to correspond to go time formatting<commit_after>package serverlog

/*
The MIT License (MIT)
Copyright (c) 2015 Mladen Kajic
See https://github.com/CanOpener/serverlog
*/

import (
	"fmt"
	"github.com/mgutz/ansi"
	"io/ioutil"
	"log"
	"os"
	"path"
	"sort"
	"strings"
	"syscall"
	"time"
)

var (
	logToConsol = false // should log to consol
	logToFile   = false // should log to file
	logDir      = ""    // directory in which to store logfiles
	maxDays     = -1    // total number of logfiles at any time
	// if exeeded will delete oldest

	//log queue
	logChan     = make(chan logItem, 100)
	logNameChan = make(chan string, 2)
	killChan    = make(chan bool, 1)

	//colour functions
	startupColourFunc = ansi.ColorFunc("green+b")
	fatalColourFunc   = ansi.ColorFunc("red+b")
	generalColourFunc = ansi.ColorFunc("blue+b")
	warningColourFunc = ansi.ColorFunc("yellow+b")
)

// The colour type enumerator
const (
	startupColour int = iota
	fatalColour
	generalColour
	warningColour
)

// Init initialises the srvlog package. if either consoleLog or fileLog
// is true it will start the logger in another gorutine ready to log
func Init(consolLog, fileLog bool, maxLogDays int, pathToLogDir string) {
	logToConsol = consolLog
	logToFile = fileLog
	logDir = pathToLogDir
	maxDays = maxLogDays

	if logToFile {
		// make sure log directory exists
		info, err := os.Stat(logDir)
		if err != nil {
			if os.IsNotExist(err) {
				log.Fatalln("The directory specified to serverlog does not exist.")
			}
			log.Fatalln(err)
		}
		if !info.IsDir() {
			log.Fatalln("The path specified to serverlog is not a directory.")
		}

		// make sure have premissions to log directory
		err = syscall.Access(logDir, syscall.O_RDWR)
		if err != nil {
			log.Fatalln("Serverlog needs read and write premissions to the specified directory.")
		}

		// manage logfile names and number of logfiles at any one time
		// only needed if logging to files
		go logFileOverseer()
	}

	go listen()
}

// Startup used to log the startup message
// example "Startup("Server listening on port:", PORT)"
func Startup(args ...interface{}) {
	logItem := logItem{
		prefix:           "STARTUP:",
		prefixColourFunc: startupColour,
		time:             time.Now(),
		content:          args,
	}
	logChan <- logItem
}

// Fatal is used to log something a server killing circumstance
// same as log.Fatalln()
// This will terminate the process with an exit code of 1
func Fatal(args ...interface{}) {
	logItem := logItem{
		prefix:           "FATAL:  ",
		prefixColourFunc: fatalColour,
		time:             time.Now(),
		content:          args,
	}
	logChan <- logItem
}

// General is used to log general stuff
func General(args ...interface{}) {
	logItem := logItem{
		prefix:           "GENERAL:",
		prefixColourFunc: generalColour,
		time:             time.Now(),
		content:          args,
	}
	logChan <- logItem
}

// Warning is used to log warnings
func Warning(args ...interface{}) {
	logItem := logItem{
		prefix:           "WARNING:",
		prefixColourFunc: warningColour,
		time:             time.Now(),
		content:          args,
	}
	logChan <- logItem
}

// Kill will terminate the listener and logFileOverseer
func Kill() {
	killChan <- false
	logToConsol = false
	logToFile = false
}

// logitem is the struct passed to the logger function
type logItem struct {
	prefix           string
	prefixColourFunc int
	time             time.Time
	content          []interface{}
}

// listen is the listener which runs in its own gorutine and logs messages
func listen() {
	currentLogPath := path.Join(logDir, time.Now().Format("2006-01-02.crsv.log"))

	for {
		select {
		case item := <-logChan:
			writeToConsole(item)
			writeToFile(item, currentLogPath)
			if item.prefixColourFunc == fatalColour {
				os.Exit(1)
			}
		case newLogPath := <-logNameChan:
			currentLogPath = newLogPath
		case <-killChan:
			return
		}
	}
}

// writeToFile logs a message to the logfile
func writeToFile(item logItem, logPath string) {
	if logToFile {
		file, err := os.OpenFile(logPath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0666)
		defer file.Close()
		if err != nil {
			log.Fatalln(err)
		}

		line := item.time.Format("15:04:05") + " " + item.prefix + " " + fmt.Sprintln(item.content...)
		_, err = file.WriteString(line)
		if err != nil {
			log.Fatalln(err)
		}
	}
}

// writeToConsole logs a message to the console
func writeToConsole(item logItem) {
	if logToConsol {
		fmt.Print(item.time.Format("15:04:05"), " ", colourInText(item.prefix, item.prefixColourFunc), " ", fmt.Sprintln(item.content...))
	}
}

// logFileOverseer makes sure that there are never more logfiles for each day than
// maxLogDays. also makes sure to update the listener as to new logfile names
// for each day.
func logFileOverseer() {
	for {
		in24Hr := time.Now().AddDate(0, 0, 1) // AddDate is used in case the next day is next month or year
		tomorrow := time.Date(in24Hr.Year(), in24Hr.Month(), in24Hr.Day(), 0, 0, 0, 0, in24Hr.Location())
		timeToWait := tomorrow.Sub(time.Now())
		newDay := time.After(timeToWait)

		select {
		case <-newDay:
			newLogFile := path.Join(logDir, tomorrow.Format("2006-01-02.crsv.log"))

			// create new logfile
			_, err := os.Create(newLogFile)
			if err != nil {
				Warning("Serverlog failed to create new logfile:", newLogFile, ":", err)
				break
			}

			// tell listener new logfile name.
			logNameChan <- newLogFile

			// check number of logfiles
			if maxDays > 0 {
				files, err := ioutil.ReadDir(logDir)
				if err != nil {
					Warning("Server log failed to read from log directory :", err)
					break
				}

				logs := make([]string, 0, maxDays*2)
				index := 0
				for _, file := range files {
					if strings.Contains(file.Name(), "{csrv}") {
						logs[index] = file.Name()
						index++
					}
				}
				sort.Strings(logs)

				numberLogsLeft := len(logs)
				for i := 0; (numberLogsLeft > maxDays) && (i < len(logs)); i++ {
					logToDelete := path.Join(logDir, logs[i])
					err := os.Remove(logToDelete)
					if err != nil {
						Warning("Server log failed to delete logfile :", logToDelete, ":", err)
						break
					}
					numberLogsLeft--
				}
			}
		case <-killChan:
			return
		}
	}
}

// colourInText returns the text coloured in based on the colour func enumerator
func colourInText(text string, colourFunc int) string {
	switch colourFunc {
	case startupColour:
		return startupColourFunc(text)
	case fatalColour:
		return fatalColourFunc(text)
	case generalColour:
		return generalColourFunc(text)
	case warningColour:
		return warningColourFunc(text)
	default:
		return text
	}
}
<|endoftext|>