<commit_before>package game

import (
	"errors"
	"strconv"
	"strings"

	"github.com/ghthor/engine/net/protocol"
	"github.com/ghthor/engine/rpg2d"
	"github.com/ghthor/engine/rpg2d/coord"
	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/sim/stime"
)

type actorCmd struct {
	timeIssued stime.Time
	cmd        string
	params     string
}

type moveRequest struct {
	stime.Time
	coord.Direction
}

type actorCmdRequest struct {
	*moveRequest
}

type actorConn struct {
	// Comm interface to muxer used by WriteInput() method
	submitCmd chan<- actorCmd

	// Comm interface to muxer used by getEntity() method
	readCmdReq <-chan actorCmdRequest

	// Comm interface to muxer used by SendState() method
	sendState chan<- *rpg2d.WorldState

	// Comm interface to muxer used by stopIO() method
	stop chan<- chan<- struct{}

	// External connection used to publish the world state
	protocol.Conn

	lastState rpg2d.WorldState
}

func newActorConn(conn protocol.Conn) actorConn {
	return actorConn{Conn: conn}
}

// Object stored in the quad tree
type actorEntity struct {
	id int64

	name string

	cell   coord.Cell
	facing coord.Direction
	speed  int

	pathAction     *coord.PathAction
	lastMoveAction coord.MoveAction
}

type actorEntityState struct {
	EntityId int64 `json:"id"`

	Name string `json:"name"`

	Facing string     `json:"facing"`
	Cell   coord.Cell `json:"cell"`
	bounds coord.Bounds

	PathAction *coord.PathActionJson `json:"pathAction"`
}

type actor struct {
	actorCmdRequest
	actorEntity
	undoLastMoveAction func()

	actorConn
}

func (a *actor) Entity() entity.Entity { return a.actorEntity }
func (e actorEntity) Id() int64        { return e.id }
func (e actorEntity) Cell() coord.Cell { return e.cell }
func (e actorEntity) Bounds() coord.Bounds {
	bounds := coord.Bounds{
		e.cell,
		e.cell,
	}

	if e.pathAction != nil {
		bounds = coord.JoinBounds(bounds, e.pathAction.Bounds())
	}

	return bounds
}

func (e actorEntity) ToState() entity.State {
	var pathAction *coord.PathActionJson

	if e.pathAction != nil {
		pa := e.pathAction.Json()
		pathAction = &pa
	}

	return actorEntityState{
		EntityId: e.id,

		Name: e.name,

		Cell:   e.cell,
		Facing: e.facing.String(),

		bounds: e.Bounds(),

		PathAction: pathAction,
	}
}

func (e actorEntityState) Id() int64            { return e.EntityId }
func (e actorEntityState) Bounds() coord.Bounds { return e.bounds }
func (e actorEntityState) IsDifferentFrom(other entity.State) (different bool) {
	o := other.(actorEntityState)

	switch {
	case e.Facing != o.Facing:
		return true
	case e.PathAction != nil && o.PathAction != nil:
		if *e.PathAction != *o.PathAction {
			return true
		}
	case e.bounds != o.bounds:
		return true
	}

	return false
}

func (a *actor) applyPathAction(pa *coord.PathAction) {
	prevPathAction := a.pathAction
	prevFacing := a.facing
	prevMoveRequest := a.actorCmdRequest.moveRequest

	a.undoLastMoveAction = func() {
		a.pathAction = prevPathAction
		a.facing = prevFacing
		a.actorCmdRequest.moveRequest = prevMoveRequest
		a.undoLastMoveAction = nil
	}

	a.pathAction = pa
	a.facing = pa.Direction()
	a.actorCmdRequest.moveRequest = nil
}

func (a *actor) applyTurnAction(ta coord.TurnAction) {
	prevAction := a.lastMoveAction
	prevFacing := a.facing
	prevMoveRequest := a.actorCmdRequest.moveRequest

	a.undoLastMoveAction = func() {
		a.lastMoveAction = prevAction
		a.facing = prevFacing
		a.actorCmdRequest.moveRequest = prevMoveRequest
		a.undoLastMoveAction = nil
	}

	a.lastMoveAction = ta
	a.facing = ta.To
	a.actorCmdRequest.moveRequest = nil
}

func (a *actor) revertMoveAction() {
	if a.undoLastMoveAction != nil {
		a.undoLastMoveAction()
	}
}

func (a *actorConn) startIO() {
	// Setup communication channels
	cmdCh := make(chan actorCmd)
	cmdReqCh := make(chan actorCmdRequest)
	outputCh := make(chan *rpg2d.WorldState)
	stopCh := make(chan chan<- struct{})

	// Set the channels accessible to the outside world
	a.submitCmd = cmdCh
	a.readCmdReq = cmdReqCh
	a.sendState = outputCh
	a.stop = stopCh

	// Establish the channel endpoints used inside the go routine
	var newCmd <-chan actorCmd
	var sendCmdReq chan<- actorCmdRequest
	var newState <-chan *rpg2d.WorldState
	var stopReq <-chan chan<- struct{}

	newCmd = cmdCh
	sendCmdReq = cmdReqCh
	newState = outputCh
	stopReq = stopCh

	go func() {
		var hasStopped chan<- struct{}

		// Buffer of 1 used to store the most recently
		// received actor cmd from the network.
		var cmdReq actorCmdRequest

		updateCmdReqWith := func(c actorCmd) {
			switch c.cmd {
			case "move":
				// TODO This is a shit place to be having an error to deal with
				// TODO It needs to be dealt with at the packet handler level
				d, _ := coord.NewDirectionWithString(c.params)

				cmdReq.moveRequest = &moveRequest{
					Time:      stime.Time(c.timeIssued),
					Direction: d,
				}
			case "moveCancel":
				d, _ := coord.NewDirectionWithString(c.params)
				if cmdReq.moveRequest != nil {
					if cmdReq.moveRequest.Direction == d {
						cmdReq.moveRequest = nil
					}
				}
			}
		}

	unlocked:
		// # This select prioritizes the following events.
		// ## 2 potential events to respond to
		// 1. ReadCmdRequest() method requests the actor command request
		// 2. stopIO() method has been called
		select {
		case sendCmdReq <- cmdReq:
			// Transition: unlocked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: unlocked -> exit
			goto exit
		default:
		}

		// ## 3 potential events to respond to
		// 1. SubmitInput() method has been called with a new command
		// 2. ReadCmdRequest() method requests the actor command request
		// 3. stopIO() method has been called
		select {
		case c := <-newCmd:
			updateCmdReqWith(c)

			// Transition: unlocked -> unlocked
			goto unlocked

		case sendCmdReq <- cmdReq:
			// Transition: unlocked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: unlocked -> exit
			goto exit
		}

		panic("unclosed case in unlocked state select")

	locked:
		// Accepting and processing input commands is now on hold

		// ## 3 potential events to respond to
		// 1. WriteState() method has been called with a new world state
		// 2. ReadCmdRequest() method requests the actor command request.. again!
		// 3. stopIO() method has been called
		select {
		case state := <-newState:
			if state != nil {
				a.SendJson("update", state)
			}

			// Transition: locked -> unlocked
			goto unlocked

		case sendCmdReq <- cmdReq:
			// Transition: locked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: locked -> exit
			goto exit
		}

		panic("unclosed case in locked state select")
	exit:
		hasStopped <- struct{}{}
	}()
}

func (c actorConn) SubmitCmd(cmd, params string) error {
	parts := strings.Split(cmd, "=")

	if len(parts) != 2 {
		return errors.New("invalid command")
	}

	timeIssued, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return err
	}

	c.submitCmd <- actorCmd{
		timeIssued: stime.Time(timeIssued),
		cmd:        parts[0],
		params:     params,
	}

	return nil
}

func (c actorConn) ReadCmdRequest() actorCmdRequest {
	return <-c.readCmdReq
}

// Culls the world state to the actor's viewport.
// Is called before actorConn.WriteState()
func (a *actor) WriteState(state rpg2d.WorldState) {
	c := a.Cell()

	state = state.Cull(coord.Bounds{
		c.Add(-26, 26),
		c.Add(26, -26),
	})

	a.actorConn.WriteState(state)
}

// Diffs the world state so only the changes are sent.
// Is called after actor.WriteState(). Expects the state
// to have been culled already.
func (a *actorConn) WriteState(state rpg2d.WorldState) {
	diff := a.lastState.Diff(state)
	a.lastState = state

	// Will need this when I start comparing for terrain type changes
	// a.lastState.Prepare()

	if len(diff.Entities) > 0 || len(diff.Removed) > 0 || diff.TerrainMap != nil {
		diff.Prepare()
		a.sendState <- &diff
	} else {
		a.sendState <- nil
	}
}

func (a actorConn) stopIO() {
	hasStopped := make(chan struct{})

	a.stop <- hasStopped
	<-hasStopped
}
<commit_msg>[game] improve string output for actor entity<commit_after>package game

import (
	"errors"
	"fmt"
	"strconv"
	"strings"

	"github.com/ghthor/engine/net/protocol"
	"github.com/ghthor/engine/rpg2d"
	"github.com/ghthor/engine/rpg2d/coord"
	"github.com/ghthor/engine/rpg2d/entity"
	"github.com/ghthor/engine/sim/stime"
)

type actorCmd struct {
	timeIssued stime.Time
	cmd        string
	params     string
}

type moveRequest struct {
	stime.Time
	coord.Direction
}

type actorCmdRequest struct {
	*moveRequest
}

type actorConn struct {
	// Comm interface to muxer used by WriteInput() method
	submitCmd chan<- actorCmd

	// Comm interface to muxer used by getEntity() method
	readCmdReq <-chan actorCmdRequest

	// Comm interface to muxer used by SendState() method
	sendState chan<- *rpg2d.WorldState

	// Comm interface to muxer used by stopIO() method
	stop chan<- chan<- struct{}

	// External connection used to publish the world state
	protocol.Conn

	lastState rpg2d.WorldState
}

func newActorConn(conn protocol.Conn) actorConn {
	return actorConn{Conn: conn}
}

// Object stored in the quad tree
type actorEntity struct {
	id int64

	name string

	cell   coord.Cell
	facing coord.Direction
	speed  int

	pathAction     *coord.PathAction
	lastMoveAction coord.MoveAction
}

type actorEntityState struct {
	EntityId int64 `json:"id"`

	Name string `json:"name"`

	Facing string     `json:"facing"`
	Cell   coord.Cell `json:"cell"`
	bounds coord.Bounds

	PathAction *coord.PathActionJson `json:"pathAction"`
}

type actor struct {
	actorCmdRequest
	actorEntity
	undoLastMoveAction func()

	actorConn
}

func (a *actor) Entity() entity.Entity { return a.actorEntity }
func (e actorEntity) Id() int64        { return e.id }
func (e actorEntity) Cell() coord.Cell { return e.cell }
func (e actorEntity) Bounds() coord.Bounds {
	bounds := coord.Bounds{
		e.cell,
		e.cell,
	}

	if e.pathAction != nil {
		bounds = coord.JoinBounds(bounds, e.pathAction.Bounds())
	}

	return bounds
}

func (e actorEntity) ToState() entity.State {
	var pathAction *coord.PathActionJson

	if e.pathAction != nil {
		pa := e.pathAction.Json()
		pathAction = &pa
	}

	return actorEntityState{
		EntityId: e.id,

		Name: e.name,

		Cell:   e.cell,
		Facing: e.facing.String(),

		bounds: e.Bounds(),

		PathAction: pathAction,
	}
}

func (e actorEntity) String() string {
	return fmt.Sprintf("{id %d, cell%v, %v, speed:%d, pathAction:%v}", e.id, e.cell, e.facing, e.speed, e.pathAction)
}

func (e actorEntityState) Id() int64            { return e.EntityId }
func (e actorEntityState) Bounds() coord.Bounds { return e.bounds }
func (e actorEntityState) IsDifferentFrom(other entity.State) (different bool) {
	o := other.(actorEntityState)

	switch {
	case e.Facing != o.Facing:
		return true
	case e.PathAction != nil && o.PathAction != nil:
		if *e.PathAction != *o.PathAction {
			return true
		}
	case e.bounds != o.bounds:
		return true
	}

	return false
}

func (a *actor) applyPathAction(pa *coord.PathAction) {
	prevPathAction := a.pathAction
	prevFacing := a.facing
	prevMoveRequest := a.actorCmdRequest.moveRequest

	a.undoLastMoveAction = func() {
		a.pathAction = prevPathAction
		a.facing = prevFacing
		a.actorCmdRequest.moveRequest = prevMoveRequest
		a.undoLastMoveAction = nil
	}

	a.pathAction = pa
	a.facing = pa.Direction()
	a.actorCmdRequest.moveRequest = nil
}

func (a *actor) applyTurnAction(ta coord.TurnAction) {
	prevAction := a.lastMoveAction
	prevFacing := a.facing
	prevMoveRequest := a.actorCmdRequest.moveRequest

	a.undoLastMoveAction = func() {
		a.lastMoveAction = prevAction
		a.facing = prevFacing
		a.actorCmdRequest.moveRequest = prevMoveRequest
		a.undoLastMoveAction = nil
	}

	a.lastMoveAction = ta
	a.facing = ta.To
	a.actorCmdRequest.moveRequest = nil
}

func (a *actor) revertMoveAction() {
	if a.undoLastMoveAction != nil {
		a.undoLastMoveAction()
	}
}

func (a *actorConn) startIO() {
	// Setup communication channels
	cmdCh := make(chan actorCmd)
	cmdReqCh := make(chan actorCmdRequest)
	outputCh := make(chan *rpg2d.WorldState)
	stopCh := make(chan chan<- struct{})

	// Set the channels accessible to the outside world
	a.submitCmd = cmdCh
	a.readCmdReq = cmdReqCh
	a.sendState = outputCh
	a.stop = stopCh

	// Establish the channel endpoints used inside the go routine
	var newCmd <-chan actorCmd
	var sendCmdReq chan<- actorCmdRequest
	var newState <-chan *rpg2d.WorldState
	var stopReq <-chan chan<- struct{}

	newCmd = cmdCh
	sendCmdReq = cmdReqCh
	newState = outputCh
	stopReq = stopCh

	go func() {
		var hasStopped chan<- struct{}

		// Buffer of 1 used to store the most recently
		// received actor cmd from the network.
		var cmdReq actorCmdRequest

		updateCmdReqWith := func(c actorCmd) {
			switch c.cmd {
			case "move":
				// TODO This is a shit place to be having an error to deal with
				// TODO It needs to be dealt with at the packet handler level
				d, _ := coord.NewDirectionWithString(c.params)

				cmdReq.moveRequest = &moveRequest{
					Time:      stime.Time(c.timeIssued),
					Direction: d,
				}
			case "moveCancel":
				d, _ := coord.NewDirectionWithString(c.params)
				if cmdReq.moveRequest != nil {
					if cmdReq.moveRequest.Direction == d {
						cmdReq.moveRequest = nil
					}
				}
			}
		}

	unlocked:
		// # This select prioritizes the following events.
		// ## 2 potential events to respond to
		// 1. ReadCmdRequest() method requests the actor command request
		// 2. stopIO() method has been called
		select {
		case sendCmdReq <- cmdReq:
			// Transition: unlocked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: unlocked -> exit
			goto exit
		default:
		}

		// ## 3 potential events to respond to
		// 1. SubmitInput() method has been called with a new command
		// 2. ReadCmdRequest() method requests the actor command request
		// 3. stopIO() method has been called
		select {
		case c := <-newCmd:
			updateCmdReqWith(c)

			// Transition: unlocked -> unlocked
			goto unlocked

		case sendCmdReq <- cmdReq:
			// Transition: unlocked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: unlocked -> exit
			goto exit
		}

		panic("unclosed case in unlocked state select")

	locked:
		// Accepting and processing input commands is now on hold

		// ## 3 potential events to respond to
		// 1. WriteState() method has been called with a new world state
		// 2. ReadCmdRequest() method requests the actor command request.. again!
		// 3. stopIO() method has been called
		select {
		case state := <-newState:
			if state != nil {
				a.SendJson("update", state)
			}

			// Transition: locked -> unlocked
			goto unlocked

		case sendCmdReq <- cmdReq:
			// Transition: locked -> locked
			goto locked

		case hasStopped = <-stopReq:
			// Transition: locked -> exit
			goto exit
		}

		panic("unclosed case in locked state select")
	exit:
		hasStopped <- struct{}{}
	}()
}

func (c actorConn) SubmitCmd(cmd, params string) error {
	parts := strings.Split(cmd, "=")

	if len(parts) != 2 {
		return errors.New("invalid command")
	}

	timeIssued, err := strconv.ParseInt(parts[1], 10, 64)
	if err != nil {
		return err
	}

	c.submitCmd <- actorCmd{
		timeIssued: stime.Time(timeIssued),
		cmd:        parts[0],
		params:     params,
	}

	return nil
}

func (c actorConn) ReadCmdRequest() actorCmdRequest {
	return <-c.readCmdReq
}

// Culls the world state to the actor's viewport.
// Is called before actorConn.WriteState()
func (a *actor) WriteState(state rpg2d.WorldState) {
	c := a.Cell()

	state = state.Cull(coord.Bounds{
		c.Add(-26, 26),
		c.Add(26, -26),
	})

	a.actorConn.WriteState(state)
}

// Diffs the world state so only the changes are sent.
// Is called after actor.WriteState(). Expects the state
// to have been culled already.
func (a *actorConn) WriteState(state rpg2d.WorldState) {
	diff := a.lastState.Diff(state)
	a.lastState = state

	// Will need this when I start comparing for terrain type changes
	// a.lastState.Prepare()

	if len(diff.Entities) > 0 || len(diff.Removed) > 0 || diff.TerrainMap != nil {
		diff.Prepare()
		a.sendState <- &diff
	} else {
		a.sendState <- nil
	}
}

func (a actorConn) stopIO() {
	hasStopped := make(chan struct{})

	a.stop <- hasStopped
	<-hasStopped
}
<|endoftext|>


<commit_before>// Copyright 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.

package gosrc

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

func init() {
	addService(&service{
		pattern:         regexp.MustCompile(`^github\.com/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/.*)?$`),
		prefix:          "github.com/",
		get:             getGitHubDir,
		getPresentation: getGitHubPresentation,
		getProject:      getGitHubProject,
	})

	addService(&service{
		pattern: regexp.MustCompile(`^gist\.github\.com/(?P<gist>[a-z0-9A-Z_.\-]+)\.git$`),
		prefix:  "gist.github.com/",
		get:     getGistDir,
	})
}

var (
	gitHubRawHeader     = http.Header{"Accept": {"application/vnd.github-blob.raw"}}
	gitHubPreviewHeader = http.Header{"Accept": {"application/vnd.github.preview"}}
	ownerRepoPat        = regexp.MustCompile(`^https://api.github.com/repos/([^/]+)/([^/]+)/`)
)

type githubCommit struct {
	ID     string `json:"sha"`
	Commit struct {
		Committer struct {
			Date time.Time `json:"date"`
		} `json:"committer"`
	} `json:"commit"`
}

func gitHubError(resp *http.Response) error {
	var e struct {
		Message string `json:"message"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&e); err == nil {
		return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: %s (%s)", resp.StatusCode, e.Message, resp.Request.URL.String())}
	}
	return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: (%s)", resp.StatusCode, resp.Request.URL.String())}
}

func getGitHubDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {

	c := &httpClient{client: client, errFn: gitHubError}

	var repo struct {
		Fork          bool      `json:"fork"`
		Stars         int       `json:"stargazers_count"`
		CreatedAt     time.Time `json:"created_at"`
		PushedAt      time.Time `json:"pushed_at"`
		DefaultBranch string    `json:"default_branch"`
	}

	if _, err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}", match), &repo); err != nil {
		return nil, err
	}

	status := Active
	var commits []*githubCommit
	u := expand("https://api.github.com/repos/{owner}/{repo}/commits", match)
	if match["dir"] != "" {
		u += fmt.Sprintf("?path=%s", url.QueryEscape(match["dir"]))
	}
	if _, err := c.getJSON(u, &commits); err != nil {
		return nil, err
	}
	if len(commits) == 0 {
		return nil, NotFoundError{Message: "package directory changed or removed"}
	}

	lastCommitted := commits[0].Commit.Committer.Date
	if lastCommitted.Add(ExpiresAfter).Before(time.Now()) {
		status = NoRecentCommits
	} else if repo.Fork {
		if repo.PushedAt.Before(repo.CreatedAt) {
			status = DeadEndFork
		} else if isQuickFork(commits, repo.CreatedAt) {
			status = QuickFork
		}
	}
	if commits[0].ID == savedEtag {
		return nil, NotModifiedError{
			Since:  lastCommitted,
			Status: status,
		}
	}

	var contents []*struct {
		Type    string
		Name    string
		GitURL  string `json:"git_url"`
		HTMLURL string `json:"html_url"`
	}

	if _, err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}", match), &contents); err != nil {
		// The GitHub content API returns array values for directories
		// and object values for files. If there's a type mismatch at
		// the beginning of the response, then assume that the path is
		// for a file.
		if e, ok := err.(*json.UnmarshalTypeError); ok && e.Offset == 1 {
			return nil, NotFoundError{Message: "Not a directory"}
		}
		return nil, err
	}

	if len(contents) == 0 {
		return nil, NotFoundError{Message: "No files in directory."}
	}

	// GitHub owner and repo names are case-insensitive. Redirect if requested
	// names do not match the canonical names in API response.
	if m := ownerRepoPat.FindStringSubmatch(contents[0].GitURL); m != nil && (m[1] != match["owner"] || m[2] != match["repo"]) {
		match["owner"] = m[1]
		match["repo"] = m[2]
		return nil, NotFoundError{
			Message:  "Github import path has incorrect case.",
			Redirect: expand("github.com/{owner}/{repo}{dir}", match),
		}
	}

	var files []*File
	var dataURLs []string
	var subdirs []string

	for _, item := range contents {
		switch {
		case item.Type == "dir":
			if isValidPathElement(item.Name) {
				subdirs = append(subdirs, item.Name)
			}
		case isDocFile(item.Name):
			files = append(files, &File{Name: item.Name, BrowseURL: item.HTMLURL})
			dataURLs = append(dataURLs, item.GitURL)
		}
	}

	c.header = gitHubRawHeader
	if err := c.getFiles(dataURLs, files); err != nil {
		return nil, err
	}

	browseURL := expand("https://github.com/{owner}/{repo}", match)
	if match["dir"] != "" {
		match["tag"] = repo.DefaultBranch // TODO: This doesn't respect "go1" tag/branch special case.
		browseURL = expand("https://github.com/{owner}/{repo}/tree/{tag}{dir}", match)
	}

	return &Directory{
		BrowseURL:      browseURL,
		Etag:           commits[0].ID,
		Files:          files,
		LineFmt:        "%s#L%d",
		ProjectName:    match["repo"],
		ProjectRoot:    expand("github.com/{owner}/{repo}", match),
		ProjectURL:     expand("https://github.com/{owner}/{repo}", match),
		Subdirectories: subdirs,
		VCS:            "git",
		Status:         status,
		Fork:           repo.Fork,
		Stars:          repo.Stars,
	}, nil
}

// isQuickFork reports whether the repository is a "quick fork":
// it has fewer than 3 commits, all within a week of the repo creation, createdAt.
func isQuickFork(commits []*githubCommit, createdAt time.Time) bool {
	oneWeekOld := createdAt.Add(7 * 24 * time.Hour)
	if oneWeekOld.After(time.Now()) {
		return false // a newborn baby of a repository
	}
	n := 0
	for _, commit := range commits {
		if commit.Commit.Committer.Date.After(oneWeekOld) {
			return false
		}
		n++
	}
	return n < 3
}

func getGitHubPresentation(client *http.Client, match map[string]string) (*Presentation, error) {
	c := &httpClient{client: client, header: gitHubRawHeader}

	p, err := c.getBytes(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/{file}", match))
	if err != nil {
		return nil, err
	}

	apiBase, err := url.Parse(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/", match))
	if err != nil {
		return nil, err
	}
	rawBase, err := url.Parse(expand("https://raw.github.com/{owner}/{repo}/master{dir}/", match))
	if err != nil {
		return nil, err
	}

	c.header = gitHubRawHeader

	b := &presBuilder{
		data:     p,
		filename: match["file"],
		fetch: func(fnames []string) ([]*File, error) {
			var files []*File
			var dataURLs []string
			for _, fname := range fnames {
				u, err := apiBase.Parse(fname)
				if err != nil {
					return nil, err
				}
				u.RawQuery = apiBase.RawQuery
				files = append(files, &File{Name: fname})
				dataURLs = append(dataURLs, u.String())
			}
			err := c.getFiles(dataURLs, files)
			return files, err
		},
		resolveURL: func(fname string) string {
			u, err := rawBase.Parse(fname)
			if err != nil {
				return "/notfound"
			}
			if strings.HasSuffix(fname, ".svg") {
				u.Host = "rawgithub.com"
			}
			return u.String()
		},
	}

	return b.build()
}

// GetGitHubUpdates returns the full names ("owner/repo") of recently pushed GitHub repositories.
// by pushedAfter.
func GetGitHubUpdates(client *http.Client, pushedAfter string) (maxPushedAt string, names []string, err error) {
	c := httpClient{client: client, header: gitHubPreviewHeader}

	if pushedAfter == "" {
		pushedAfter = time.Now().Add(-24 * time.Hour).UTC().Format("2006-01-02T15:04:05Z")
	}
	u := "https://api.github.com/search/repositories?order=asc&sort=updated&q=fork:true+language:Go+pushed:>" + pushedAfter
	var updates struct {
		Items []struct {
			FullName string `json:"full_name"`
			PushedAt string `json:"pushed_at"`
		}
	}
	_, err = c.getJSON(u, &updates)
	if err != nil {
		return pushedAfter, nil, err
	}

	maxPushedAt = pushedAfter
	for _, item := range updates.Items {
		names = append(names, item.FullName)
		if item.PushedAt > maxPushedAt {
			maxPushedAt = item.PushedAt
		}
	}
	return maxPushedAt, names, nil
}

func getGitHubProject(client *http.Client, match map[string]string) (*Project, error) {
	c := &httpClient{client: client, errFn: gitHubError}

	var repo struct {
		Description string
	}

	if _, err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}", match), &repo); err != nil {
		return nil, err
	}

	return &Project{
		Description: repo.Description,
	}, nil
}

func getGistDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
	c := &httpClient{client: client, errFn: gitHubError}

	var gist struct {
		Files map[string]struct {
			Content string
		}
		HTMLURL string `json:"html_url"`
		History []struct {
			Version string
		}
	}

	if _, err := c.getJSON(expand("https://api.github.com/gists/{gist}", match), &gist); err != nil {
		return nil, err
	}

	if len(gist.History) == 0 {
		return nil, NotFoundError{Message: "History not found."}
	}
	commit := gist.History[0].Version

	if commit == savedEtag {
		return nil, NotModifiedError{}
	}

	var files []*File

	for name, file := range gist.Files {
		if isDocFile(name) {
			files = append(files, &File{
				Name:      name,
				Data:      []byte(file.Content),
				BrowseURL: gist.HTMLURL + "#file-" + strings.Replace(name, ".", "-", -1),
			})
		}
	}

	return &Directory{
		BrowseURL:      gist.HTMLURL,
		Etag:           commit,
		Files:          files,
		LineFmt:        "%s-L%d",
		ProjectName:    match["gist"],
		ProjectRoot:    expand("gist.github.com/{gist}.git", match),
		ProjectURL:     gist.HTMLURL,
		Subdirectories: nil,
		VCS:            "git",
	}, nil
}
<commit_msg>gosrc: only count commits for a forked repo after created<commit_after>// Copyright 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.

package gosrc

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

func init() {
	addService(&service{
		pattern:         regexp.MustCompile(`^github\.com/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/.*)?$`),
		prefix:          "github.com/",
		get:             getGitHubDir,
		getPresentation: getGitHubPresentation,
		getProject:      getGitHubProject,
	})

	addService(&service{
		pattern: regexp.MustCompile(`^gist\.github\.com/(?P<gist>[a-z0-9A-Z_.\-]+)\.git$`),
		prefix:  "gist.github.com/",
		get:     getGistDir,
	})
}

var (
	gitHubRawHeader     = http.Header{"Accept": {"application/vnd.github-blob.raw"}}
	gitHubPreviewHeader = http.Header{"Accept": {"application/vnd.github.preview"}}
	ownerRepoPat        = regexp.MustCompile(`^https://api.github.com/repos/([^/]+)/([^/]+)/`)
)

type githubCommit struct {
	ID     string `json:"sha"`
	Commit struct {
		Committer struct {
			Date time.Time `json:"date"`
		} `json:"committer"`
	} `json:"commit"`
}

func gitHubError(resp *http.Response) error {
	var e struct {
		Message string `json:"message"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&e); err == nil {
		return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: %s (%s)", resp.StatusCode, e.Message, resp.Request.URL.String())}
	}
	return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: (%s)", resp.StatusCode, resp.Request.URL.String())}
}

func getGitHubDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {

	c := &httpClient{client: client, errFn: gitHubError}

	var repo struct {
		Fork          bool      `json:"fork"`
		Stars         int       `json:"stargazers_count"`
		CreatedAt     time.Time `json:"created_at"`
		PushedAt      time.Time `json:"pushed_at"`
		DefaultBranch string    `json:"default_branch"`
	}

	if _, err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}", match), &repo); err != nil {
		return nil, err
	}

	status := Active
	var commits []*githubCommit
	u := expand("https://api.github.com/repos/{owner}/{repo}/commits", match)
	if match["dir"] != "" {
		u += fmt.Sprintf("?path=%s", url.QueryEscape(match["dir"]))
	}
	if _, err := c.getJSON(u, &commits); err != nil {
		return nil, err
	}
	if len(commits) == 0 {
		return nil, NotFoundError{Message: "package directory changed or removed"}
	}

	lastCommitted := commits[0].Commit.Committer.Date
	if lastCommitted.Add(ExpiresAfter).Before(time.Now()) {
		status = NoRecentCommits
	} else if repo.Fork {
		if repo.PushedAt.Before(repo.CreatedAt) {
			status = DeadEndFork
		} else if isQuickFork(commits, repo.CreatedAt) {
			status = QuickFork
		}
	}
	if commits[0].ID == savedEtag {
		return nil, NotModifiedError{
			Since:  lastCommitted,
			Status: status,
		}
	}

	var contents []*struct {
		Type    string
		Name    string
		GitURL  string `json:"git_url"`
		HTMLURL string `json:"html_url"`
	}

	if _, err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}", match), &contents); err != nil {
		// The GitHub content API returns array values for directories
		// and object values for files. If there's a type mismatch at
		// the beginning of the response, then assume that the path is
		// for a file.
		if e, ok := err.(*json.UnmarshalTypeError); ok && e.Offset == 1 {
			return nil, NotFoundError{Message: "Not a directory"}
		}
		return nil, err
	}

	if len(contents) == 0 {
		return nil, NotFoundError{Message: "No files in directory."}
	}

	// GitHub owner and repo names are case-insensitive. Redirect if requested
	// names do not match the canonical names in API response.
	if m := ownerRepoPat.FindStringSubmatch(contents[0].GitURL); m != nil && (m[1] != match["owner"] || m[2] != match["repo"]) {
		match["owner"] = m[1]
		match["repo"] = m[2]
		return nil, NotFoundError{
			Message:  "Github import path has incorrect case.",
			Redirect: expand("github.com/{owner}/{repo}{dir}", match),
		}
	}

	var files []*File
	var dataURLs []string
	var subdirs []string

	for _, item := range contents {
		switch {
		case item.Type == "dir":
			if isValidPathElement(item.Name) {
				subdirs = append(subdirs, item.Name)
			}
		case isDocFile(item.Name):
			files = append(files, &File{Name: item.Name, BrowseURL: item.HTMLURL})
			dataURLs = append(dataURLs, item.GitURL)
		}
	}

	c.header = gitHubRawHeader
	if err := c.getFiles(dataURLs, files); err != nil {
		return nil, err
	}

	browseURL := expand("https://github.com/{owner}/{repo}", match)
	if match["dir"] != "" {
		match["tag"] = repo.DefaultBranch // TODO: This doesn't respect "go1" tag/branch special case.
		browseURL = expand("https://github.com/{owner}/{repo}/tree/{tag}{dir}", match)
	}

	return &Directory{
		BrowseURL:      browseURL,
		Etag:           commits[0].ID,
		Files:          files,
		LineFmt:        "%s#L%d",
		ProjectName:    match["repo"],
		ProjectRoot:    expand("github.com/{owner}/{repo}", match),
		ProjectURL:     expand("https://github.com/{owner}/{repo}", match),
		Subdirectories: subdirs,
		VCS:            "git",
		Status:         status,
		Fork:           repo.Fork,
		Stars:          repo.Stars,
	}, nil
}

// isQuickFork reports whether the repository is a "quick fork":
// it has fewer than 3 commits, all within a week of the repo creation, createdAt.
// Commits must be in reverse chronological order by Commit.Committer.Date.
func isQuickFork(commits []*githubCommit, createdAt time.Time) bool {
	oneWeekOld := createdAt.Add(7 * 24 * time.Hour)
	if oneWeekOld.After(time.Now()) {
		return false // a newborn baby of a repository
	}
	n := 0
	for _, commit := range commits {
		if commit.Commit.Committer.Date.After(oneWeekOld) {
			return false
		}
		if commit.Commit.Committer.Date.Before(createdAt) {
			break
		}
		n++
	}
	return n < 3
}

func getGitHubPresentation(client *http.Client, match map[string]string) (*Presentation, error) {
	c := &httpClient{client: client, header: gitHubRawHeader}

	p, err := c.getBytes(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/{file}", match))
	if err != nil {
		return nil, err
	}

	apiBase, err := url.Parse(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/", match))
	if err != nil {
		return nil, err
	}
	rawBase, err := url.Parse(expand("https://raw.github.com/{owner}/{repo}/master{dir}/", match))
	if err != nil {
		return nil, err
	}

	c.header = gitHubRawHeader

	b := &presBuilder{
		data:     p,
		filename: match["file"],
		fetch: func(fnames []string) ([]*File, error) {
			var files []*File
			var dataURLs []string
			for _, fname := range fnames {
				u, err := apiBase.Parse(fname)
				if err != nil {
					return nil, err
				}
				u.RawQuery = apiBase.RawQuery
				files = append(files, &File{Name: fname})
				dataURLs = append(dataURLs, u.String())
			}
			err := c.getFiles(dataURLs, files)
			return files, err
		},
		resolveURL: func(fname string) string {
			u, err := rawBase.Parse(fname)
			if err != nil {
				return "/notfound"
			}
			if strings.HasSuffix(fname, ".svg") {
				u.Host = "rawgithub.com"
			}
			return u.String()
		},
	}

	return b.build()
}

// GetGitHubUpdates returns the full names ("owner/repo") of recently pushed GitHub repositories.
// by pushedAfter.
func GetGitHubUpdates(client *http.Client, pushedAfter string) (maxPushedAt string, names []string, err error) {
	c := httpClient{client: client, header: gitHubPreviewHeader}

	if pushedAfter == "" {
		pushedAfter = time.Now().Add(-24 * time.Hour).UTC().Format("2006-01-02T15:04:05Z")
	}
	u := "https://api.github.com/search/repositories?order=asc&sort=updated&q=fork:true+language:Go+pushed:>" + pushedAfter
	var updates struct {
		Items []struct {
			FullName string `json:"full_name"`
			PushedAt string `json:"pushed_at"`
		}
	}
	_, err = c.getJSON(u, &updates)
	if err != nil {
		return pushedAfter, nil, err
	}

	maxPushedAt = pushedAfter
	for _, item := range updates.Items {
		names = append(names, item.FullName)
		if item.PushedAt > maxPushedAt {
			maxPushedAt = item.PushedAt
		}
	}
	return maxPushedAt, names, nil
}

func getGitHubProject(client *http.Client, match map[string]string) (*Project, error) {
	c := &httpClient{client: client, errFn: gitHubError}

	var repo struct {
		Description string
	}

	if _, err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}", match), &repo); err != nil {
		return nil, err
	}

	return &Project{
		Description: repo.Description,
	}, nil
}

func getGistDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
	c := &httpClient{client: client, errFn: gitHubError}

	var gist struct {
		Files map[string]struct {
			Content string
		}
		HTMLURL string `json:"html_url"`
		History []struct {
			Version string
		}
	}

	if _, err := c.getJSON(expand("https://api.github.com/gists/{gist}", match), &gist); err != nil {
		return nil, err
	}

	if len(gist.History) == 0 {
		return nil, NotFoundError{Message: "History not found."}
	}
	commit := gist.History[0].Version

	if commit == savedEtag {
		return nil, NotModifiedError{}
	}

	var files []*File

	for name, file := range gist.Files {
		if isDocFile(name) {
			files = append(files, &File{
				Name:      name,
				Data:      []byte(file.Content),
				BrowseURL: gist.HTMLURL + "#file-" + strings.Replace(name, ".", "-", -1),
			})
		}
	}

	return &Directory{
		BrowseURL:      gist.HTMLURL,
		Etag:           commit,
		Files:          files,
		LineFmt:        "%s-L%d",
		ProjectName:    match["gist"],
		ProjectRoot:    expand("gist.github.com/{gist}.git", match),
		ProjectURL:     gist.HTMLURL,
		Subdirectories: nil,
		VCS:            "git",
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.

package gosrc

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

func init() {
	addService(&service{
		pattern:         regexp.MustCompile(`^github\.com/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$`),
		prefix:          "github.com/",
		get:             getGitHubDir,
		getPresentation: getGitHubPresentation,
		getProject:      getGitHubProject,
	})

	addService(&service{
		pattern: regexp.MustCompile(`^gist\.github\.com/(?P<gist>[a-z0-9A-Z_.\-]+)\.git$`),
		prefix:  "gist.github.com/",
		get:     getGistDir,
	})
}

var (
	gitHubRawHeader     = http.Header{"Accept": {"application/vnd.github-blob.raw"}}
	gitHubPreviewHeader = http.Header{"Accept": {"application/vnd.github.preview"}}
	gitHubCred          string
)

// SetGitHubCredentials specifies the application credentials used when making
// API requests to GitHub.
func SetGitHubCredentials(id, secret string) {
	gitHubCred = "client_id=" + id + "&client_secret=" + secret
}

func gitHubError(resp *http.Response) error {
	var e struct {
		Message string `json:"message"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&e); err == nil {
		return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: %s (%s)", resp.StatusCode, e.Message, resp.Request.URL.String())}
	}
	return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: (%s)", resp.StatusCode, resp.Request.URL.String())}
}

func getGitHubDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {

	c := &httpClient{client: client, errFn: gitHubError}

	match["cred"] = gitHubCred

	var refs []*struct {
		Object struct {
			Type string
			Sha  string
			URL  string
		}
		Ref string
		URL string
	}

	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}/git/refs?{cred}", match), &refs); err != nil {
		return nil, err
	}

	tags := make(map[string]string)
	for _, ref := range refs {
		switch {
		case strings.HasPrefix(ref.Ref, "refs/heads/"):
			tags[ref.Ref[len("refs/heads/"):]] = ref.Object.Sha
		case strings.HasPrefix(ref.Ref, "refs/tags/"):
			tags[ref.Ref[len("refs/tags/"):]] = ref.Object.Sha
		}
	}

	var commit string
	var err error
	match["tag"], commit, err = bestTag(tags, "master")
	if err != nil {
		return nil, err
	}

	if commit == savedEtag {
		return nil, ErrNotModified
	}

	var contents []*struct {
		Type    string
		Name    string
		GitURL  string `json:"git_url"`
		HTMLURL string `json:"html_url"`
	}

	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}?ref={tag}&{cred}", match), &contents); err != nil {
		return nil, err
	}

	if len(contents) == 0 {
		return nil, NotFoundError{"No files in directory."}
	}

	// Because Github API URLs are case-insensitive, we check that the owner
	// and repo returned from Github matches the one that we are requesting.
	if !strings.HasPrefix(contents[0].GitURL, expand("https://api.github.com/repos/{owner}/{repo}/", match)) {
		return nil, NotFoundError{"Github import path has incorrect case."}
	}

	var files []*File
	var dataURLs []string
	var subdirs []string

	for _, item := range contents {
		switch {
		case item.Type == "dir":
			if isValidPathElement(item.Name) {
				subdirs = append(subdirs, item.Name)
			}
		case isDocFile(item.Name):
			files = append(files, &File{Name: item.Name, BrowseURL: item.HTMLURL})
			dataURLs = append(dataURLs, item.GitURL+"?"+gitHubCred)
		}
	}

	c.header = gitHubRawHeader
	if err := c.getFiles(dataURLs, files); err != nil {
		return nil, err
	}

	browseURL := expand("https://github.com/{owner}/{repo}", match)
	if match["dir"] != "" {
		browseURL = expand("https://github.com/{owner}/{repo}/tree/{tag}{dir}", match)
	}

	return &Directory{
		BrowseURL:      browseURL,
		Etag:           commit,
		Files:          files,
		LineFmt:        "%s#L%d",
		ProjectName:    match["repo"],
		ProjectRoot:    expand("github.com/{owner}/{repo}", match),
		ProjectURL:     expand("https://github.com/{owner}/{repo}", match),
		Subdirectories: subdirs,
		VCS:            "git",
	}, nil
}

func getGitHubPresentation(client *http.Client, match map[string]string) (*Presentation, error) {
	c := &httpClient{client: client, header: gitHubRawHeader}

	match["cred"] = gitHubCred

	p, err := c.getBytes(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/{file}?{cred}", match))
	if err != nil {
		return nil, err
	}

	apiBase, err := url.Parse(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/?{cred}", match))
	if err != nil {
		return nil, err
	}
	rawBase, err := url.Parse(expand("https://raw.github.com/{owner}/{repo}/master{dir}/", match))
	if err != nil {
		return nil, err
	}

	c.header = gitHubRawHeader

	b := &presBuilder{
		data:     p,
		filename: match["file"],
		fetch: func(fnames []string) ([]*File, error) {
			var files []*File
			var dataURLs []string
			for _, fname := range fnames {
				u, err := apiBase.Parse(fname)
				if err != nil {
					return nil, err
				}
				u.RawQuery = apiBase.RawQuery
				files = append(files, &File{Name: fname})
				dataURLs = append(dataURLs, u.String())
			}
			err := c.getFiles(dataURLs, files)
			return files, err
		},
		resolveURL: func(fname string) string {
			u, err := rawBase.Parse(fname)
			if err != nil {
				return "/notfound"
			}
			if strings.HasSuffix(fname, ".svg") {
				u.Host = "rawgithub.com"
			}
			return u.String()
		},
	}

	return b.build()
}

// GetGitHubUpdates returns the full names ("owner/repo") of recently pushed GitHub repositories.
// by pushedAfter.
func GetGitHubUpdates(client *http.Client, pushedAfter string) (maxPushedAt string, names []string, err error) {
	c := httpClient{client: client, header: gitHubPreviewHeader}

	if pushedAfter == "" {
		pushedAfter = time.Now().Add(-24 * time.Hour).UTC().Format("2006-01-02T15:04:05Z")
	}
	u := "https://api.github.com/search/repositories?order=asc&sort=updated&q=language:Go+pushed:>" + pushedAfter
	if gitHubCred != "" {
		u += "&" + gitHubCred
	}
	var updates struct {
		Items []struct {
			FullName string `json:"full_name"`
			PushedAt string `json:"pushed_at"`
		}
	}
	err = c.getJSON(u, &updates)
	if err != nil {
		return pushedAfter, nil, err
	}

	maxPushedAt = pushedAfter
	for _, item := range updates.Items {
		names = append(names, item.FullName)
		if item.PushedAt > maxPushedAt {
			maxPushedAt = item.PushedAt
		}
	}
	return maxPushedAt, names, nil
}

func getGitHubProject(client *http.Client, match map[string]string) (*Project, error) {
	c := &httpClient{client: client, errFn: gitHubError}
	match["cred"] = gitHubCred

	var repo struct {
		Description string
	}

	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}?{cred}", match), &repo); err != nil {
		return nil, err
	}

	return &Project{
		Description: repo.Description,
	}, nil
}

func getGistDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
	c := &httpClient{client: client, errFn: gitHubError}

	match["cred"] = gitHubCred

	var gist struct {
		Files map[string]struct {
			Content string
		}
		HtmlUrl string `json:"html_url"`
		History []struct {
			Version string
		}
	}

	if err := c.getJSON(expand("https://api.github.com/gists/{gist}", match)+"?"+gitHubCred, &gist); err != nil {
		return nil, err
	}

	if len(gist.History) == 0 {
		return nil, NotFoundError{"History not found."}
	}
	commit := gist.History[0].Version

	if commit == savedEtag {
		return nil, ErrNotModified
	}

	var files []*File

	for name, file := range gist.Files {
		if isDocFile(name) {
			files = append(files, &File{
				Name:      name,
				Data:      []byte(file.Content),
				BrowseURL: gist.HtmlUrl + "#file-" + strings.Replace(name, ".", "-", -1),
			})
		}
	}

	return &Directory{
		BrowseURL:      gist.HtmlUrl,
		Etag:           commit,
		Files:          files,
		LineFmt:        "%s-L%d",
		ProjectName:    match["gist"],
		ProjectRoot:    expand("gist.github.com/{gist}.git", match),
		ProjectURL:     gist.HtmlUrl,
		Subdirectories: nil,
		VCS:            "git",
	}, nil
}
<commit_msg>Use expand to construct the gist URL.<commit_after>// Copyright 2013 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 or at
// https://developers.google.com/open-source/licenses/bsd.

package gosrc

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"time"
)

func init() {
	addService(&service{
		pattern:         regexp.MustCompile(`^github\.com/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$`),
		prefix:          "github.com/",
		get:             getGitHubDir,
		getPresentation: getGitHubPresentation,
		getProject:      getGitHubProject,
	})

	addService(&service{
		pattern: regexp.MustCompile(`^gist\.github\.com/(?P<gist>[a-z0-9A-Z_.\-]+)\.git$`),
		prefix:  "gist.github.com/",
		get:     getGistDir,
	})
}

var (
	gitHubRawHeader     = http.Header{"Accept": {"application/vnd.github-blob.raw"}}
	gitHubPreviewHeader = http.Header{"Accept": {"application/vnd.github.preview"}}
	gitHubCred          string
)

// SetGitHubCredentials specifies the application credentials used when making
// API requests to GitHub.
func SetGitHubCredentials(id, secret string) {
	gitHubCred = "client_id=" + id + "&client_secret=" + secret
}

func gitHubError(resp *http.Response) error {
	var e struct {
		Message string `json:"message"`
	}
	if err := json.NewDecoder(resp.Body).Decode(&e); err == nil {
		return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: %s (%s)", resp.StatusCode, e.Message, resp.Request.URL.String())}
	}
	return &RemoteError{resp.Request.URL.Host, fmt.Errorf("%d: (%s)", resp.StatusCode, resp.Request.URL.String())}
}

func getGitHubDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {

	c := &httpClient{client: client, errFn: gitHubError}

	match["cred"] = gitHubCred

	var refs []*struct {
		Object struct {
			Type string
			Sha  string
			URL  string
		}
		Ref string
		URL string
	}

	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}/git/refs?{cred}", match), &refs); err != nil {
		return nil, err
	}

	tags := make(map[string]string)
	for _, ref := range refs {
		switch {
		case strings.HasPrefix(ref.Ref, "refs/heads/"):
			tags[ref.Ref[len("refs/heads/"):]] = ref.Object.Sha
		case strings.HasPrefix(ref.Ref, "refs/tags/"):
			tags[ref.Ref[len("refs/tags/"):]] = ref.Object.Sha
		}
	}

	var commit string
	var err error
	match["tag"], commit, err = bestTag(tags, "master")
	if err != nil {
		return nil, err
	}

	if commit == savedEtag {
		return nil, ErrNotModified
	}

	var contents []*struct {
		Type    string
		Name    string
		GitURL  string `json:"git_url"`
		HTMLURL string `json:"html_url"`
	}

	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}?ref={tag}&{cred}", match), &contents); err != nil {
		return nil, err
	}

	if len(contents) == 0 {
		return nil, NotFoundError{"No files in directory."}
	}

	// Because Github API URLs are case-insensitive, we check that the owner
	// and repo returned from Github matches the one that we are requesting.
	if !strings.HasPrefix(contents[0].GitURL, expand("https://api.github.com/repos/{owner}/{repo}/", match)) {
		return nil, NotFoundError{"Github import path has incorrect case."}
	}

	var files []*File
	var dataURLs []string
	var subdirs []string

	for _, item := range contents {
		switch {
		case item.Type == "dir":
			if isValidPathElement(item.Name) {
				subdirs = append(subdirs, item.Name)
			}
		case isDocFile(item.Name):
			files = append(files, &File{Name: item.Name, BrowseURL: item.HTMLURL})
			dataURLs = append(dataURLs, item.GitURL+"?"+gitHubCred)
		}
	}

	c.header = gitHubRawHeader
	if err := c.getFiles(dataURLs, files); err != nil {
		return nil, err
	}

	browseURL := expand("https://github.com/{owner}/{repo}", match)
	if match["dir"] != "" {
		browseURL = expand("https://github.com/{owner}/{repo}/tree/{tag}{dir}", match)
	}

	return &Directory{
		BrowseURL:      browseURL,
		Etag:           commit,
		Files:          files,
		LineFmt:        "%s#L%d",
		ProjectName:    match["repo"],
		ProjectRoot:    expand("github.com/{owner}/{repo}", match),
		ProjectURL:     expand("https://github.com/{owner}/{repo}", match),
		Subdirectories: subdirs,
		VCS:            "git",
	}, nil
}

func getGitHubPresentation(client *http.Client, match map[string]string) (*Presentation, error) {
	c := &httpClient{client: client, header: gitHubRawHeader}

	match["cred"] = gitHubCred

	p, err := c.getBytes(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/{file}?{cred}", match))
	if err != nil {
		return nil, err
	}

	apiBase, err := url.Parse(expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}/?{cred}", match))
	if err != nil {
		return nil, err
	}
	rawBase, err := url.Parse(expand("https://raw.github.com/{owner}/{repo}/master{dir}/", match))
	if err != nil {
		return nil, err
	}

	c.header = gitHubRawHeader

	b := &presBuilder{
		data:     p,
		filename: match["file"],
		fetch: func(fnames []string) ([]*File, error) {
			var files []*File
			var dataURLs []string
			for _, fname := range fnames {
				u, err := apiBase.Parse(fname)
				if err != nil {
					return nil, err
				}
				u.RawQuery = apiBase.RawQuery
				files = append(files, &File{Name: fname})
				dataURLs = append(dataURLs, u.String())
			}
			err := c.getFiles(dataURLs, files)
			return files, err
		},
		resolveURL: func(fname string) string {
			u, err := rawBase.Parse(fname)
			if err != nil {
				return "/notfound"
			}
			if strings.HasSuffix(fname, ".svg") {
				u.Host = "rawgithub.com"
			}
			return u.String()
		},
	}

	return b.build()
}

// GetGitHubUpdates returns the full names ("owner/repo") of recently pushed GitHub repositories.
// by pushedAfter.
func GetGitHubUpdates(client *http.Client, pushedAfter string) (maxPushedAt string, names []string, err error) {
	c := httpClient{client: client, header: gitHubPreviewHeader}

	if pushedAfter == "" {
		pushedAfter = time.Now().Add(-24 * time.Hour).UTC().Format("2006-01-02T15:04:05Z")
	}
	u := "https://api.github.com/search/repositories?order=asc&sort=updated&q=language:Go+pushed:>" + pushedAfter
	if gitHubCred != "" {
		u += "&" + gitHubCred
	}
	var updates struct {
		Items []struct {
			FullName string `json:"full_name"`
			PushedAt string `json:"pushed_at"`
		}
	}
	err = c.getJSON(u, &updates)
	if err != nil {
		return pushedAfter, nil, err
	}

	maxPushedAt = pushedAfter
	for _, item := range updates.Items {
		names = append(names, item.FullName)
		if item.PushedAt > maxPushedAt {
			maxPushedAt = item.PushedAt
		}
	}
	return maxPushedAt, names, nil
}

func getGitHubProject(client *http.Client, match map[string]string) (*Project, error) {
	c := &httpClient{client: client, errFn: gitHubError}
	match["cred"] = gitHubCred

	var repo struct {
		Description string
	}

	if err := c.getJSON(expand("https://api.github.com/repos/{owner}/{repo}?{cred}", match), &repo); err != nil {
		return nil, err
	}

	return &Project{
		Description: repo.Description,
	}, nil
}

func getGistDir(client *http.Client, match map[string]string, savedEtag string) (*Directory, error) {
	c := &httpClient{client: client, errFn: gitHubError}

	match["cred"] = gitHubCred

	var gist struct {
		Files map[string]struct {
			Content string
		}
		HtmlUrl string `json:"html_url"`
		History []struct {
			Version string
		}
	}

	if err := c.getJSON(expand("https://api.github.com/gists/{gist}?{cred}", match), &gist); err != nil {
		return nil, err
	}

	if len(gist.History) == 0 {
		return nil, NotFoundError{"History not found."}
	}
	commit := gist.History[0].Version

	if commit == savedEtag {
		return nil, ErrNotModified
	}

	var files []*File

	for name, file := range gist.Files {
		if isDocFile(name) {
			files = append(files, &File{
				Name:      name,
				Data:      []byte(file.Content),
				BrowseURL: gist.HtmlUrl + "#file-" + strings.Replace(name, ".", "-", -1),
			})
		}
	}

	return &Directory{
		BrowseURL:      gist.HtmlUrl,
		Etag:           commit,
		Files:          files,
		LineFmt:        "%s-L%d",
		ProjectName:    match["gist"],
		ProjectRoot:    expand("gist.github.com/{gist}.git", match),
		ProjectURL:     gist.HtmlUrl,
		Subdirectories: nil,
		VCS:            "git",
	}, nil
}
<|endoftext|>


<commit_before>// Copyright 2011 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.

// +build !appengine

package main

import (
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"code.google.com/p/go.tools/playground/socket"

	// Imports so that go build/install automatically installs them.
	_ "code.google.com/p/go-tour/pic"
	_ "code.google.com/p/go-tour/tree"
	_ "code.google.com/p/go-tour/wc"
)

const (
	basePkg    = "code.google.com/p/go-tour/"
	socketPath = "/socket"
)

var (
	httpListen  = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
	openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
)

var (
	// GOPATH containing the tour packages
	gopath = os.Getenv("GOPATH")

	httpAddr string
)

// isRoot reports whether path is the root directory of the tour tree.
// To be the root, it must have content and template subdirectories.
func isRoot(path string) bool {
	_, err := os.Stat(filepath.Join(path, "content", "welcome.article"))
	if err == nil {
		_, err = os.Stat(filepath.Join(path, "template", "index.tmpl"))
	}
	return err == nil
}

func findRoot() (string, error) {
	ctx := build.Default
	p, err := ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(p.Dir) {
		return p.Dir, nil
	}
	tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour")
	ctx.GOPATH = tourRoot
	p, err = ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(tourRoot) {
		gopath = tourRoot
		return tourRoot, nil
	}
	return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH")
}

func main() {
	flag.Parse()

	// find and serve the go tour files
	root, err := findRoot()
	if err != nil {
		log.Fatalf("Couldn't find tour files: %v", err)
	}

	log.Println("Serving content from", root)

	host, port, err := net.SplitHostPort(*httpListen)
	if err != nil {
		log.Fatal(err)
	}
	if host == "" {
		host = "localhost"
	}
	if host != "127.0.0.1" && host != "localhost" {
		log.Print(localhostWarning)
	}
	httpAddr = host + ":" + port

	if err := initTour(root, "SocketTransport"); err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/", rootHandler)
	http.HandleFunc("/lesson/", lessonHandler)
	http.Handle(socketPath, socket.Handler)

	// Keep these static file handlers in sync with ../app.yaml.
	static := http.FileServer(http.Dir(root))
	http.Handle("/content/img/", static)
	http.Handle("/static/", static)
	imgDir := filepath.Join(root, "static", "img")
	http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir)))

	go func() {
		url := "http://" + httpAddr
		if waitServer(url) && *openBrowser && startBrowser(url) {
			log.Printf("A browser window should open. If not, please visit %s", url)
		} else {
			log.Printf("Please open your web browser and visit %s", url)
		}
	}()
	log.Fatal(http.ListenAndServe(httpAddr, nil))
}

// rootHandler returns a handler for all the requests except the ones for lessons.
func rootHandler(w http.ResponseWriter, r *http.Request) {
	if err := renderUI(w); err != nil {
		log.Println(err)
	}
}

// lessonHandler handler the HTTP requests for lessons.
func lessonHandler(w http.ResponseWriter, r *http.Request) {
	lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
	if err := writeLesson(lesson, w); err != nil {
		if err == lessonNotFound {
			http.NotFound(w, r)
		} else {
			log.Println(err)
		}
	}
}

const localhostWarning = `
WARNING!  WARNING!  WARNING!

I appear to be listening on an address that is not localhost.
Anyone with access to this address and port will have access
to this machine as the user running gotour.

If you don't understand this message, hit Control-C to terminate this process.

WARNING!  WARNING!  WARNING!
`

type response struct {
	Output string `json:"output"`
	Errors string `json:"compile_errors"`
}

func init() {
	socket.Environ = environ
}

// environ returns the original execution environment with GOPATH
// replaced (or added) with the value of the global var gopath.
func environ() (env []string) {
	for _, v := range os.Environ() {
		if !strings.HasPrefix(v, "GOPATH=") {
			env = append(env, v)
		}
	}
	env = append(env, "GOPATH="+gopath)
	return
}

// waitServer waits some time for the http Server to start
// serving url. The return value reports whether it starts.
func waitServer(url string) bool {
	tries := 20
	for tries > 0 {
		resp, err := http.Get(url)
		if err == nil {
			resp.Body.Close()
			return true
		}
		time.Sleep(100 * time.Millisecond)
		tries--
	}
	return false
}

// startBrowser tries to open the URL in a browser, and returns
// whether it succeed.
func startBrowser(url string) bool {
	// try to start the browser
	var args []string
	switch runtime.GOOS {
	case "darwin":
		args = []string{"open"}
	case "windows":
		args = []string{"cmd", "/c", "start"}
	default:
		args = []string{"xdg-open"}
	}
	cmd := exec.Command(args[0], append(args[1:], url)...)
	return cmd.Start() == nil
}

// prepContent for the local tour simply returns the content as-is.
func prepContent(r io.Reader) io.Reader { return r }

// socketAddr returns the WebSocket handler address.
func socketAddr() string { return "ws://" + httpAddr + socketPath }
<commit_msg>go-tour: use new socket interface<commit_after>// Copyright 2011 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.

// +build !appengine

package main

import (
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"code.google.com/p/go.tools/playground/socket"

	// Imports so that go build/install automatically installs them.
	_ "code.google.com/p/go-tour/pic"
	_ "code.google.com/p/go-tour/tree"
	_ "code.google.com/p/go-tour/wc"
)

const (
	basePkg    = "code.google.com/p/go-tour/"
	socketPath = "/socket"
)

var (
	httpListen  = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
	openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
)

var (
	// GOPATH containing the tour packages
	gopath = os.Getenv("GOPATH")

	httpAddr string
)

// isRoot reports whether path is the root directory of the tour tree.
// To be the root, it must have content and template subdirectories.
func isRoot(path string) bool {
	_, err := os.Stat(filepath.Join(path, "content", "welcome.article"))
	if err == nil {
		_, err = os.Stat(filepath.Join(path, "template", "index.tmpl"))
	}
	return err == nil
}

func findRoot() (string, error) {
	ctx := build.Default
	p, err := ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(p.Dir) {
		return p.Dir, nil
	}
	tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour")
	ctx.GOPATH = tourRoot
	p, err = ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(tourRoot) {
		gopath = tourRoot
		return tourRoot, nil
	}
	return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH")
}

func main() {
	flag.Parse()

	// find and serve the go tour files
	root, err := findRoot()
	if err != nil {
		log.Fatalf("Couldn't find tour files: %v", err)
	}

	log.Println("Serving content from", root)

	host, port, err := net.SplitHostPort(*httpListen)
	if err != nil {
		log.Fatal(err)
	}
	if host == "" {
		host = "localhost"
	}
	if host != "127.0.0.1" && host != "localhost" {
		log.Print(localhostWarning)
	}
	httpAddr = host + ":" + port

	if err := initTour(root, "SocketTransport"); err != nil {
		log.Fatal(err)
	}

	http.HandleFunc("/", rootHandler)
	http.HandleFunc("/lesson/", lessonHandler)

	origin := &url.URL{Scheme: "http", Host: host + ":" + port}
	http.Handle(socketPath, socket.NewHandler(origin))

	// Keep these static file handlers in sync with ../app.yaml.
	static := http.FileServer(http.Dir(root))
	http.Handle("/content/img/", static)
	http.Handle("/static/", static)
	imgDir := filepath.Join(root, "static", "img")
	http.Handle("/favicon.ico", http.FileServer(http.Dir(imgDir)))

	go func() {
		url := "http://" + httpAddr
		if waitServer(url) && *openBrowser && startBrowser(url) {
			log.Printf("A browser window should open. If not, please visit %s", url)
		} else {
			log.Printf("Please open your web browser and visit %s", url)
		}
	}()
	log.Fatal(http.ListenAndServe(httpAddr, nil))
}

// rootHandler returns a handler for all the requests except the ones for lessons.
func rootHandler(w http.ResponseWriter, r *http.Request) {
	if err := renderUI(w); err != nil {
		log.Println(err)
	}
}

// lessonHandler handler the HTTP requests for lessons.
func lessonHandler(w http.ResponseWriter, r *http.Request) {
	lesson := strings.TrimPrefix(r.URL.Path, "/lesson/")
	if err := writeLesson(lesson, w); err != nil {
		if err == lessonNotFound {
			http.NotFound(w, r)
		} else {
			log.Println(err)
		}
	}
}

const localhostWarning = `
WARNING!  WARNING!  WARNING!

I appear to be listening on an address that is not localhost.
Anyone with access to this address and port will have access
to this machine as the user running gotour.

If you don't understand this message, hit Control-C to terminate this process.

WARNING!  WARNING!  WARNING!
`

type response struct {
	Output string `json:"output"`
	Errors string `json:"compile_errors"`
}

func init() {
	socket.Environ = environ
}

// environ returns the original execution environment with GOPATH
// replaced (or added) with the value of the global var gopath.
func environ() (env []string) {
	for _, v := range os.Environ() {
		if !strings.HasPrefix(v, "GOPATH=") {
			env = append(env, v)
		}
	}
	env = append(env, "GOPATH="+gopath)
	return
}

// waitServer waits some time for the http Server to start
// serving url. The return value reports whether it starts.
func waitServer(url string) bool {
	tries := 20
	for tries > 0 {
		resp, err := http.Get(url)
		if err == nil {
			resp.Body.Close()
			return true
		}
		time.Sleep(100 * time.Millisecond)
		tries--
	}
	return false
}

// startBrowser tries to open the URL in a browser, and returns
// whether it succeed.
func startBrowser(url string) bool {
	// try to start the browser
	var args []string
	switch runtime.GOOS {
	case "darwin":
		args = []string{"open"}
	case "windows":
		args = []string{"cmd", "/c", "start"}
	default:
		args = []string{"xdg-open"}
	}
	cmd := exec.Command(args[0], append(args[1:], url)...)
	return cmd.Start() == nil
}

// prepContent for the local tour simply returns the content as-is.
func prepContent(r io.Reader) io.Reader { return r }

// socketAddr returns the WebSocket handler address.
func socketAddr() string { return "ws://" + httpAddr + socketPath }
<|endoftext|>


<commit_before>package gen

import (
	"errors"
	"path/filepath"
)

//Config is the settings needed by the static generatot. It is inspired by the
//jekyll configuration options.
//
// The format can either be json, yaml or toml
// TODO: add yaml and toml support.
type Config struct {
	Name         string   `json:"name" yaml:"name"`
	Source       string   `json:"source" yaml:"source"`
	Destination  string   `json:"destination" yaml:"destination"`
	StaticDir    string   `json:"statc_dir" yaml:"statc_dir"`
	TemplatesDir string   `json:"templates_dir" yaml:"templates_dir"`
	ThemeDir     string   `json:"theme_dir" yaml:"theme_dir"`
	DefaultTheme string   `json:"default_theme" yaml:"default_theme"`
	PluginDir    string   `json:"plugin_dir" yaml:"plugin_dir"`
	Safe         bool     `json:"safe" yaml:"safe"`
	Excluede     []string `json:"exclude" yaml:"exclude"`
	Include      []string `json:"include" yaml:"include"`
}

//DefaultConfig retruns *Config with default settings
func DefaultConfig() *Config {
	return &Config{
		Source:       "src",
		Destination:  "dest",
		StaticDir:    "static",
		ThemeDir:     "themes",
		TemplatesDir: "templates",
		DefaultTheme: "doxsey",
		PluginDir:    "plugins",
		Safe:         true,
		Excluede: []string{
			"CONTRIBUTING.md",
		},
		Include: []string{
			"LICENCE.md",
		},
	}
}

//System configuration for the whole static generator system. The information
//consost of things necessary to bootup, configure and plan the execution of the
//static project..
type System struct {
	Boot   *Boot   `json:"boot"`
	Config *Config `json:"config"`
	Plan   *Plan   `json:"plan"`
}

//Boot necessary info to bootstrap the Generator.
type Boot struct {
	ConfigiFile string            `json:"config_file"`
	PlanFile    string            `json:"plan_file"`
	ENV         map[string]string `json:"env"`
}

//Theme discreption of a theme.
type Theme struct {
	Name   string   `json:"name"`
	Author []Author `json:"author"`
}

//Author description of the author of the project being built.
type Author struct {
	Name     string `json:"name"`
	Github   string `json:"github"`
	Twitter  string `json:"twitter"`
	Linkedin string `json:"linkedin"`
	Email    string `json:"email"`
	Website  string `json:"website"`
}

// Plan is the execution planner object. It states the steps and stages on which
// the execution process should take.
type Plan struct {
	Title string `json:"title"`

	// Modules that are supposed to be loaded before the execution starts. The
	// execution process wont start if one of the dependencies is missing.
	Dependency []string `json:"dependencies"`

	TemplateEngine string      `json:"template_engine"`
	Strategies     []*Strategy `json:"strategies"`
}

//Strategy defines what should be done when a file matches a certain pattern.
//the pattern is glob like and matches only on the filenames
//
// The FullMatch field specifies the pattern to be matchig the full file path.
// if set to  false is means the pattern only matches the base of the file path(
// ignoring the directories).
type Strategy struct {
	Title     string   `json:"title"`
	Patterns  []string `json:"patterns"`
	FullMatch bool     `json:"fullMatch"`
	Before    []string `json:"before"`
	Exec      []string `json:"exec"`
	After     []string `json:"after"`
}

//FindStrategy searches for a strategy matching the given filePath that is
//defined in the Plan.
//
//TODO: Support multiple return strategies, since more than one strategy can be
//defined for a given file path.
func (p *Plan) FindStrategy(filePath string) (*Strategy, error) {
	for i := range p.Strategies {
		s := p.Strategies[i]
		m := filePath
		if !s.FullMatch {
			m = filepath.Base(filePath)
		}
		for _, patt := range s.Patterns {
			ok, err := filepath.Match(patt, m)
			if err != nil {
				return nil, err
			}
			if !ok {
				continue
			}
			return s, nil
		}
	}
	return nil, errors.New("can't find strategy for " + filePath)
}

//File is a representation of a file unit as it is passed arouund for
//processing.
// File content is passed as a string so as to allow easy trasition between Go
// and javascript boundary.
type File struct {
	Name     string                 `json:"name"`
	Meta     map[string]interface{} `json:"meta"`
	Contents string                 `json:"contents"`
}

//FileList is a list of files, which can be ordered based on various criterias
type FileList []*File
<commit_msg>gen: Add After field to Plan<commit_after>package gen

import (
	"errors"
	"path/filepath"
)

//Config is the settings needed by the static generatot. It is inspired by the
//jekyll configuration options.
//
// The format can either be json, yaml or toml
// TODO: add yaml and toml support.
type Config struct {
	Name         string   `json:"name" yaml:"name"`
	Source       string   `json:"source" yaml:"source"`
	Destination  string   `json:"destination" yaml:"destination"`
	StaticDir    string   `json:"statc_dir" yaml:"statc_dir"`
	TemplatesDir string   `json:"templates_dir" yaml:"templates_dir"`
	ThemeDir     string   `json:"theme_dir" yaml:"theme_dir"`
	DefaultTheme string   `json:"default_theme" yaml:"default_theme"`
	PluginDir    string   `json:"plugin_dir" yaml:"plugin_dir"`
	Safe         bool     `json:"safe" yaml:"safe"`
	Excluede     []string `json:"exclude" yaml:"exclude"`
	Include      []string `json:"include" yaml:"include"`
}

//DefaultConfig retruns *Config with default settings
func DefaultConfig() *Config {
	return &Config{
		Source:       "src",
		Destination:  "dest",
		StaticDir:    "static",
		ThemeDir:     "themes",
		TemplatesDir: "templates",
		DefaultTheme: "doxsey",
		PluginDir:    "plugins",
		Safe:         true,
		Excluede: []string{
			"CONTRIBUTING.md",
		},
		Include: []string{
			"LICENCE.md",
		},
	}
}

//System configuration for the whole static generator system. The information
//consost of things necessary to bootup, configure and plan the execution of the
//static project..
type System struct {
	Boot   *Boot   `json:"boot"`
	Config *Config `json:"config"`
	Plan   *Plan   `json:"plan"`
}

//Boot necessary info to bootstrap the Generator.
type Boot struct {
	ConfigiFile string            `json:"config_file"`
	PlanFile    string            `json:"plan_file"`
	ENV         map[string]string `json:"env"`
}

//Theme discreption of a theme.
type Theme struct {
	Name   string   `json:"name"`
	Author []Author `json:"author"`
}

//Author description of the author of the project being built.
type Author struct {
	Name     string `json:"name"`
	Github   string `json:"github"`
	Twitter  string `json:"twitter"`
	Linkedin string `json:"linkedin"`
	Email    string `json:"email"`
	Website  string `json:"website"`
}

// Plan is the execution planner object. It states the steps and stages on which
// the execution process should take.
type Plan struct {
	Title string `json:"title"`

	// Modules that are supposed to be loaded before the execution starts. The
	// execution process wont start if one of the dependencies is missing.
	Dependency []string `json:"dependencies"`

	TemplateEngine string      `json:"template_engine"`
	Strategies     []*Strategy `json:"strategies"`
	After          []string    `json:"after"`
}

//Strategy defines what should be done when a file matches a certain pattern.
//the pattern is glob like and matches only on the filenames
//
// The FullMatch field specifies the pattern to be matchig the full file path.
// if set to  false is means the pattern only matches the base of the file path(
// ignoring the directories).
type Strategy struct {
	Title     string   `json:"title"`
	Patterns  []string `json:"patterns"`
	FullMatch bool     `json:"fullMatch"`
	Before    []string `json:"before"`
	Exec      []string `json:"exec"`
	After     []string `json:"after"`
}

//FindStrategy searches for a strategy matching the given filePath that is
//defined in the Plan.
//
//TODO: Support multiple return strategies, since more than one strategy can be
//defined for a given file path.
func (p *Plan) FindStrategy(filePath string) (*Strategy, error) {
	for i := range p.Strategies {
		s := p.Strategies[i]
		m := filePath
		if !s.FullMatch {
			m = filepath.Base(filePath)
		}
		for _, patt := range s.Patterns {
			ok, err := filepath.Match(patt, m)
			if err != nil {
				return nil, err
			}
			if !ok {
				continue
			}
			return s, nil
		}
	}
	return nil, errors.New("can't find strategy for " + filePath)
}

//File is a representation of a file unit as it is passed arouund for
//processing.
// File content is passed as a string so as to allow easy trasition between Go
// and javascript boundary.
type File struct {
	Name     string                 `json:"name"`
	Meta     map[string]interface{} `json:"meta"`
	Contents string                 `json:"contents"`
}

//FileList is a list of files, which can be ordered based on various criterias
type FileList []*File
<|endoftext|>


<commit_before>// Copyright (c) 2013-2016 Laurent Moussault. All rights reserved.
// Licensed under a simplified BSD license (see LICENSE file).

package gfx

//------------------------------------------------------------------------------

import (
	"fmt"
	"reflect"

	"strconv"

	"github.com/drakmaniso/glam/color"
	"github.com/drakmaniso/glam/geom"
)

/*
#include "glad.h"

void VertexAttribute(
	GLuint vao,
	GLuint index,
	GLuint binding,
	GLint size,
	GLenum type,
	GLboolean normalized,
	GLuint relativeOffset
) {
	glVertexArrayAttribFormat(vao, index, size, type, normalized, relativeOffset);
	glVertexArrayAttribBinding(vao, index, binding);
	glEnableVertexArrayAttrib(vao, index);
}
*/
import "C"

//------------------------------------------------------------------------------

// VertexFormat prepares everything the pipeline needs to use a
// vertex buffer of a specific format, and assign a binding index to it.
//
// The format must be a struct with layout tags.
func VertexFormat(binding uint32, format interface{}) PipelineOption {
	return func(p *Pipeline) {
		p.setVertexFormat(binding, format)
	}
}

func (p *Pipeline) setVertexFormat(binding uint32, format interface{}) {
	f := reflect.TypeOf(format)
	if f.Kind() != reflect.Struct {
		setErr(fmt.Errorf("attributes binding format must be a struct, not a %s", f.Kind()))
		return
	}

	for i := 0; i < f.NumField(); i++ {
		a := f.Field(i)
		al := a.Tag.Get("layout")
		if al == "" {
			continue
		}
		ali, err := strconv.Atoi(al)
		if err != nil {
			setErr(fmt.Errorf("invalid layout for attributes binding: %q", al))
			return
		}
		//TODO: check that ali is in range
		at := a.Type
		var as int32
		ao := a.Offset
		var ate C.GLenum
		switch {
		// Float32
		case at.ConvertibleTo(float32Type):
			as = 1
			ate = C.GL_FLOAT
		case at.ConvertibleTo(vec4Type), at.ConvertibleTo(rgbaType):
			as = 4
			ate = C.GL_FLOAT
		case at.ConvertibleTo(vec3Type), at.ConvertibleTo(rgbType):
			as = 3
			ate = C.GL_FLOAT
		case at.ConvertibleTo(vec2Type):
			as = 2
			ate = C.GL_FLOAT
		// Int32
		case at.ConvertibleTo(int32Type):
			as = 1
			ate = C.GL_INT
		case at.ConvertibleTo(ivec4Type):
			as = 4
			ate = C.GL_INT
		case at.ConvertibleTo(ivec3Type):
			as = 3
			ate = C.GL_INT
		case at.ConvertibleTo(ivec2Type):
			as = 2
			ate = C.GL_INT
		}

		C.VertexAttribute(
			p.vao,
			C.GLuint(ali),
			C.GLuint(binding),
			C.GLint(as),
			ate,
			C.GLboolean(0), //TODO
			C.GLuint(ao),
		)
	}
	return
}

var (
	float32Type = reflect.TypeOf(float32(0))
	vec4Type    = reflect.TypeOf(geom.Vec4{})
	vec3Type    = reflect.TypeOf(geom.Vec3{})
	vec2Type    = reflect.TypeOf(geom.Vec2{})
	int32Type   = reflect.TypeOf(int32(0))
	ivec4Type   = reflect.TypeOf(geom.IVec4{})
	ivec3Type   = reflect.TypeOf(geom.IVec3{})
	ivec2Type   = reflect.TypeOf(geom.IVec2{})
	rgbType     = reflect.TypeOf(color.RGB{})
	rgbaType    = reflect.TypeOf(color.RGBA{})
)

//------------------------------------------------------------------------------
<commit_msg>Remove geom and color dependancy from VertexFormat<commit_after>// Copyright (c) 2013-2016 Laurent Moussault. All rights reserved.
// Licensed under a simplified BSD license (see LICENSE file).

package gfx

//------------------------------------------------------------------------------

import (
	"fmt"
	"reflect"
	"strconv"
)

/*
#include "glad.h"

void VertexAttribute(
	GLuint vao,
	GLuint index,
	GLuint binding,
	GLint size,
	GLenum type,
	GLboolean normalized,
	GLuint relativeOffset
) {
	glVertexArrayAttribFormat(vao, index, size, type, normalized, relativeOffset);
	glVertexArrayAttribBinding(vao, index, binding);
	glEnableVertexArrayAttrib(vao, index);
}
*/
import "C"

//------------------------------------------------------------------------------

// VertexFormat prepares everything the pipeline needs to use a
// vertex buffer of a specific format, and assign a binding index to it.
//
// The format must be a struct with layout tags.
func VertexFormat(binding uint32, format interface{}) PipelineOption {
	return func(p *Pipeline) {
		p.setVertexFormat(binding, format)
	}
}

func (p *Pipeline) setVertexFormat(binding uint32, format interface{}) {
	f := reflect.TypeOf(format)
	if f.Kind() != reflect.Struct {
		setErr(fmt.Errorf("attributes binding format must be a struct, not a %s", f.Kind()))
		return
	}

	for i := 0; i < f.NumField(); i++ {
		a := f.Field(i)
		al := a.Tag.Get("layout")
		if al == "" {
			continue
		}
		ali, err := strconv.Atoi(al)
		if err != nil {
			setErr(fmt.Errorf("invalid layout for attributes binding: %q", al))
			return
		}
		//TODO: check that ali is in range
		at := a.Type
		var as int32
		ao := a.Offset
		var ate C.GLenum
		switch {
		// Float32
		case at.ConvertibleTo(float32Type):
			as = 1
			ate = C.GL_FLOAT
		case at.ConvertibleTo(vec4Type), at.ConvertibleTo(rgbaType):
			as = 4
			ate = C.GL_FLOAT
		case at.ConvertibleTo(vec3Type), at.ConvertibleTo(rgbType):
			as = 3
			ate = C.GL_FLOAT
		case at.ConvertibleTo(vec2Type):
			as = 2
			ate = C.GL_FLOAT
		// Int32
		case at.ConvertibleTo(int32Type):
			as = 1
			ate = C.GL_INT
		case at.ConvertibleTo(ivec4Type):
			as = 4
			ate = C.GL_INT
		case at.ConvertibleTo(ivec3Type):
			as = 3
			ate = C.GL_INT
		case at.ConvertibleTo(ivec2Type):
			as = 2
			ate = C.GL_INT
		}

		C.VertexAttribute(
			p.vao,
			C.GLuint(ali),
			C.GLuint(binding),
			C.GLint(as),
			ate,
			C.GLboolean(0), //TODO
			C.GLuint(ao),
		)
	}
	return
}

var (
	float32Type = reflect.TypeOf(float32(0))
	vec4Type    = reflect.TypeOf(struct{ X, Y, Z, W float32 }{})
	vec3Type    = reflect.TypeOf(struct{ X, Y, Z float32 }{})
	vec2Type    = reflect.TypeOf(struct{ X, Y float32 }{})
	int32Type   = reflect.TypeOf(int32(0))
	ivec4Type   = reflect.TypeOf(struct{ X, Y, Z, W int32 }{})
	ivec3Type   = reflect.TypeOf(struct{ X, Y, Z int32 }{})
	ivec2Type   = reflect.TypeOf(struct{ X, Y int32 }{})
	rgbType     = reflect.TypeOf(struct{ R, G, B float32 }{})
	rgbaType    = reflect.TypeOf(struct{ R, G, B, A float32 }{})
)

//------------------------------------------------------------------------------
<|endoftext|>


<commit_before>package main

import (
    //"fmt"
    "image"
    "image/color"
    //"image/gif"
    "os"
    //"bufio"
    "io"
    "compress/lzw"
    "bytes"
    "math"
)

func writeHeader(w io.Writer, m image.Image) {
    b := m.Bounds()

    header := make([]byte, 0x320)

    header[0] = 'G'
    header[1] = 'I'
    header[2] = 'F'
    header[3] = '8'
    header[4] = '9'
    header[5] = 'a'

    header[7] = byte(b.Max.X / 255)
    header[6] = byte(b.Max.X % 255)
    header[9] = byte(b.Max.Y / 255)
    header[8] = byte(b.Max.Y % 255)

    header[0x0A] = byte(0xF7) // GCT follows for 256 colors with resolution
                              // 3 x 8 bits/primary

    header[0x0B] = byte(0x00) // Background color.
    header[0x0C] = byte(0x00) // Default pixel aspect ratio.

    // Grayscale color table.
    for i := 0; i < 255; i++ {
        header[0x0F + i * 3] = byte(i)
        header[0x0E + i * 3] = byte(i)
        header[0x0D + i * 3] = byte(i)
    }

    header[0x30D] = byte(0x21) // GCE data header.
    header[0x30E] = byte(0xF9) // GCE data header (cont).
    header[0x30F] = byte(0x04) // Next 4 bytes are GCE data.
    header[0x310] = byte(0x01) // There is a transparent pixel.
    header[0x311] = byte(0x00) // Animation delay, LSB.
    header[0x312] = byte(0x00) // Animation delay, MSB.
    header[0x313] = byte(0x10) // And it is color #16 (0x10).
    header[0x314] = byte(0x00) // End of GCE data.

    header[0x315] = byte(0x2C) // Start of Image Descriptor.

    header[0x317] = byte(b.Min.X / 255)
    header[0x316] = byte(b.Min.X % 255)
    header[0x319] = byte(b.Min.Y / 255)
    header[0x318] = byte(b.Min.Y % 255)

    header[0x31B] = byte(b.Max.X / 255)
    header[0x31A] = byte(b.Max.X % 255)
    header[0x31D] = byte(b.Max.Y / 255)
    header[0x31C] = byte(b.Max.Y % 255)

    header[0x31E] = byte(0x00) // No local color table.

    header[0x31F] = byte(0x08) // Start of LZW with minimum code size 8.

    w.Write(header)
}

func compressImage(m image.Image) *bytes.Buffer {
    b := m.Bounds()

    compressedImageBuffer := bytes.NewBuffer(make([]byte, 0, 255))
    lzww := lzw.NewWriter(compressedImageBuffer, lzw.LSB, int(8))

    for y := b.Min.Y; y < b.Max.Y; y++ {
        for x := b.Min.X; x < b.Max.X; x++ {
            //c := color.GrayModel.Convert(m.At(x, y)).(color.Gray)
            //lzww.Write([]byte{c.Y})
            lzww.Write([]byte{byte(x ^ y)})
        }
    }
    lzww.Close()

    return compressedImageBuffer
}

func writeBlocks(w io.Writer, compressedImage *bytes.Buffer) {
    const maxBlockSize = 255
    bytesSoFar := 0
    bytesRemaining := compressedImage.Len()
    for bytesRemaining > 0 {
        if bytesSoFar == 0 {
            blockSize := math.Min(maxBlockSize, float64(bytesRemaining))
            w.Write([]byte{byte(blockSize)})
        }

        b, _ :=  compressedImage.ReadByte()
        w.Write([]byte{b})

        bytesSoFar = (bytesSoFar + 1) % maxBlockSize
        bytesRemaining--
    }
}

func Encode(w io.Writer, m image.Image) error {
    writeHeader(w, m)
    writeBlocks(w, compressImage(m))
    w.Write([]byte{0, ';'})

    return nil
}

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    m.Set(1, 1, color.RGBA{0x00, 0xFF, 0x00, 0xFF})
    file, _ := os.Create("new_image.gif")
    Encode(file, m)
}
<commit_msg>Actually write the user image<commit_after>package main

import (
    //"fmt"
    "image"
    "image/color"
    //"image/gif"
    "os"
    //"bufio"
    "io"
    "compress/lzw"
    "bytes"
    "math"
)

func writeHeader(w io.Writer, m image.Image) {
    b := m.Bounds()

    header := make([]byte, 0x320)

    header[0] = 'G'
    header[1] = 'I'
    header[2] = 'F'
    header[3] = '8'
    header[4] = '9'
    header[5] = 'a'

    header[7] = byte(b.Max.X / 255)
    header[6] = byte(b.Max.X % 255)
    header[9] = byte(b.Max.Y / 255)
    header[8] = byte(b.Max.Y % 255)

    header[0x0A] = byte(0xF7) // GCT follows for 256 colors with resolution
                              // 3 x 8 bits/primary

    header[0x0B] = byte(0x00) // Background color.
    header[0x0C] = byte(0x00) // Default pixel aspect ratio.

    // Grayscale color table.
    for i := 0; i < 255; i++ {
        header[0x0F + i * 3] = byte(i)
        header[0x0E + i * 3] = byte(i)
        header[0x0D + i * 3] = byte(i)
    }

    header[0x30D] = byte(0x21) // GCE data header.
    header[0x30E] = byte(0xF9) // GCE data header (cont).
    header[0x30F] = byte(0x04) // Next 4 bytes are GCE data.
    header[0x310] = byte(0x01) // There is a transparent pixel.
    header[0x311] = byte(0x00) // Animation delay, LSB.
    header[0x312] = byte(0x00) // Animation delay, MSB.
    header[0x313] = byte(0x10) // And it is color #16 (0x10).
    header[0x314] = byte(0x00) // End of GCE data.

    header[0x315] = byte(0x2C) // Start of Image Descriptor.

    header[0x317] = byte(b.Min.X / 255)
    header[0x316] = byte(b.Min.X % 255)
    header[0x319] = byte(b.Min.Y / 255)
    header[0x318] = byte(b.Min.Y % 255)

    header[0x31B] = byte(b.Max.X / 255)
    header[0x31A] = byte(b.Max.X % 255)
    header[0x31D] = byte(b.Max.Y / 255)
    header[0x31C] = byte(b.Max.Y % 255)

    header[0x31E] = byte(0x00) // No local color table.

    header[0x31F] = byte(0x08) // Start of LZW with minimum code size 8.

    w.Write(header)
}

func compressImage(m image.Image) *bytes.Buffer {
    b := m.Bounds()

    compressedImageBuffer := bytes.NewBuffer(make([]byte, 0, 255))
    lzww := lzw.NewWriter(compressedImageBuffer, lzw.LSB, int(8))

    for y := b.Min.Y; y < b.Max.Y; y++ {
        for x := b.Min.X; x < b.Max.X; x++ {
            c := color.GrayModel.Convert(m.At(x, y)).(color.Gray)
            lzww.Write([]byte{c.Y})
        }
    }
    lzww.Close()

    return compressedImageBuffer
}

func writeBlocks(w io.Writer, compressedImage *bytes.Buffer) {
    const maxBlockSize = 255
    bytesSoFar := 0
    bytesRemaining := compressedImage.Len()
    for bytesRemaining > 0 {
        if bytesSoFar == 0 {
            blockSize := math.Min(maxBlockSize, float64(bytesRemaining))
            w.Write([]byte{byte(blockSize)})
        }

        b, _ :=  compressedImage.ReadByte()
        w.Write([]byte{b})

        bytesSoFar = (bytesSoFar + 1) % maxBlockSize
        bytesRemaining--
    }
}

func Encode(w io.Writer, m image.Image) error {
    writeHeader(w, m)
    writeBlocks(w, compressImage(m))
    w.Write([]byte{0, ';'})

    return nil
}

func main() {
    m := image.NewRGBA(image.Rect(0, 0, 100, 100))
    m.Set(1, 1, color.RGBA{0x00, 0xFF, 0x00, 0xFF})
    file, _ := os.Create("new_image.gif")
    Encode(file, m)
}
<|endoftext|>


<commit_before>package closenotifier

import (
	"bufio"
	"errors"
	"io"
	"net"
	"net/http"
)

func makeCloseNotifier(r io.Reader) <-chan bool {
	closeNotifier := make(chan bool, 1)
	go func() {
		buf := make([]byte, 1)
		r.Read(buf)
		closeNotifier <- true
	}()
	return closeNotifier
}

func Hijack(w http.ResponseWriter) (buf *bufio.ReadWriter, closeNotifier <-chan bool, closer func(), err error) {
	var rwc net.Conn
	hijacker, ok := w.(http.Hijacker)
	if !ok {
		err = errors.New("can't hijack")
		return
	}
	rwc, buf, err = hijacker.Hijack()
	if err != nil {
		return
	}
	closer = func() {
		buf.WriteString("0\r\n\r\n")
		buf.Flush()
		buf.Reader.Reset(nil)
		buf.Writer.Reset(nil)
		buf = nil
		rwc.Close()
		rwc = nil
	}
	closeNotifier = makeCloseNotifier(rwc)
	return
}
<commit_msg>not proper for closenotifier to write something special<commit_after>package closenotifier

import (
	"bufio"
	"errors"
	"io"
	"net"
	"net/http"
)

func makeCloseNotifier(r io.Reader) <-chan bool {
	closeNotifier := make(chan bool, 1)
	go func() {
		buf := make([]byte, 1)
		r.Read(buf)
		closeNotifier <- true
	}()
	return closeNotifier
}

func Hijack(w http.ResponseWriter) (buf *bufio.ReadWriter, closeNotifier <-chan bool, closer func(), err error) {
	var rwc net.Conn
	hijacker, ok := w.(http.Hijacker)
	if !ok {
		err = errors.New("can't hijack")
		return
	}
	rwc, buf, err = hijacker.Hijack()
	if err != nil {
		return
	}
	closer = func() {
		buf.Reader.Reset(nil)
		buf.Writer.Reset(nil)
		buf = nil
		rwc.Close()
		rwc = nil
	}
	closeNotifier = makeCloseNotifier(rwc)
	return
}
<|endoftext|>