mirror of https://github.com/gogits/gogs.git
Lunny Xiao
12 years ago
18 changed files with 2502 additions and 430 deletions
@ -0,0 +1,210 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"archive/tar" |
||||||
|
"bytes" |
||||||
|
"compress/gzip" |
||||||
|
"errors" |
||||||
|
"io" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
bitbucketPattern = regexp.MustCompile(`^bitbucket\.org/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$`) |
||||||
|
bitbucketEtagRe = regexp.MustCompile(`^(hg|git)-`) |
||||||
|
) |
||||||
|
|
||||||
|
// getBitbucketDoc downloads tarball from bitbucket.org.
|
||||||
|
func getBitbucketDoc(client *http.Client, match map[string]string, installRepoPath string, nod *Node, cmdFlags map[string]bool) ([]string, error) { |
||||||
|
// Check version control.
|
||||||
|
if m := bitbucketEtagRe.FindStringSubmatch(nod.Value); m != nil { |
||||||
|
match["vcs"] = m[1] |
||||||
|
} else { |
||||||
|
var repo struct { |
||||||
|
Scm string |
||||||
|
} |
||||||
|
if err := com.HttpGetJSON(client, expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}", match), &repo); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
match["vcs"] = repo.Scm |
||||||
|
} |
||||||
|
|
||||||
|
if nod.Type == BRANCH { |
||||||
|
if len(nod.Value) == 0 { |
||||||
|
match["commit"] = defaultTags[match["vcs"]] |
||||||
|
} else { |
||||||
|
match["commit"] = nod.Value |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if nod.IsGetDeps { |
||||||
|
if nod.Type == COMMIT { |
||||||
|
tags := make(map[string]string) |
||||||
|
for _, nodeType := range []string{"branches", "tags"} { |
||||||
|
var nodes map[string]struct { |
||||||
|
Node string |
||||||
|
} |
||||||
|
if err := com.HttpGetJSON(client, expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/{0}", match, nodeType), &nodes); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
for t, n := range nodes { |
||||||
|
tags[t] = n.Node |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Check revision tag.
|
||||||
|
var err error |
||||||
|
match["tag"], match["commit"], err = bestTag(tags, defaultTags[match["vcs"]]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
nod.Value = match["commit"] |
||||||
|
} |
||||||
|
} else { |
||||||
|
// Check downlaod type.
|
||||||
|
switch nod.Type { |
||||||
|
case TAG, COMMIT, BRANCH: |
||||||
|
match["commit"] = nod.Value |
||||||
|
default: |
||||||
|
return nil, errors.New("Unknown node type: " + nod.Type) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// We use .tar.gz here.
|
||||||
|
// zip : https://bitbucket.org/{owner}/{repo}/get/{commit}.zip
|
||||||
|
// tarball : https://bitbucket.org/{owner}/{repo}/get/{commit}.tar.gz
|
||||||
|
|
||||||
|
// Downlaod archive.
|
||||||
|
p, err := com.HttpGetBytes(client, expand("https://bitbucket.org/{owner}/{repo}/get/{commit}.tar.gz", match), nil) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
var installPath string |
||||||
|
if nod.ImportPath == nod.DownloadURL { |
||||||
|
suf := "." + nod.Value |
||||||
|
if len(suf) == 1 { |
||||||
|
suf = "" |
||||||
|
} |
||||||
|
projectPath := expand("bitbucket.org/{owner}/{repo}", match) |
||||||
|
installPath = installRepoPath + "/" + projectPath + suf |
||||||
|
nod.ImportPath = projectPath |
||||||
|
} else { |
||||||
|
installPath = installRepoPath + "/" + nod.ImportPath |
||||||
|
} |
||||||
|
|
||||||
|
// Remove old files.
|
||||||
|
os.RemoveAll(installPath + "/") |
||||||
|
os.MkdirAll(installPath+"/", os.ModePerm) |
||||||
|
|
||||||
|
gzr, err := gzip.NewReader(bytes.NewReader(p)) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer gzr.Close() |
||||||
|
|
||||||
|
tr := tar.NewReader(gzr) |
||||||
|
|
||||||
|
var autoPath string // Auto path is the root path that generated by bitbucket.org.
|
||||||
|
// Get source file data.
|
||||||
|
dirs := make([]string, 0, 5) |
||||||
|
for { |
||||||
|
h, err := tr.Next() |
||||||
|
if err == io.EOF { |
||||||
|
break |
||||||
|
} else if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
fn := h.FileInfo().Name() |
||||||
|
|
||||||
|
// In case that we find directory, usually we should not.
|
||||||
|
if strings.HasSuffix(fn, "/") { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Check root path.
|
||||||
|
if len(autoPath) == 0 { |
||||||
|
autoPath = fn[:strings.Index(fn, "/")] |
||||||
|
} |
||||||
|
absPath := strings.Replace(fn, autoPath, installPath, 1) |
||||||
|
|
||||||
|
// Create diretory before create file.
|
||||||
|
dir := path.Dir(absPath) |
||||||
|
if !checkDir(dir, dirs) && !(!cmdFlags["-e"] && strings.Contains(absPath, "example")) { |
||||||
|
dirs = append(dirs, dir) |
||||||
|
os.MkdirAll(dir+"/", os.ModePerm) |
||||||
|
} |
||||||
|
|
||||||
|
if strings.HasPrefix(fn, ".") { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// Get data from archive.
|
||||||
|
fbytes := make([]byte, h.Size) |
||||||
|
if _, err := io.ReadFull(tr, fbytes); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Write data to file
|
||||||
|
fw, err := os.Create(absPath) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
_, err = fw.Write(fbytes) |
||||||
|
fw.Close() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Set modify time.
|
||||||
|
os.Chtimes(absPath, h.AccessTime, h.ModTime) |
||||||
|
} |
||||||
|
|
||||||
|
var imports []string |
||||||
|
|
||||||
|
// Check if need to check imports.
|
||||||
|
if nod.IsGetDeps { |
||||||
|
for _, d := range dirs { |
||||||
|
importPkgs, err := CheckImports(d+"/", match["importPath"]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
imports = append(imports, importPkgs...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return imports, err |
||||||
|
} |
||||||
|
|
||||||
|
// checkDir checks if current directory has been saved.
|
||||||
|
func checkDir(dir string, dirs []string) bool { |
||||||
|
for _, d := range dirs { |
||||||
|
if dir == d { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
@ -0,0 +1,25 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
errNotModified = errors.New("Package not modified") |
||||||
|
errNoMatch = errors.New("no match") |
||||||
|
errUpdateTimeout = errors.New("update timeout") |
||||||
|
) |
@ -0,0 +1,174 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"archive/zip" |
||||||
|
"bytes" |
||||||
|
"errors" |
||||||
|
"io" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
githubRawHeader = http.Header{"Accept": {"application/vnd.github-blob.raw"}} |
||||||
|
githubPattern = regexp.MustCompile(`^github\.com/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$`) |
||||||
|
githubCred string |
||||||
|
) |
||||||
|
|
||||||
|
/*func SetGithubCredentials(id, secret string) { |
||||||
|
//githubCred = "client_id=" + id + "&client_secret=" + secret
|
||||||
|
}*/ |
||||||
|
|
||||||
|
func SetGithubCredentials(token string) { |
||||||
|
if len(token) > 0 { |
||||||
|
githubCred = "access_token=" + token |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// getGithubDoc downloads tarball from github.com.
|
||||||
|
func getGithubDoc(client *http.Client, match map[string]string, installRepoPath string, nod *Node, cmdFlags map[string]bool) ([]string, error) { |
||||||
|
match["cred"] = githubCred |
||||||
|
|
||||||
|
// Check downlaod type.
|
||||||
|
switch nod.Type { |
||||||
|
case BRANCH: |
||||||
|
if len(nod.Value) == 0 { |
||||||
|
match["sha"] = MASTER |
||||||
|
} else { |
||||||
|
match["sha"] = nod.Value |
||||||
|
} |
||||||
|
case TAG, COMMIT: |
||||||
|
match["sha"] = nod.Value |
||||||
|
default: |
||||||
|
return nil, errors.New("Unknown node type: " + nod.Type) |
||||||
|
} |
||||||
|
|
||||||
|
// We use .zip here.
|
||||||
|
// zip: https://github.com/{owner}/{repo}/archive/{sha}.zip
|
||||||
|
// tarball: https://github.com/{owner}/{repo}/tarball/{sha}
|
||||||
|
|
||||||
|
// Downlaod archive.
|
||||||
|
p, err := com.HttpGetBytes(client, expand("https://github.com/{owner}/{repo}/archive/{sha}.zip", match), nil) |
||||||
|
if err != nil { |
||||||
|
return nil, errors.New("Fail to donwload Github repo -> " + err.Error()) |
||||||
|
} |
||||||
|
|
||||||
|
shaName := expand("{repo}-{sha}", match) |
||||||
|
if nod.Type == "tag" { |
||||||
|
shaName = strings.Replace(shaName, "-v", "-", 1) |
||||||
|
} |
||||||
|
|
||||||
|
var installPath string |
||||||
|
if nod.ImportPath == nod.DownloadURL { |
||||||
|
suf := "." + nod.Value |
||||||
|
if len(suf) == 1 { |
||||||
|
suf = "" |
||||||
|
} |
||||||
|
projectPath := expand("github.com/{owner}/{repo}", match) |
||||||
|
installPath = installRepoPath + "/" + projectPath + suf |
||||||
|
nod.ImportPath = projectPath |
||||||
|
} else { |
||||||
|
installPath = installRepoPath + "/" + nod.ImportPath |
||||||
|
} |
||||||
|
|
||||||
|
// Remove old files.
|
||||||
|
os.RemoveAll(installPath + "/") |
||||||
|
os.MkdirAll(installPath+"/", os.ModePerm) |
||||||
|
|
||||||
|
r, err := zip.NewReader(bytes.NewReader(p), int64(len(p))) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
dirs := make([]string, 0, 5) |
||||||
|
// Need to add root path because we cannot get from tarball.
|
||||||
|
dirs = append(dirs, installPath+"/") |
||||||
|
for _, f := range r.File { |
||||||
|
absPath := strings.Replace(f.FileInfo().Name(), shaName, installPath, 1) |
||||||
|
// Create diretory before create file.
|
||||||
|
os.MkdirAll(path.Dir(absPath)+"/", os.ModePerm) |
||||||
|
|
||||||
|
compareDir: |
||||||
|
switch { |
||||||
|
case strings.HasSuffix(absPath, "/"): // Directory.
|
||||||
|
// Check if current directory is example.
|
||||||
|
if !(!cmdFlags["-e"] && strings.Contains(absPath, "example")) { |
||||||
|
for _, d := range dirs { |
||||||
|
if d == absPath { |
||||||
|
break compareDir |
||||||
|
} |
||||||
|
} |
||||||
|
dirs = append(dirs, absPath) |
||||||
|
} |
||||||
|
case !strings.HasPrefix(f.FileInfo().Name(), "."): |
||||||
|
// Get file from archive.
|
||||||
|
rc, err := f.Open() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Write data to file
|
||||||
|
fw, _ := os.Create(absPath) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
_, err = io.Copy(fw, rc) |
||||||
|
// Close files.
|
||||||
|
rc.Close() |
||||||
|
fw.Close() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Set modify time.
|
||||||
|
os.Chtimes(absPath, f.ModTime(), f.ModTime()) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var imports []string |
||||||
|
|
||||||
|
// Check if need to check imports.
|
||||||
|
if nod.IsGetDeps { |
||||||
|
for _, d := range dirs { |
||||||
|
importPkgs, err := CheckImports(d, match["importPath"]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
imports = append(imports, importPkgs...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/*fpath := appPath + "repo/tarballs/" + node.ImportPath + "-" + node.Value + ".zip" |
||||||
|
// Save tarball.
|
||||||
|
if autoBackup && !utils.IsExist(fpath) { |
||||||
|
os.MkdirAll(path.Dir(fpath)+"/", os.ModePerm) |
||||||
|
f, err := os.Create(fpath) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer f.Close() |
||||||
|
_, err = f.Write(p) |
||||||
|
}*/ |
||||||
|
|
||||||
|
return imports, err |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
"regexp" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
"github.com/Unknwon/ctw/packer" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
googlePattern = regexp.MustCompile(`^code\.google\.com/p/(?P<repo>[a-z0-9\-]+)(:?\.(?P<subrepo>[a-z0-9\-]+))?(?P<dir>/[a-z0-9A-Z_.\-/]+)?$`) |
||||||
|
) |
||||||
|
|
||||||
|
// getGoogleDoc downloads raw files from code.google.com.
|
||||||
|
func getGoogleDoc(client *http.Client, match map[string]string, installRepoPath string, nod *Node, cmdFlags map[string]bool) ([]string, error) { |
||||||
|
packer.SetupGoogleMatch(match) |
||||||
|
// Check version control.
|
||||||
|
if err := packer.GetGoogleVCS(client, match); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
var installPath string |
||||||
|
if nod.ImportPath == nod.DownloadURL { |
||||||
|
suf := "." + nod.Value |
||||||
|
if len(suf) == 1 { |
||||||
|
suf = "" |
||||||
|
} |
||||||
|
projectPath := expand("code.google.com/p/{repo}{dot}{subrepo}{dir}", match) |
||||||
|
installPath = installRepoPath + "/" + projectPath + suf |
||||||
|
nod.ImportPath = projectPath |
||||||
|
} else { |
||||||
|
installPath = installRepoPath + "/" + nod.ImportPath |
||||||
|
} |
||||||
|
|
||||||
|
// Remove old files.
|
||||||
|
os.RemoveAll(installPath + "/") |
||||||
|
match["tag"] = nod.Value |
||||||
|
|
||||||
|
ext := ".zip" |
||||||
|
if match["vcs"] == "svn" { |
||||||
|
ext = ".tar.gz" |
||||||
|
} |
||||||
|
|
||||||
|
err := packer.PackToFile(match["importPath"], installPath+ext, match) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
var dirs []string |
||||||
|
if match["vcs"] != "svn" { |
||||||
|
dirs, err = com.Unzip(installPath+ext, path.Dir(installPath)) |
||||||
|
} else { |
||||||
|
dirs, err = com.UnTarGz(installPath+ext, path.Dir(installPath)) |
||||||
|
} |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
os.Remove(installPath + ext) |
||||||
|
os.Rename(path.Dir(installPath)+"/"+dirs[0], installPath) |
||||||
|
|
||||||
|
// Check if need to check imports.
|
||||||
|
if nod.IsGetDeps { |
||||||
|
imports := getImports(installPath+"/", match, cmdFlags) |
||||||
|
return imports, err |
||||||
|
} |
||||||
|
|
||||||
|
return nil, err |
||||||
|
} |
@ -0,0 +1,52 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"flag" |
||||||
|
"net" |
||||||
|
"net/http" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
dialTimeout = flag.Duration("dial_timeout", 10*time.Second, "Timeout for dialing an HTTP connection.") |
||||||
|
requestTimeout = flag.Duration("request_timeout", 20*time.Second, "Time out for roundtripping an HTTP request.") |
||||||
|
) |
||||||
|
|
||||||
|
func timeoutDial(network, addr string) (net.Conn, error) { |
||||||
|
return net.DialTimeout(network, addr, *dialTimeout) |
||||||
|
} |
||||||
|
|
||||||
|
type transport struct { |
||||||
|
t http.Transport |
||||||
|
} |
||||||
|
|
||||||
|
func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { |
||||||
|
timer := time.AfterFunc(*requestTimeout, func() { |
||||||
|
t.t.CancelRequest(req) |
||||||
|
com.ColorLog("[WARN] Canceled request for %s, please interrupt the program.\n", req.URL) |
||||||
|
}) |
||||||
|
defer timer.Stop() |
||||||
|
resp, err := t.t.RoundTrip(req) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
var ( |
||||||
|
httpTransport = &transport{t: http.Transport{Dial: timeoutDial, ResponseHeaderTimeout: *requestTimeout / 2}} |
||||||
|
HttpClient = &http.Client{Transport: httpTransport} |
||||||
|
) |
@ -0,0 +1,140 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"archive/tar" |
||||||
|
"bytes" |
||||||
|
"compress/gzip" |
||||||
|
"io" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
) |
||||||
|
|
||||||
|
var launchpadPattern = regexp.MustCompile(`^launchpad\.net/(?P<repo>(?P<project>[a-z0-9A-Z_.\-]+)(?P<series>/[a-z0-9A-Z_.\-]+)?|~[a-z0-9A-Z_.\-]+/(\+junk|[a-z0-9A-Z_.\-]+)/[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]+)*$`) |
||||||
|
|
||||||
|
// getLaunchpadDoc downloads tarball from launchpad.net.
|
||||||
|
func getLaunchpadDoc(client *http.Client, match map[string]string, installRepoPath string, nod *Node, cmdFlags map[string]bool) ([]string, error) { |
||||||
|
|
||||||
|
if match["project"] != "" && match["series"] != "" { |
||||||
|
rc, err := com.HttpGet(client, expand("https://code.launchpad.net/{project}{series}/.bzr/branch-format", match), nil) |
||||||
|
_, isNotFound := err.(com.NotFoundError) |
||||||
|
switch { |
||||||
|
case err == nil: |
||||||
|
rc.Close() |
||||||
|
// The structure of the import path is launchpad.net/{root}/{dir}.
|
||||||
|
case isNotFound: |
||||||
|
// The structure of the import path is is launchpad.net/{project}/{dir}.
|
||||||
|
match["repo"] = match["project"] |
||||||
|
match["dir"] = expand("{series}{dir}", match) |
||||||
|
default: |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var downloadPath string |
||||||
|
// Check if download with specific revision.
|
||||||
|
if len(nod.Value) == 0 { |
||||||
|
downloadPath = expand("https://bazaar.launchpad.net/+branch/{repo}/tarball", match) |
||||||
|
} else { |
||||||
|
downloadPath = expand("https://bazaar.launchpad.net/+branch/{repo}/tarball/"+nod.Value, match) |
||||||
|
} |
||||||
|
|
||||||
|
// Scrape the repo browser to find the project revision and individual Go files.
|
||||||
|
p, err := com.HttpGetBytes(client, downloadPath, nil) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
installPath := installRepoPath + "/" + nod.ImportPath |
||||||
|
|
||||||
|
// Remove old files.
|
||||||
|
os.RemoveAll(installPath + "/") |
||||||
|
os.MkdirAll(installPath+"/", os.ModePerm) |
||||||
|
|
||||||
|
gzr, err := gzip.NewReader(bytes.NewReader(p)) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer gzr.Close() |
||||||
|
|
||||||
|
tr := tar.NewReader(gzr) |
||||||
|
|
||||||
|
var autoPath string // Auto path is the root path that generated by bitbucket.org.
|
||||||
|
// Get source file data.
|
||||||
|
dirs := make([]string, 0, 5) |
||||||
|
for { |
||||||
|
h, err := tr.Next() |
||||||
|
if err == io.EOF { |
||||||
|
break |
||||||
|
} else if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
fn := h.FileInfo().Name() |
||||||
|
// Check root path.
|
||||||
|
if len(autoPath) == 0 { |
||||||
|
autoPath = fn[:strings.Index(fn, match["repo"])+len(match["repo"])] |
||||||
|
} |
||||||
|
absPath := strings.Replace(fn, autoPath, installPath, 1) |
||||||
|
|
||||||
|
switch { |
||||||
|
case h.FileInfo().IsDir(): // Directory.
|
||||||
|
// Create diretory before create file.
|
||||||
|
os.MkdirAll(absPath+"/", os.ModePerm) |
||||||
|
// Check if current directory is example.
|
||||||
|
if !(!cmdFlags["-e"] && strings.Contains(absPath, "example")) { |
||||||
|
dirs = append(dirs, absPath) |
||||||
|
} |
||||||
|
case !strings.HasPrefix(fn, "."): |
||||||
|
// Get data from archive.
|
||||||
|
fbytes := make([]byte, h.Size) |
||||||
|
if _, err := io.ReadFull(tr, fbytes); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
// Write data to file
|
||||||
|
fw, err := os.Create(absPath) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
_, err = fw.Write(fbytes) |
||||||
|
fw.Close() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var imports []string |
||||||
|
|
||||||
|
// Check if need to check imports.
|
||||||
|
if nod.IsGetDeps { |
||||||
|
for _, d := range dirs { |
||||||
|
importPkgs, err := CheckImports(d+"/", match["importPath"]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
imports = append(imports, importPkgs...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return imports, err |
||||||
|
} |
@ -0,0 +1,135 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"archive/zip" |
||||||
|
"bytes" |
||||||
|
"errors" |
||||||
|
"io" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"regexp" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
oscTagRe = regexp.MustCompile(`/repository/archive\?ref=(.*)">`) |
||||||
|
oscPattern = regexp.MustCompile(`^git\.oschina\.net/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$`) |
||||||
|
) |
||||||
|
|
||||||
|
// getGithubDoc downloads tarball from git.oschina.com.
|
||||||
|
func getOSCDoc(client *http.Client, match map[string]string, installRepoPath string, nod *Node, cmdFlags map[string]bool) ([]string, error) { |
||||||
|
// Check downlaod type.
|
||||||
|
switch nod.Type { |
||||||
|
case BRANCH: |
||||||
|
if len(nod.Value) == 0 { |
||||||
|
match["sha"] = MASTER |
||||||
|
} else { |
||||||
|
match["sha"] = nod.Value |
||||||
|
} |
||||||
|
case TAG, COMMIT: |
||||||
|
match["sha"] = nod.Value |
||||||
|
default: |
||||||
|
return nil, errors.New("Unknown node type: " + nod.Type) |
||||||
|
} |
||||||
|
|
||||||
|
// zip: http://{projectRoot}/repository/archive?ref={sha}
|
||||||
|
|
||||||
|
// Downlaod archive.
|
||||||
|
p, err := com.HttpGetBytes(client, expand("http://git.oschina.net/{owner}/{repo}/repository/archive?ref={sha}", match), nil) |
||||||
|
if err != nil { |
||||||
|
return nil, errors.New("Fail to donwload OSChina repo -> " + err.Error()) |
||||||
|
} |
||||||
|
|
||||||
|
var installPath string |
||||||
|
if nod.ImportPath == nod.DownloadURL { |
||||||
|
suf := "." + nod.Value |
||||||
|
if len(suf) == 1 { |
||||||
|
suf = "" |
||||||
|
} |
||||||
|
projectPath := expand("git.oschina.net/{owner}/{repo}", match) |
||||||
|
installPath = installRepoPath + "/" + projectPath + suf |
||||||
|
nod.ImportPath = projectPath |
||||||
|
} else { |
||||||
|
installPath = installRepoPath + "/" + nod.ImportPath |
||||||
|
} |
||||||
|
|
||||||
|
// Remove old files.
|
||||||
|
os.RemoveAll(installPath + "/") |
||||||
|
os.MkdirAll(installPath+"/", os.ModePerm) |
||||||
|
|
||||||
|
r, err := zip.NewReader(bytes.NewReader(p), int64(len(p))) |
||||||
|
if err != nil { |
||||||
|
return nil, errors.New("Fail to unzip OSChina repo -> " + err.Error()) |
||||||
|
} |
||||||
|
|
||||||
|
nameLen := len(match["repo"]) |
||||||
|
dirs := make([]string, 0, 5) |
||||||
|
// Need to add root path because we cannot get from tarball.
|
||||||
|
dirs = append(dirs, installPath+"/") |
||||||
|
for _, f := range r.File { |
||||||
|
fileName := f.FileInfo().Name()[nameLen+1:] |
||||||
|
absPath := installPath + "/" + fileName |
||||||
|
|
||||||
|
if strings.HasSuffix(absPath, "/") { |
||||||
|
dirs = append(dirs, absPath) |
||||||
|
os.MkdirAll(absPath, os.ModePerm) |
||||||
|
continue |
||||||
|
} |
||||||
|
// d, _ := path.Split(absPath)
|
||||||
|
// if !checkDir(d, dirs) {
|
||||||
|
// dirs = append(dirs, d)
|
||||||
|
// os.MkdirAll(d, os.ModePerm)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Get file from archive.
|
||||||
|
rc, err := f.Open() |
||||||
|
if err != nil { |
||||||
|
return nil, errors.New("Fail to open OSChina repo -> " + err.Error()) |
||||||
|
} |
||||||
|
|
||||||
|
// Write data to file
|
||||||
|
fw, _ := os.Create(absPath) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
_, err = io.Copy(fw, rc) |
||||||
|
// Close files.
|
||||||
|
rc.Close() |
||||||
|
fw.Close() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
var imports []string |
||||||
|
|
||||||
|
// Check if need to check imports.
|
||||||
|
if nod.IsGetDeps { |
||||||
|
for _, d := range dirs { |
||||||
|
importPkgs, err := CheckImports(d, match["importPath"]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
imports = append(imports, importPkgs...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return imports, err |
||||||
|
} |
@ -0,0 +1,85 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"go/token" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"regexp" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
TRUNK = "trunk" |
||||||
|
MASTER = "master" |
||||||
|
DEFAULT = "default" |
||||||
|
TAG = "tag" |
||||||
|
BRANCH = "branch" |
||||||
|
COMMIT = "commit" |
||||||
|
) |
||||||
|
|
||||||
|
type Node struct { |
||||||
|
ImportPath string |
||||||
|
DownloadURL string |
||||||
|
Type string |
||||||
|
Value string // Branch, tag or commit.
|
||||||
|
IsGetDeps bool |
||||||
|
} |
||||||
|
|
||||||
|
func (nod *Node) VerString() string { |
||||||
|
if nod.Value == "" { |
||||||
|
return nod.Type |
||||||
|
} |
||||||
|
return fmt.Sprintf("%v:%v", nod.Type, nod.Value) |
||||||
|
} |
||||||
|
|
||||||
|
// source is source code file.
|
||||||
|
type source struct { |
||||||
|
rawURL string |
||||||
|
name string |
||||||
|
data []byte |
||||||
|
} |
||||||
|
|
||||||
|
func (s *source) Name() string { return s.name } |
||||||
|
func (s *source) Size() int64 { return int64(len(s.data)) } |
||||||
|
func (s *source) Mode() os.FileMode { return 0 } |
||||||
|
func (s *source) ModTime() time.Time { return time.Time{} } |
||||||
|
func (s *source) IsDir() bool { return false } |
||||||
|
func (s *source) Sys() interface{} { return nil } |
||||||
|
|
||||||
|
// walker holds the state used when building the documentation.
|
||||||
|
type walker struct { |
||||||
|
ImportPath string |
||||||
|
srcs map[string]*source // Source files.
|
||||||
|
fset *token.FileSet |
||||||
|
} |
||||||
|
|
||||||
|
// service represents a source code control service.
|
||||||
|
type service struct { |
||||||
|
pattern *regexp.Regexp |
||||||
|
prefix string |
||||||
|
get func(*http.Client, map[string]string, string, *Node, map[string]bool) ([]string, error) |
||||||
|
} |
||||||
|
|
||||||
|
// services is the list of source code control services handled by gopkgdoc.
|
||||||
|
var services = []*service{ |
||||||
|
{githubPattern, "github.com/", getGithubDoc}, |
||||||
|
{googlePattern, "code.google.com/", getGoogleDoc}, |
||||||
|
{bitbucketPattern, "bitbucket.org/", getBitbucketDoc}, |
||||||
|
{launchpadPattern, "launchpad.net/", getLaunchpadDoc}, |
||||||
|
{oscPattern, "git.oschina.net/", getOSCDoc}, |
||||||
|
} |
@ -0,0 +1,417 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"encoding/xml" |
||||||
|
"errors" |
||||||
|
"io" |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"net/http" |
||||||
|
"os" |
||||||
|
"os/exec" |
||||||
|
"path" |
||||||
|
"regexp" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/Unknwon/com" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
appPath string |
||||||
|
autoBackup bool |
||||||
|
) |
||||||
|
|
||||||
|
func SetAppConfig(path string, backup bool) { |
||||||
|
appPath = path |
||||||
|
autoBackup = backup |
||||||
|
} |
||||||
|
|
||||||
|
// TODO: specify with command line flag
|
||||||
|
const repoRoot = "/tmp/gddo" |
||||||
|
|
||||||
|
var urlTemplates = []struct { |
||||||
|
re *regexp.Regexp |
||||||
|
template string |
||||||
|
lineFmt string |
||||||
|
}{ |
||||||
|
{ |
||||||
|
regexp.MustCompile(`^git\.gitorious\.org/(?P<repo>[^/]+/[^/]+)$`), |
||||||
|
"https://gitorious.org/{repo}/blobs/{tag}/{dir}{0}", |
||||||
|
"#line%d", |
||||||
|
}, |
||||||
|
{ |
||||||
|
regexp.MustCompile(`^camlistore\.org/r/p/(?P<repo>[^/]+)$`), |
||||||
|
"http://camlistore.org/code/?p={repo}.git;hb={tag};f={dir}{0}", |
||||||
|
"#l%d", |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
// lookupURLTemplate finds an expand() template, match map and line number
|
||||||
|
// format for well known repositories.
|
||||||
|
func lookupURLTemplate(repo, dir, tag string) (string, map[string]string, string) { |
||||||
|
if strings.HasPrefix(dir, "/") { |
||||||
|
dir = dir[1:] + "/" |
||||||
|
} |
||||||
|
for _, t := range urlTemplates { |
||||||
|
if m := t.re.FindStringSubmatch(repo); m != nil { |
||||||
|
match := map[string]string{ |
||||||
|
"dir": dir, |
||||||
|
"tag": tag, |
||||||
|
} |
||||||
|
for i, name := range t.re.SubexpNames() { |
||||||
|
if name != "" { |
||||||
|
match[name] = m[i] |
||||||
|
} |
||||||
|
} |
||||||
|
return t.template, match, t.lineFmt |
||||||
|
} |
||||||
|
} |
||||||
|
return "", nil, "" |
||||||
|
} |
||||||
|
|
||||||
|
type vcsCmd struct { |
||||||
|
schemes []string |
||||||
|
download func([]string, string, string) (string, string, error) |
||||||
|
} |
||||||
|
|
||||||
|
var vcsCmds = map[string]*vcsCmd{ |
||||||
|
"git": &vcsCmd{ |
||||||
|
schemes: []string{"http", "https", "git"}, |
||||||
|
download: downloadGit, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
var lsremoteRe = regexp.MustCompile(`(?m)^([0-9a-f]{40})\s+refs/(?:tags|heads)/(.+)$`) |
||||||
|
|
||||||
|
func downloadGit(schemes []string, repo, savedEtag string) (string, string, error) { |
||||||
|
var p []byte |
||||||
|
var scheme string |
||||||
|
for i := range schemes { |
||||||
|
cmd := exec.Command("git", "ls-remote", "--heads", "--tags", schemes[i]+"://"+repo+".git") |
||||||
|
log.Println(strings.Join(cmd.Args, " ")) |
||||||
|
var err error |
||||||
|
p, err = cmd.Output() |
||||||
|
if err == nil { |
||||||
|
scheme = schemes[i] |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if scheme == "" { |
||||||
|
return "", "", com.NotFoundError{"VCS not found"} |
||||||
|
} |
||||||
|
|
||||||
|
tags := make(map[string]string) |
||||||
|
for _, m := range lsremoteRe.FindAllSubmatch(p, -1) { |
||||||
|
tags[string(m[2])] = string(m[1]) |
||||||
|
} |
||||||
|
|
||||||
|
tag, commit, err := bestTag(tags, "master") |
||||||
|
if err != nil { |
||||||
|
return "", "", err |
||||||
|
} |
||||||
|
|
||||||
|
etag := scheme + "-" + commit |
||||||
|
|
||||||
|
if etag == savedEtag { |
||||||
|
return "", "", errNotModified |
||||||
|
} |
||||||
|
|
||||||
|
dir := path.Join(repoRoot, repo+".git") |
||||||
|
p, err = ioutil.ReadFile(path.Join(dir, ".git/HEAD")) |
||||||
|
switch { |
||||||
|
case err != nil: |
||||||
|
if err := os.MkdirAll(dir, 0777); err != nil { |
||||||
|
return "", "", err |
||||||
|
} |
||||||
|
cmd := exec.Command("git", "clone", scheme+"://"+repo, dir) |
||||||
|
log.Println(strings.Join(cmd.Args, " ")) |
||||||
|
if err := cmd.Run(); err != nil { |
||||||
|
return "", "", err |
||||||
|
} |
||||||
|
case string(bytes.TrimRight(p, "\n")) == commit: |
||||||
|
return tag, etag, nil |
||||||
|
default: |
||||||
|
cmd := exec.Command("git", "fetch") |
||||||
|
log.Println(strings.Join(cmd.Args, " ")) |
||||||
|
cmd.Dir = dir |
||||||
|
if err := cmd.Run(); err != nil { |
||||||
|
return "", "", err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
cmd := exec.Command("git", "checkout", "--detach", "--force", commit) |
||||||
|
cmd.Dir = dir |
||||||
|
if err := cmd.Run(); err != nil { |
||||||
|
return "", "", err |
||||||
|
} |
||||||
|
|
||||||
|
return tag, etag, nil |
||||||
|
} |
||||||
|
|
||||||
|
var defaultTags = map[string]string{"git": "master", "hg": "default"} |
||||||
|
|
||||||
|
func bestTag(tags map[string]string, defaultTag string) (string, string, error) { |
||||||
|
if commit, ok := tags["go1"]; ok { |
||||||
|
return "go1", commit, nil |
||||||
|
} |
||||||
|
if commit, ok := tags[defaultTag]; ok { |
||||||
|
return defaultTag, commit, nil |
||||||
|
} |
||||||
|
return "", "", com.NotFoundError{"Tag or branch not found."} |
||||||
|
} |
||||||
|
|
||||||
|
// expand replaces {k} in template with match[k] or subs[atoi(k)] if k is not in match.
|
||||||
|
func expand(template string, match map[string]string, subs ...string) string { |
||||||
|
var p []byte |
||||||
|
var i int |
||||||
|
for { |
||||||
|
i = strings.Index(template, "{") |
||||||
|
if i < 0 { |
||||||
|
break |
||||||
|
} |
||||||
|
p = append(p, template[:i]...) |
||||||
|
template = template[i+1:] |
||||||
|
i = strings.Index(template, "}") |
||||||
|
if s, ok := match[template[:i]]; ok { |
||||||
|
p = append(p, s...) |
||||||
|
} else { |
||||||
|
j, _ := strconv.Atoi(template[:i]) |
||||||
|
p = append(p, subs[j]...) |
||||||
|
} |
||||||
|
template = template[i+1:] |
||||||
|
} |
||||||
|
p = append(p, template...) |
||||||
|
return string(p) |
||||||
|
} |
||||||
|
|
||||||
|
// PureDownload downloads package without version control.
|
||||||
|
func PureDownload(nod *Node, installRepoPath string, flags map[string]bool) ([]string, error) { |
||||||
|
for _, s := range services { |
||||||
|
if s.get == nil || !strings.HasPrefix(nod.DownloadURL, s.prefix) { |
||||||
|
continue |
||||||
|
} |
||||||
|
m := s.pattern.FindStringSubmatch(nod.DownloadURL) |
||||||
|
if m == nil { |
||||||
|
if s.prefix != "" { |
||||||
|
return nil, errors.New("Cannot match package service prefix by given path") |
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
match := map[string]string{"importPath": nod.DownloadURL} |
||||||
|
for i, n := range s.pattern.SubexpNames() { |
||||||
|
if n != "" { |
||||||
|
match[n] = m[i] |
||||||
|
} |
||||||
|
} |
||||||
|
return s.get(HttpClient, match, installRepoPath, nod, flags) |
||||||
|
} |
||||||
|
|
||||||
|
ColorLog("[TRAC] Cannot match any service, getting dynamic...\n") |
||||||
|
return getDynamic(HttpClient, nod, installRepoPath, flags) |
||||||
|
} |
||||||
|
|
||||||
|
func getDynamic(client *http.Client, nod *Node, installRepoPath string, flags map[string]bool) ([]string, error) { |
||||||
|
match, err := fetchMeta(client, nod.ImportPath) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if match["projectRoot"] != nod.ImportPath { |
||||||
|
rootMatch, err := fetchMeta(client, match["projectRoot"]) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if rootMatch["projectRoot"] != match["projectRoot"] { |
||||||
|
return nil, com.NotFoundError{"Project root mismatch."} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
nod.DownloadURL = expand("{repo}{dir}", match) |
||||||
|
return PureDownload(nod, installRepoPath, flags) |
||||||
|
} |
||||||
|
|
||||||
|
func fetchMeta(client *http.Client, importPath string) (map[string]string, error) { |
||||||
|
uri := importPath |
||||||
|
if !strings.Contains(uri, "/") { |
||||||
|
// Add slash for root of domain.
|
||||||
|
uri = uri + "/" |
||||||
|
} |
||||||
|
uri = uri + "?go-get=1" |
||||||
|
|
||||||
|
scheme := "https" |
||||||
|
resp, err := client.Get(scheme + "://" + uri) |
||||||
|
if err != nil || resp.StatusCode != 200 { |
||||||
|
if err == nil { |
||||||
|
resp.Body.Close() |
||||||
|
} |
||||||
|
scheme = "http" |
||||||
|
resp, err = client.Get(scheme + "://" + uri) |
||||||
|
if err != nil { |
||||||
|
return nil, &com.RemoteError{strings.SplitN(importPath, "/", 2)[0], err} |
||||||
|
} |
||||||
|
} |
||||||
|
defer resp.Body.Close() |
||||||
|
return parseMeta(scheme, importPath, resp.Body) |
||||||
|
} |
||||||
|
|
||||||
|
func attrValue(attrs []xml.Attr, name string) string { |
||||||
|
for _, a := range attrs { |
||||||
|
if strings.EqualFold(a.Name.Local, name) { |
||||||
|
return a.Value |
||||||
|
} |
||||||
|
} |
||||||
|
return "" |
||||||
|
} |
||||||
|
|
||||||
|
func parseMeta(scheme, importPath string, r io.Reader) (map[string]string, error) { |
||||||
|
var match map[string]string |
||||||
|
|
||||||
|
d := xml.NewDecoder(r) |
||||||
|
d.Strict = false |
||||||
|
metaScan: |
||||||
|
for { |
||||||
|
t, tokenErr := d.Token() |
||||||
|
if tokenErr != nil { |
||||||
|
break metaScan |
||||||
|
} |
||||||
|
switch t := t.(type) { |
||||||
|
case xml.EndElement: |
||||||
|
if strings.EqualFold(t.Name.Local, "head") { |
||||||
|
break metaScan |
||||||
|
} |
||||||
|
case xml.StartElement: |
||||||
|
if strings.EqualFold(t.Name.Local, "body") { |
||||||
|
break metaScan |
||||||
|
} |
||||||
|
if !strings.EqualFold(t.Name.Local, "meta") || |
||||||
|
attrValue(t.Attr, "name") != "go-import" { |
||||||
|
continue metaScan |
||||||
|
} |
||||||
|
f := strings.Fields(attrValue(t.Attr, "content")) |
||||||
|
if len(f) != 3 || |
||||||
|
!strings.HasPrefix(importPath, f[0]) || |
||||||
|
!(len(importPath) == len(f[0]) || importPath[len(f[0])] == '/') { |
||||||
|
continue metaScan |
||||||
|
} |
||||||
|
if match != nil { |
||||||
|
return nil, com.NotFoundError{"More than one <meta> found at " + scheme + "://" + importPath} |
||||||
|
} |
||||||
|
|
||||||
|
projectRoot, vcs, repo := f[0], f[1], f[2] |
||||||
|
|
||||||
|
repo = strings.TrimSuffix(repo, "."+vcs) |
||||||
|
i := strings.Index(repo, "://") |
||||||
|
if i < 0 { |
||||||
|
return nil, com.NotFoundError{"Bad repo URL in <meta>."} |
||||||
|
} |
||||||
|
proto := repo[:i] |
||||||
|
repo = repo[i+len("://"):] |
||||||
|
|
||||||
|
match = map[string]string{ |
||||||
|
// Used in getVCSDoc, same as vcsPattern matches.
|
||||||
|
"importPath": importPath, |
||||||
|
"repo": repo, |
||||||
|
"vcs": vcs, |
||||||
|
"dir": importPath[len(projectRoot):], |
||||||
|
|
||||||
|
// Used in getVCSDoc
|
||||||
|
"scheme": proto, |
||||||
|
|
||||||
|
// Used in getDynamic.
|
||||||
|
"projectRoot": projectRoot, |
||||||
|
"projectName": path.Base(projectRoot), |
||||||
|
"projectURL": scheme + "://" + projectRoot, |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if match == nil { |
||||||
|
return nil, com.NotFoundError{"<meta> not found."} |
||||||
|
} |
||||||
|
return match, nil |
||||||
|
} |
||||||
|
|
||||||
|
func getImports(rootPath string, match map[string]string, cmdFlags map[string]bool) (imports []string) { |
||||||
|
dirs, err := GetDirsInfo(rootPath) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
for _, d := range dirs { |
||||||
|
if d.IsDir() && !(!cmdFlags["-e"] && strings.Contains(d.Name(), "example")) { |
||||||
|
absPath := rootPath + d.Name() + "/" |
||||||
|
importPkgs, err := CheckImports(absPath, match["importPath"]) |
||||||
|
if err != nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
imports = append(imports, importPkgs...) |
||||||
|
} |
||||||
|
} |
||||||
|
return imports |
||||||
|
} |
||||||
|
|
||||||
|
// checkImports checks package denpendencies.
|
||||||
|
func CheckImports(absPath, importPath string) (importPkgs []string, err error) { |
||||||
|
dir, err := os.Open(absPath) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
defer dir.Close() |
||||||
|
|
||||||
|
// Get file info slice.
|
||||||
|
fis, err := dir.Readdir(0) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
files := make([]*source, 0, 10) |
||||||
|
for _, fi := range fis { |
||||||
|
// Only handle files.
|
||||||
|
if strings.HasSuffix(fi.Name(), ".go") { |
||||||
|
f, err := os.Open(absPath + fi.Name()) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
fbytes := make([]byte, fi.Size()) |
||||||
|
_, err = f.Read(fbytes) |
||||||
|
f.Close() |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
files = append(files, &source{ |
||||||
|
name: fi.Name(), |
||||||
|
data: fbytes, |
||||||
|
}) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Check if has Go source files.
|
||||||
|
if len(files) > 0 { |
||||||
|
w := &walker{ImportPath: importPath} |
||||||
|
importPkgs, err = w.build(files) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return importPkgs, err |
||||||
|
} |
@ -0,0 +1,144 @@ |
|||||||
|
// Copyright 2013 gopm authors.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License"): you may
|
||||||
|
// not use this file except in compliance with the License. You may obtain
|
||||||
|
// a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
// License for the specific language governing permissions and limitations
|
||||||
|
// under the License.
|
||||||
|
|
||||||
|
package doc |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"go/ast" |
||||||
|
"go/build" |
||||||
|
"go/parser" |
||||||
|
"go/token" |
||||||
|
"io" |
||||||
|
"io/ioutil" |
||||||
|
"os" |
||||||
|
"path" |
||||||
|
"runtime" |
||||||
|
"strings" |
||||||
|
) |
||||||
|
|
||||||
|
type sliceWriter struct{ p *[]byte } |
||||||
|
|
||||||
|
func (w sliceWriter) Write(p []byte) (int, error) { |
||||||
|
*w.p = append(*w.p, p...) |
||||||
|
return len(p), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) readDir(dir string) ([]os.FileInfo, error) { |
||||||
|
if dir != w.ImportPath { |
||||||
|
panic("unexpected") |
||||||
|
} |
||||||
|
fis := make([]os.FileInfo, 0, len(w.srcs)) |
||||||
|
for _, src := range w.srcs { |
||||||
|
fis = append(fis, src) |
||||||
|
} |
||||||
|
return fis, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *walker) openFile(path string) (io.ReadCloser, error) { |
||||||
|
if strings.HasPrefix(path, w.ImportPath+"/") { |
||||||
|
if src, ok := w.srcs[path[len(w.ImportPath)+1:]]; ok { |
||||||
|
return ioutil.NopCloser(bytes.NewReader(src.data)), nil |
||||||
|
} |
||||||
|
} |
||||||
|
panic("unexpected") |
||||||
|
} |
||||||
|
|
||||||
|
func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) { |
||||||
|
pkg := imports[path] |
||||||
|
if pkg == nil { |
||||||
|
// Guess the package name without importing it. Start with the last
|
||||||
|
// element of the path.
|
||||||
|
name := path[strings.LastIndex(path, "/")+1:] |
||||||
|
|
||||||
|
// Trim commonly used prefixes and suffixes containing illegal name
|
||||||
|
// runes.
|
||||||
|
name = strings.TrimSuffix(name, ".go") |
||||||
|
name = strings.TrimSuffix(name, "-go") |
||||||
|
name = strings.TrimPrefix(name, "go.") |
||||||
|
name = strings.TrimPrefix(name, "go-") |
||||||
|
name = strings.TrimPrefix(name, "biogo.") |
||||||
|
|
||||||
|
// It's also common for the last element of the path to contain an
|
||||||
|
// extra "go" prefix, but not always. TODO: examine unresolved ids to
|
||||||
|
// detect when trimming the "go" prefix is appropriate.
|
||||||
|
|
||||||
|
pkg = ast.NewObj(ast.Pkg, name) |
||||||
|
pkg.Data = ast.NewScope(nil) |
||||||
|
imports[path] = pkg |
||||||
|
} |
||||||
|
return pkg, nil |
||||||
|
} |
||||||
|
|
||||||
|
// build gets imports from source files.
|
||||||
|
func (w *walker) build(srcs []*source) ([]string, error) { |
||||||
|
// Add source files to walker, I skipped references here.
|
||||||
|
w.srcs = make(map[string]*source) |
||||||
|
for _, src := range srcs { |
||||||
|
w.srcs[src.name] = src |
||||||
|
} |
||||||
|
|
||||||
|
w.fset = token.NewFileSet() |
||||||
|
|
||||||
|
// Find the package and associated files.
|
||||||
|
ctxt := build.Context{ |
||||||
|
GOOS: runtime.GOOS, |
||||||
|
GOARCH: runtime.GOARCH, |
||||||
|
CgoEnabled: true, |
||||||
|
JoinPath: path.Join, |
||||||
|
IsAbsPath: path.IsAbs, |
||||||
|
SplitPathList: func(list string) []string { return strings.Split(list, ":") }, |
||||||
|
IsDir: func(path string) bool { panic("unexpected") }, |
||||||
|
HasSubdir: func(root, dir string) (rel string, ok bool) { panic("unexpected") }, |
||||||
|
ReadDir: func(dir string) (fi []os.FileInfo, err error) { return w.readDir(dir) }, |
||||||
|
OpenFile: func(path string) (r io.ReadCloser, err error) { return w.openFile(path) }, |
||||||
|
Compiler: "gc", |
||||||
|
} |
||||||
|
|
||||||
|
bpkg, err := ctxt.ImportDir(w.ImportPath, 0) |
||||||
|
// Continue if there are no Go source files; we still want the directory info.
|
||||||
|
_, nogo := err.(*build.NoGoError) |
||||||
|
if err != nil { |
||||||
|
if nogo { |
||||||
|
err = nil |
||||||
|
} else { |
||||||
|
ColorLog("[WARN] Error occurs when check imports[ %s ]\n", err) |
||||||
|
return nil, nil |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Parse the Go files
|
||||||
|
|
||||||
|
files := make(map[string]*ast.File) |
||||||
|
for _, name := range append(bpkg.GoFiles, bpkg.CgoFiles...) { |
||||||
|
file, err := parser.ParseFile(w.fset, name, w.srcs[name].data, parser.ParseComments) |
||||||
|
if err != nil { |
||||||
|
//beego.Error("doc.walker.build():", err)
|
||||||
|
continue |
||||||
|
} |
||||||
|
files[name] = file |
||||||
|
} |
||||||
|
|
||||||
|
w.ImportPath = strings.Replace(w.ImportPath, "\\", "/", -1) |
||||||
|
var imports []string |
||||||
|
for _, v := range bpkg.Imports { |
||||||
|
// Skip strandard library.
|
||||||
|
if !IsGoRepoPath(v) && |
||||||
|
(GetProjectPath(v) != GetProjectPath(w.ImportPath)) { |
||||||
|
imports = append(imports, v) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return imports, err |
||||||
|
} |
Loading…
Reference in new issue