github.com/motemen/ghq@v1.0.3/go_import.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"net/http"
     7  	"net/url"
     8  	"strings"
     9  
    10  	"golang.org/x/net/html"
    11  )
    12  
    13  // metaImport represents the parsed <meta name="go-import"
    14  // content="prefix vcs reporoot" /> tags from HTML files.
    15  type metaImport struct {
    16  	Prefix, VCS, RepoRoot string
    17  }
    18  
    19  func detectGoImport(u *url.URL) (string, *url.URL, error) {
    20  	goGetU := &url.URL{ // clone
    21  		Scheme:   u.Scheme,
    22  		User:     u.User,
    23  		Host:     u.Host,
    24  		Path:     u.Path,
    25  		RawQuery: u.RawQuery,
    26  	}
    27  	q := goGetU.Query()
    28  	q.Add("go-get", "1")
    29  	goGetU.RawQuery = q.Encode()
    30  
    31  	cli := &http.Client{
    32  		CheckRedirect: func(req *http.Request, via []*http.Request) error {
    33  			// never follow redirection
    34  			return http.ErrUseLastResponse
    35  		},
    36  	}
    37  	req, _ := http.NewRequest(http.MethodGet, goGetU.String(), nil)
    38  	req.Header.Add("User-Agent", fmt.Sprintf("ghq/%s (+https://github.com/motemen/ghq)", version))
    39  	resp, err := cli.Do(req)
    40  	if err != nil {
    41  		return "", nil, err
    42  	}
    43  	defer resp.Body.Close()
    44  
    45  	return detectVCSAndRepoURL(resp.Body)
    46  }
    47  
    48  // find meta tag like following from html
    49  // <meta name="go-import" content="gopkg.in/yaml.v2 git https://gopkg.in/yaml.v2">
    50  // ref. https://golang.org/cmd/go/#hdr-Remote_import_paths
    51  func detectVCSAndRepoURL(r io.Reader) (string, *url.URL, error) {
    52  	doc, err := html.Parse(r)
    53  	if err != nil {
    54  		return "", nil, err
    55  	}
    56  
    57  	var mImport *metaImport
    58  
    59  	var f func(*html.Node)
    60  	f = func(n *html.Node) {
    61  		if mImport != nil {
    62  			return
    63  		}
    64  		if n.Type == html.ElementNode && n.Data == "meta" {
    65  			var (
    66  				goImportMeta = false
    67  				content      = ""
    68  			)
    69  			for _, a := range n.Attr {
    70  				if a.Key == "name" && a.Val == "go-import" {
    71  					goImportMeta = true
    72  					continue
    73  				}
    74  				if a.Key == "content" {
    75  					content = a.Val
    76  				}
    77  			}
    78  			if f := strings.Fields(content); goImportMeta && len(f) == 3 && f[1] != "mod" {
    79  				mImport = &metaImport{
    80  					Prefix:   f[0],
    81  					VCS:      f[1],
    82  					RepoRoot: f[2],
    83  				}
    84  			}
    85  		}
    86  		for c := n.FirstChild; c != nil; c = c.NextSibling {
    87  			f(c)
    88  		}
    89  	}
    90  	f(doc)
    91  
    92  	if mImport == nil {
    93  		return "", nil, fmt.Errorf("no go-import meta tags detected")
    94  	}
    95  	u, err := url.Parse(mImport.RepoRoot)
    96  	if err != nil {
    97  		return "", nil, err
    98  	}
    99  	return mImport.VCS, u, nil
   100  }