git-import/parse.go

106 lines
2.5 KiB
Go

package main
import (
"encoding/xml"
"fmt"
"io"
"net/url"
"regexp"
"strings"
)
var (
ErrNoImport = fmt.Errorf("no git-import found")
SSHRegex = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*\@[A-Za-z][A-Za-z0-9_\.]*\:(?:\/?[A-Za-z][A-Za-z0-9_\-\.]*)*$`)
)
type GitImport struct {
Name string
HTTP string
SSH string
}
func (g GitImport) String() string {
return fmt.Sprintf("%s %s %s", g.Name, g.HTTP, g.SSH)
}
// charsetReader returns a reader for the given charset.
func charsetReader(charset string, input io.Reader) (io.Reader, error) {
switch strings.ToLower(charset) {
case "ascii":
return input, nil
default:
return nil, fmt.Errorf("can't decode XML document using charset %q", charset)
}
}
// parseMetaGoImports returns meta imports from the HTML in r.
// Parsing ends at the end of the <head> section or the beginning of the <body>.
func parseMetaGitImport(r io.Reader) (gitImport GitImport, err error) {
d := xml.NewDecoder(r)
d.CharsetReader = charsetReader
d.Strict = false
var t xml.Token
for {
t, err = d.RawToken()
if err != nil {
if err == io.EOF {
err = ErrNoImport
}
break
}
if e, ok := t.(xml.StartElement); ok && strings.EqualFold(e.Name.Local, "body") {
err = ErrNoImport
break
}
if e, ok := t.(xml.EndElement); ok && strings.EqualFold(e.Name.Local, "head") {
err = ErrNoImport
break
}
e, ok := t.(xml.StartElement)
if !ok || !strings.EqualFold(e.Name.Local, "meta") {
continue
}
if attrValue(e.Attr, "name") != "git-import" {
continue
}
content := attrValue(e.Attr, "content")
f := strings.Fields(content)
if len(f) >= 2 {
if _, err = url.Parse(f[1]); err != nil {
err = fmt.Errorf("could not parse git-import HTTPS: %v", err)
break
}
gitImport = GitImport{
Name: f[0],
HTTP: f[1],
}
if len(f) >= 3 {
if !SSHRegex.MatchString(f[2]) {
err = fmt.Errorf("could not parse git-import SSH: invalid connection string")
break
}
gitImport.SSH = f[2]
}
err = nil
} else {
err = fmt.Errorf("incorrect number of import arguments\n\n wanted: project_name cloneHTTP://www.myproject.com/repo [git@myproject.com:repo]\n got: %s", content)
}
break
}
return
}
// attrValue returns the attribute value for the case-insensitive key
// `name', or the empty string if nothing is found.
func attrValue(attrs []xml.Attr, name string) string {
for _, a := range attrs {
if strings.EqualFold(a.Name.Local, name) {
return a.Value
}
}
return ""
}