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