package gimport 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) } } // ParseMetaGitImport returns meta imports from the HTML in r. // Parsing ends at the end of the section or the beginning of the . 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 "" }