<commit_before>package uritemplates

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

var unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
var reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")

func pctEncode(original string) (result string) {
	for _, b := range []byte(original) {
		if b < 16 {
			result += fmt.Sprintf("%%0%X", b)
		} else {
			result += fmt.Sprintf("%%%X", b)
		}
	}
	return result
}

func escape(s string, allowReserved bool) string {
	var result string
	if allowReserved {
		result = reserved.ReplaceAllStringFunc(s, pctEncode)
	} else {
		result = unreserved.ReplaceAllStringFunc(s, pctEncode)
	}
	return result
}

type UriTemplate struct {
	raw   string
	parts []templatePart
}

func Parse(rawtemplate string) (template *UriTemplate, err error) {
	template = new(UriTemplate)
	template.raw = rawtemplate
	split := strings.Split(rawtemplate, "{")
	template.parts = make([]templatePart, len(split)*2-1)
	for i, s := range split {
		if i == 0 {
			template.parts[i].raw = s
		} else {
			subsplit := strings.Split(s, "}")
			if len(subsplit) != 2 {
				return nil, errors.New("malformed template")
			}
			expression := subsplit[0]
			template.parts[i*2-1] = parseExpression(expression)
			template.parts[i*2].raw = subsplit[1]
		}
	}
	return template, nil
}

const (
	_          = iota
	SIMPLE     = iota
	PLUS       = iota
	SLASH      = iota
	DOT        = iota
	SEMICOLON  = iota
	QUERY      = iota
	AMPERSAND  = iota
	CROSSHATCH = iota
)

type templatePart struct {
	raw           string
	kind          int
	terms         []templateTerm
	first         string
	sep           string
	named         bool
	ifemp         string
	allowReserved bool
}

type templateTerm struct {
	name     string
	explode  bool
	truncate int
}

func parseExpression(expression string) (result templatePart) {
	switch {
	case strings.HasPrefix(expression, "+"):
		result.kind = PLUS
		result.sep = ","
		result.allowReserved = true
	case strings.HasPrefix(expression, "."):
		result.kind = DOT
		result.first = "."
		result.sep = "."
	case strings.HasPrefix(expression, "/"):
		result.kind = SLASH
		result.first = "/"
		result.sep = "/"
	case strings.HasPrefix(expression, ";"):
		result.kind = SEMICOLON
		result.first = ";"
		result.sep = ";"
		result.named = true
	case strings.HasPrefix(expression, "?"):
		result.kind = QUERY
		result.first = "?"
		result.sep = "&"
		result.named = true
		result.ifemp = "="
	case strings.HasPrefix(expression, "&"):
		result.kind = AMPERSAND
		result.first = "&"
		result.sep = "&"
		result.named = true
		result.ifemp = "="
	case strings.HasPrefix(expression, "#"):
		result.kind = CROSSHATCH
		result.first = "#"
		result.sep = ","
		result.allowReserved = true
	default:
		result.kind = SIMPLE
		result.sep = ","
	}
	if result.kind != SIMPLE {
		expression = expression[1:]
	}
	rawterms := strings.Split(expression, ",")
	result.terms = make([]templateTerm, len(rawterms))
	for i, raw := range rawterms {
		result.terms[i] = parseTerm(raw)
	}
	return result
}

func parseTerm(term string) (result templateTerm) {
	if strings.HasSuffix(term, "*") {
		result.explode = true
		term = term[:len(term)-1]
	}
	split := strings.Split(term, ":")
	if len(split) == 1 {
		result.name = term
	} else if len(split) == 2 {
		result.name = split[0]
		parsed, _ := strconv.ParseInt(split[1], 10, 0)
		result.truncate = int(parsed)
	}
	// else error ?
	return result
}

func (self *UriTemplate) ExpandString(values map[string]interface{}) string {
	raw := ""
	for _, p := range self.parts {
		raw = raw + p.expand(values)
	}
	return raw
}<commit_msg>Pass all but three negative tests.<commit_after>package uritemplates

import (
	"errors"
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

var unreserved = regexp.MustCompile("[^A-Za-z0-9\\-._~]")
var reserved = regexp.MustCompile("[^A-Za-z0-9\\-._~:/?#[\\]@!$&'()*+,;=]")
var validname = regexp.MustCompile("^([A-Za-z0-9_\\.]|%[0-9A-Fa-f][0-9A-Fa-f])+$")

func pctEncode(original string) (result string) {
	for _, b := range []byte(original) {
		if b < 16 {
			result += fmt.Sprintf("%%0%X", b)
		} else {
			result += fmt.Sprintf("%%%X", b)
		}
	}
	return result
}

func escape(s string, allowReserved bool) string {
	var result string
	if allowReserved {
		result = reserved.ReplaceAllStringFunc(s, pctEncode)
	} else {
		result = unreserved.ReplaceAllStringFunc(s, pctEncode)
	}
	return result
}

type UriTemplate struct {
	raw   string
	parts []templatePart
}

func Parse(rawtemplate string) (template *UriTemplate, err error) {
	template = new(UriTemplate)
	template.raw = rawtemplate
	split := strings.Split(rawtemplate, "{")
	template.parts = make([]templatePart, len(split)*2-1)
	for i, s := range split {
		if i == 0 {
			if strings.Contains(s, "}") {
				err = errors.New("unexpected }")
				break
			}
			template.parts[i].raw = s
		} else {
			subsplit := strings.Split(s, "}")
			if len(subsplit) != 2 {
				err = errors.New("malformed template")
				break
			}
			expression := subsplit[0]
			template.parts[i*2-1], err = parseExpression(expression)
			if err != nil { break }
			if strings.Contains(subsplit[1], "}") {
				err = errors.New("unexpected }")
				break
			}
			template.parts[i*2].raw = subsplit[1]
		}
	}
	if err != nil { template = nil }
	return template, err
}

const (
	_          = iota
	SIMPLE     = iota
	PLUS       = iota
	SLASH      = iota
	DOT        = iota
	SEMICOLON  = iota
	QUERY      = iota
	AMPERSAND  = iota
	CROSSHATCH = iota
)

type templatePart struct {
	raw           string
	kind          int
	terms         []templateTerm
	first         string
	sep           string
	named         bool
	ifemp         string
	allowReserved bool
}

type templateTerm struct {
	name     string
	explode  bool
	truncate int
}

func parseExpression(expression string) (result templatePart, err error) {
	switch {
	case strings.HasPrefix(expression, "+"):
		result.kind = PLUS
		result.sep = ","
		result.allowReserved = true
	case strings.HasPrefix(expression, "."):
		result.kind = DOT
		result.first = "."
		result.sep = "."
	case strings.HasPrefix(expression, "/"):
		result.kind = SLASH
		result.first = "/"
		result.sep = "/"
	case strings.HasPrefix(expression, ";"):
		result.kind = SEMICOLON
		result.first = ";"
		result.sep = ";"
		result.named = true
	case strings.HasPrefix(expression, "?"):
		result.kind = QUERY
		result.first = "?"
		result.sep = "&"
		result.named = true
		result.ifemp = "="
	case strings.HasPrefix(expression, "&"):
		result.kind = AMPERSAND
		result.first = "&"
		result.sep = "&"
		result.named = true
		result.ifemp = "="
	case strings.HasPrefix(expression, "#"):
		result.kind = CROSSHATCH
		result.first = "#"
		result.sep = ","
		result.allowReserved = true
	default:
		result.kind = SIMPLE
		result.sep = ","
	}
	if result.kind != SIMPLE {
		expression = expression[1:]
	}
	rawterms := strings.Split(expression, ",")
	result.terms = make([]templateTerm, len(rawterms))
	for i, raw := range rawterms {
		result.terms[i], err = parseTerm(raw)
		if err != nil { break }
	}
	return result, err
}

func parseTerm(term string) (result templateTerm, err error) {
	if strings.HasSuffix(term, "*") {
		result.explode = true
		term = term[:len(term)-1]
	}
	split := strings.Split(term, ":")
	if len(split) == 1 {
		result.name = term
	} else if len(split) == 2 {
		result.name = split[0]
		var parsed int64
		parsed, err = strconv.ParseInt(split[1], 10, 0)
		result.truncate = int(parsed)
	} else {
		err = errors.New("multiple colons in same term")
	}
	if !validname.MatchString(result.name) {
		err = errors.New("not a valid name: " + result.name)
	}
	if result.explode && result.truncate > 0 {
		err = errors.New("both explode and prefix modifers on same term")
	}
	return result, err
}

func (self *UriTemplate) ExpandString(values map[string]interface{}) string {
	raw := ""
	for _, p := range self.parts {
		raw = raw + p.expand(values)
	}
	return raw
}
{'fraction_non_alphanumeric': 0.1523329129886507, 'fraction_numerical': 0.007566204287515763, 'mean_word_length': 3.2051961823966066, 'pattern_counts': {'":': 2, '<': 5, '<?xml version=': 0, '>': 4, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6765760', 'n_tokens_mistral': 3077, 'n_tokens_neox': 2841, 'n_words': 639}