Initial checkin.
Only feature so far is accept header parsing.
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..48b8bf9
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+vendor/
diff --git a/accept.go b/accept.go
new file mode 100644
index 0000000..ed7d16e
--- /dev/null
+++ b/accept.go
@@ -0,0 +1,175 @@
+package goscaffold
+
+import (
+	"net/http"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+var qParamRe = regexp.MustCompile("^q=([0-9\\.]+)$")
+
+type acceptCriterion struct {
+	major      string
+	minor      string
+	precedence float32
+	origOrder  int
+	match      string
+}
+type acceptCriteria []acceptCriterion
+
+/*
+SelectMediaType matches a set of candidate media types against the
+Accept header in an HTTP request. It does this using the rules from
+RFC2616 section 1.4.
+If no Accept header is present, then the first choice in the
+"choices" array is returned.
+If multiple choices match, then we will take the first one specified
+in the "Accept" header.
+If there are no compatible media types, then an empty string
+is returned.
+Only the first "Accept" header on the request is considered.
+*/
+func SelectMediaType(req *http.Request, choices []string) string {
+	hdr := req.Header.Get("Accept")
+	if hdr == "" {
+		if len(choices) >= 1 {
+			return choices[0]
+		}
+		return ""
+	}
+
+	// Parse accept header and parse the criteria
+	candidates := parseAcceptHeader(hdr)
+
+	// For each choice, assign the best match (least wildcardy)
+	matches := make(map[string]*acceptCriterion)
+	for _, choice := range choices {
+		for c := range candidates {
+			crit := candidates[c]
+			if crit.matches(choice) {
+				if matches[choice] == nil ||
+					matches[choice].level() < crit.level() {
+					matches[choice] = &acceptCriterion{
+						minor:      crit.minor,
+						major:      crit.major,
+						precedence: crit.precedence,
+						origOrder:  crit.origOrder,
+						match:      choice,
+					}
+				}
+			}
+		}
+	}
+
+	if len(matches) == 0 {
+		return ""
+	}
+
+	// Sort the matches now by precedence level and original order
+	var sortedMatches acceptCriteria
+	for _, v := range matches {
+		sortedMatches = append(sortedMatches, *v)
+	}
+	sortedMatches.sort()
+
+	return sortedMatches[0].match
+}
+
+func parseAcceptHeader(hdr string) acceptCriteria {
+	var ret acceptCriteria
+	parts := strings.Split(hdr, ",")
+	for i, part := range parts {
+		candidate := parseAcceptPart(part, i)
+		ret = append(ret, candidate)
+	}
+	return ret
+}
+
+/*
+parseAcceptPart parses a single section of the header and extracts the
+"q" parameter.
+*/
+func parseAcceptPart(part string, order int) acceptCriterion {
+	params := strings.Split(part, ";")
+	finalType := strings.TrimSpace(params[0])
+	var precedence float32 = 1.0
+
+	for _, param := range params[1:] {
+		match := qParamRe.FindStringSubmatch(strings.TrimSpace(param))
+		if match == nil {
+			finalType += ";" + param
+		} else {
+			qVal, err := strconv.ParseFloat(match[1], 32)
+			if err == nil {
+				precedence = float32(qVal)
+			}
+		}
+	}
+
+	splitType := strings.SplitN(finalType, "/", 2)
+
+	return acceptCriterion{
+		major:      splitType[0],
+		minor:      splitType[1],
+		precedence: precedence,
+		origOrder:  order,
+	}
+}
+
+/*
+matches matches a candidate media type with a criterion.
+*/
+func (a acceptCriterion) matches(t string) bool {
+	st := strings.SplitN(t, "/", 2)
+
+	if a.major != "*" && a.major != st[0] {
+		return false
+	}
+	if a.minor != "*" && a.minor != st[1] {
+		return false
+	}
+	return true
+}
+func (a acceptCriterion) level() int {
+	if a.minor == "*" {
+		if a.major == "*" {
+			return 0
+		}
+		return 1
+	}
+	return 2
+}
+
+/*
+sortCandidates sorts accept header candidates in order of:
+1) Precedence (from the "q" parameter)
+2) Original order in accept header
+3) Stable sort otherwise
+*/
+func (c acceptCriteria) sort() {
+	sort.Stable(c)
+}
+
+func (c acceptCriteria) Less(i, j int) bool {
+	// Higher precedence goes first
+	if c[i].precedence > c[j].precedence {
+		return true
+	}
+	if c[i].precedence == c[j].precedence &&
+		c[i].origOrder < c[j].origOrder {
+		return true
+	}
+	return false
+}
+
+func (c acceptCriteria) Len() int {
+	return len(c)
+}
+
+func (c acceptCriteria) Swap(i, j int) {
+	tmp := c[i]
+	c[i] = c[j]
+	c[j] = tmp
+}
diff --git a/accept_test.go b/accept_test.go
new file mode 100644
index 0000000..2a80b21
--- /dev/null
+++ b/accept_test.go
@@ -0,0 +1,139 @@
+package goscaffold
+
+import (
+	"net/http"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+var _ = Describe("Accept header tests", func() {
+	It("No nothing", func() {
+		Expect(SelectMediaType(
+			makeRequest(""), []string{})).Should(BeEmpty())
+	})
+
+	It("No header, one choice", func() {
+		Expect(SelectMediaType(
+			makeRequest(""),
+			[]string{"text/plain"})).Should(Equal("text/plain"))
+	})
+
+	It("No header, two choices", func() {
+		Expect(SelectMediaType(
+			makeRequest(""),
+			[]string{"text/plain", "text/xml"})).Should(Equal("text/plain"))
+	})
+
+	It("One Header, two choices", func() {
+		Expect(SelectMediaType(
+			makeRequest("application/json"),
+			[]string{"text/plain", "application/json"})).Should(Equal("application/json"))
+	})
+
+	It("One Header, bad choices", func() {
+		Expect(SelectMediaType(
+			makeRequest("application/json"),
+			[]string{"text/plain", "application/xml"})).Should(Equal(""))
+	})
+
+	It("audio/basic", func() {
+		req := makeRequest("audio/*; q=0.2, audio/basic")
+		Expect(SelectMediaType(req,
+			[]string{"audio/mp3", "audio/basic"})).Should(Equal("audio/basic"))
+		Expect(SelectMediaType(req,
+			[]string{"audio/mp3", "text/plain"})).Should(Equal("audio/mp3"))
+		Expect(SelectMediaType(req,
+			[]string{"text/plain", "application/xml"})).Should(Equal(""))
+	})
+
+	It("text/plain", func() {
+		req := makeRequest("text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c")
+		Expect(SelectMediaType(req,
+			[]string{"text/html", "text/x-c"})).Should(Equal("text/html"))
+		Expect(SelectMediaType(req,
+			[]string{"text/x-c", "text/html"})).Should(Equal("text/html"))
+		Expect(SelectMediaType(req,
+			[]string{"text/x-dvi", "text/plain"})).Should(Equal("text/x-dvi"))
+		Expect(SelectMediaType(req,
+			[]string{"text/plain"})).Should(Equal("text/plain"))
+		Expect(SelectMediaType(req,
+			[]string{"application/json"})).Should(Equal(""))
+	})
+
+	It("text/plain 2", func() {
+		req := makeRequest(
+			`text/*;q=0.3, text/html;q=0.7, text/html;level=1,
+		  text/html;level=2;q=0.4, */*;q=0.5`)
+		Expect(SelectMediaType(req,
+			[]string{"text/plain", "text/html;level=1", "text/html", "image/jpeg", "text/html;level=2", "foo/bar"})).
+			Should(Equal("text/html;level=1"))
+		Expect(SelectMediaType(req,
+			[]string{"image/jpeg", "text/html", "text/plain", "text/html;level=2", "foo/bar"})).
+			Should(Equal("text/html"))
+		Expect(SelectMediaType(req,
+			[]string{"image/jpeg", "text/plain", "text/html;level=2"})).
+			Should(Equal("image/jpeg"))
+		Expect(SelectMediaType(req,
+			[]string{"text/plain", "text/html;level=2"})).
+			Should(Equal("text/html;level=2"))
+	})
+
+	It("Weird Browser example", func() {
+		req := makeRequest("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json")
+		Expect(SelectMediaType(req,
+			[]string{"application/xml", "text/xml", "text/html", "application/json"})).
+			Should(Equal("text/html"))
+		Expect(SelectMediaType(req,
+			[]string{"application/xml", "application/json"})).
+			Should(Equal("application/json"))
+	})
+
+	It("Firefox Example", func() {
+		req := makeRequest("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
+		Expect(SelectMediaType(req,
+			[]string{"application/xml", "text/xml", "text/html", "text/plain", "application/json"})).
+			Should(Equal("text/html"))
+		Expect(SelectMediaType(req,
+			[]string{"application/xml", "application/json"})).
+			Should(Equal("application/xml"))
+		Expect(SelectMediaType(req,
+			[]string{"application/json"})).
+			Should(Equal("application/json"))
+	})
+
+	It("Parse accept part", func() {
+		ap := parseAcceptPart("text/plain", 0)
+		Expect(ap.major).Should(Equal("text"))
+		Expect(ap.minor).Should(Equal("plain"))
+		Expect(ap.precedence).Should(BeEquivalentTo(1.0))
+		ap = parseAcceptPart("text/plain;q=0.2", 0)
+		Expect(ap.major).Should(Equal("text"))
+		Expect(ap.minor).Should(Equal("plain"))
+		Expect(ap.precedence).Should(BeNumerically("~", 0.2))
+		ap = parseAcceptPart("text/plain; q=0.2", 0)
+		Expect(ap.major).Should(Equal("text"))
+		Expect(ap.minor).Should(Equal("plain"))
+		Expect(ap.precedence).Should(BeNumerically("~", 0.2))
+		ap = parseAcceptPart("text/plain;level=1", 0)
+		Expect(ap.major).Should(Equal("text"))
+		Expect(ap.minor).Should(Equal("plain;level=1"))
+		Expect(ap.precedence).Should(BeEquivalentTo(1.0))
+		ap = parseAcceptPart("text/plain;level=1;q=0.5", 0)
+		Expect(ap.major).Should(Equal("text"))
+		Expect(ap.minor).Should(Equal("plain;level=1"))
+		Expect(ap.precedence).Should(BeNumerically("~", 0.5))
+		ap = parseAcceptPart("text/plain;level=1;q=0.5;foo=bar", 0)
+		Expect(ap.major).Should(Equal("text"))
+		Expect(ap.minor).Should(Equal("plain;level=1;foo=bar"))
+		Expect(ap.precedence).Should(BeNumerically("~", 0.5))
+	})
+})
+
+func makeRequest(accept string) *http.Request {
+	return &http.Request{
+		Header: http.Header{
+			"Accept": []string{accept},
+		},
+	}
+}
diff --git a/glide.lock b/glide.lock
new file mode 100644
index 0000000..910b296
--- /dev/null
+++ b/glide.lock
@@ -0,0 +1,8 @@
+hash: 9b96c783f272b0a712add71d2d753d1eb07ba91dfe9b42e3532b8d3e3cec639c
+updated: 2016-10-04T12:46:18.025613379-07:00
+imports: []
+testImports:
+- name: github.com/onsi/ginkgo
+  version: 45a5f6ffb2a14e4f29698c16ae4c0a34018a8951
+- name: github.com/onsi/gomega
+  version: d59fa0ac68bb5dd932ee8d24eed631cdd519efc3
diff --git a/glide.yaml b/glide.yaml
new file mode 100644
index 0000000..c151fcc
--- /dev/null
+++ b/glide.yaml
@@ -0,0 +1,4 @@
+package: github.com/30x/go-scaffold
+testImport:
+- package: github.com/onsi/ginkgo
+- package: github.com/onsi/gomega
diff --git a/scaffold_main_test.go b/scaffold_main_test.go
new file mode 100644
index 0000000..a12dc50
--- /dev/null
+++ b/scaffold_main_test.go
@@ -0,0 +1,13 @@
+package goscaffold
+
+import (
+	"testing"
+
+	. "github.com/onsi/ginkgo"
+	. "github.com/onsi/gomega"
+)
+
+func TestScaffold(t *testing.T) {
+	RegisterFailHandler(Fail)
+	RunSpecs(t, "Scaffold Suite")
+}