mirror of https://github.com/gogits/gogs.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
759 lines
22 KiB
759 lines
22 KiB
// Copyright 2013-2014 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/build" |
|
"io" |
|
"io/ioutil" |
|
"os" |
|
"path" |
|
"path/filepath" |
|
"regexp" |
|
"strings" |
|
"time" |
|
|
|
"github.com/Unknwon/com" |
|
|
|
"github.com/gpmgo/gopm/log" |
|
) |
|
|
|
const VENDOR = ".vendor" |
|
|
|
// GetDirsInfo returns os.FileInfo of all sub-directories in root path. |
|
func GetDirsInfo(rootPath string) ([]os.FileInfo, error) { |
|
if !com.IsDir(rootPath) { |
|
log.Warn("Directory %s does not exist", rootPath) |
|
return []os.FileInfo{}, nil |
|
} |
|
|
|
rootDir, err := os.Open(rootPath) |
|
if err != nil { |
|
return nil, err |
|
} |
|
defer rootDir.Close() |
|
|
|
dirs, err := rootDir.Readdir(0) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
return dirs, nil |
|
} |
|
|
|
// A Source describles a Source code file. |
|
type Source struct { |
|
SrcName string |
|
SrcData []byte |
|
} |
|
|
|
func (s *Source) Name() string { return s.SrcName } |
|
func (s *Source) Size() int64 { return int64(len(s.SrcData)) } |
|
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 } |
|
func (s *Source) Data() []byte { return s.SrcData } |
|
|
|
type Context struct { |
|
build.Context |
|
importPath string |
|
srcFiles map[string]*Source |
|
} |
|
|
|
func (ctx *Context) readDir(dir string) ([]os.FileInfo, error) { |
|
fis := make([]os.FileInfo, 0, len(ctx.srcFiles)) |
|
for _, src := range ctx.srcFiles { |
|
fis = append(fis, src) |
|
} |
|
return fis, nil |
|
} |
|
|
|
func (ctx *Context) openFile(path string) (r io.ReadCloser, err error) { |
|
if src, ok := ctx.srcFiles[filepath.Base(path)]; ok { |
|
return ioutil.NopCloser(bytes.NewReader(src.Data())), nil |
|
} |
|
return nil, os.ErrNotExist |
|
} |
|
|
|
// GetImports returns package denpendencies. |
|
func GetImports(absPath, importPath string, example, test bool) []string { |
|
fis, err := GetDirsInfo(absPath) |
|
if err != nil { |
|
log.Error("", "Fail to get directory's information") |
|
log.Fatal("", err.Error()) |
|
} |
|
absPath += "/" |
|
|
|
ctx := new(Context) |
|
ctx.importPath = importPath |
|
ctx.srcFiles = make(map[string]*Source) |
|
ctx.Context = build.Default |
|
ctx.JoinPath = path.Join |
|
ctx.IsAbsPath = path.IsAbs |
|
ctx.ReadDir = ctx.readDir |
|
ctx.OpenFile = ctx.openFile |
|
|
|
// TODO: Load too much, need to make sure which is imported which are not. |
|
dirs := make([]string, 0, 10) |
|
for _, fi := range fis { |
|
if strings.Contains(fi.Name(), VENDOR) { |
|
continue |
|
} |
|
|
|
if fi.IsDir() { |
|
dirs = append(dirs, absPath+fi.Name()) |
|
continue |
|
} else if !test && strings.HasSuffix(fi.Name(), "_test.go") { |
|
continue |
|
} else if !strings.HasSuffix(fi.Name(), ".go") || strings.HasPrefix(fi.Name(), ".") || |
|
strings.HasPrefix(fi.Name(), "_") { |
|
continue |
|
} |
|
src := &Source{SrcName: fi.Name()} |
|
src.SrcData, err = ioutil.ReadFile(absPath + fi.Name()) |
|
if err != nil { |
|
log.Error("", "Fail to read file") |
|
log.Fatal("", err.Error()) |
|
} |
|
ctx.srcFiles[fi.Name()] = src |
|
} |
|
|
|
pkg, err := ctx.ImportDir(absPath, build.AllowBinary) |
|
if err != nil { |
|
if _, ok := err.(*build.NoGoError); !ok { |
|
log.Error("", "Fail to get imports") |
|
log.Fatal("", err.Error()) |
|
} |
|
} |
|
|
|
imports := make([]string, 0, len(pkg.Imports)) |
|
for _, p := range pkg.Imports { |
|
if !IsGoRepoPath(p) && !strings.HasPrefix(p, importPath) { |
|
imports = append(imports, p) |
|
} |
|
} |
|
|
|
if len(dirs) > 0 { |
|
imports = append(imports, GetAllImports(dirs, importPath, example, test)...) |
|
} |
|
return imports |
|
} |
|
|
|
// isVcsPath returns true if the directory was created by VCS. |
|
func isVcsPath(dirPath string) bool { |
|
return strings.Contains(dirPath, "/.git") || |
|
strings.Contains(dirPath, "/.hg") || |
|
strings.Contains(dirPath, "/.svn") |
|
} |
|
|
|
// GetAllImports returns all imports in given directory and all sub-directories. |
|
func GetAllImports(dirs []string, importPath string, example, test bool) (imports []string) { |
|
for _, d := range dirs { |
|
if !isVcsPath(d) && |
|
!(!example && strings.Contains(d, "example")) { |
|
imports = append(imports, GetImports(d, importPath, example, test)...) |
|
} |
|
} |
|
return imports |
|
} |
|
|
|
// GetGOPATH returns best matched GOPATH. |
|
func GetBestMatchGOPATH(appPath string) string { |
|
paths := com.GetGOPATHs() |
|
for _, p := range paths { |
|
if strings.HasPrefix(p, appPath) { |
|
return strings.Replace(p, "\\", "/", -1) |
|
} |
|
} |
|
return paths[0] |
|
} |
|
|
|
// CheckIsExistWithVCS returns false if directory only has VCS folder, |
|
// or doesn't exist. |
|
func CheckIsExistWithVCS(path string) bool { |
|
// Check if directory exist. |
|
if !com.IsExist(path) { |
|
return false |
|
} |
|
|
|
// Check if only has VCS folder. |
|
dirs, err := GetDirsInfo(path) |
|
if err != nil { |
|
log.Error("", "Fail to get directory's information") |
|
log.Fatal("", err.Error()) |
|
} |
|
|
|
if len(dirs) > 1 { |
|
return true |
|
} else if len(dirs) == 0 { |
|
return false |
|
} |
|
|
|
switch dirs[0].Name() { |
|
case ".git", ".hg", ".svn": |
|
return false |
|
} |
|
|
|
return true |
|
} |
|
|
|
// CheckIsExistInGOPATH checks if given package import path exists in any path in GOPATH/src, |
|
// and returns corresponding GOPATH. |
|
func CheckIsExistInGOPATH(importPath string) (string, bool) { |
|
paths := com.GetGOPATHs() |
|
for _, p := range paths { |
|
if CheckIsExistWithVCS(p + "/src/" + importPath + "/") { |
|
return p, true |
|
} |
|
} |
|
return "", false |
|
} |
|
|
|
// GetProjectPath returns project path of import path. |
|
func GetProjectPath(importPath string) (projectPath string) { |
|
projectPath = importPath |
|
|
|
// Check project hosting. |
|
switch { |
|
case strings.HasPrefix(importPath, "github.com") || |
|
strings.HasPrefix(importPath, "git.oschina.net"): |
|
projectPath = joinPath(importPath, 3) |
|
case strings.HasPrefix(importPath, "code.google.com"): |
|
projectPath = joinPath(importPath, 3) |
|
case strings.HasPrefix(importPath, "bitbucket.org"): |
|
projectPath = joinPath(importPath, 3) |
|
case strings.HasPrefix(importPath, "launchpad.net"): |
|
projectPath = joinPath(importPath, 2) |
|
} |
|
|
|
return projectPath |
|
} |
|
|
|
func joinPath(importPath string, num int) string { |
|
subdirs := strings.Split(importPath, "/") |
|
if len(subdirs) > num { |
|
return strings.Join(subdirs[:num], "/") |
|
} |
|
return importPath |
|
} |
|
|
|
var validTLD = map[string]bool{ |
|
// curl http://data.iana.org/TLD/tlds-alpha-by-domain.txt | sed -e '/#/ d' -e 's/.*/"&": true,/' | tr [:upper:] [:lower:] |
|
".ac": true, |
|
".ad": true, |
|
".ae": true, |
|
".aero": true, |
|
".af": true, |
|
".ag": true, |
|
".ai": true, |
|
".al": true, |
|
".am": true, |
|
".an": true, |
|
".ao": true, |
|
".aq": true, |
|
".ar": true, |
|
".arpa": true, |
|
".as": true, |
|
".asia": true, |
|
".at": true, |
|
".au": true, |
|
".aw": true, |
|
".ax": true, |
|
".az": true, |
|
".ba": true, |
|
".bb": true, |
|
".bd": true, |
|
".be": true, |
|
".bf": true, |
|
".bg": true, |
|
".bh": true, |
|
".bi": true, |
|
".biz": true, |
|
".bj": true, |
|
".bm": true, |
|
".bn": true, |
|
".bo": true, |
|
".br": true, |
|
".bs": true, |
|
".bt": true, |
|
".bv": true, |
|
".bw": true, |
|
".by": true, |
|
".bz": true, |
|
".ca": true, |
|
".cat": true, |
|
".cc": true, |
|
".cd": true, |
|
".cf": true, |
|
".cg": true, |
|
".ch": true, |
|
".ci": true, |
|
".ck": true, |
|
".cl": true, |
|
".cm": true, |
|
".cn": true, |
|
".co": true, |
|
".com": true, |
|
".coop": true, |
|
".cr": true, |
|
".cu": true, |
|
".cv": true, |
|
".cw": true, |
|
".cx": true, |
|
".cy": true, |
|
".cz": true, |
|
".de": true, |
|
".dj": true, |
|
".dk": true, |
|
".dm": true, |
|
".do": true, |
|
".dz": true, |
|
".ec": true, |
|
".edu": true, |
|
".ee": true, |
|
".eg": true, |
|
".er": true, |
|
".es": true, |
|
".et": true, |
|
".eu": true, |
|
".fi": true, |
|
".fj": true, |
|
".fk": true, |
|
".fm": true, |
|
".fo": true, |
|
".fr": true, |
|
".ga": true, |
|
".gb": true, |
|
".gd": true, |
|
".ge": true, |
|
".gf": true, |
|
".gg": true, |
|
".gh": true, |
|
".gi": true, |
|
".gl": true, |
|
".gm": true, |
|
".gn": true, |
|
".gov": true, |
|
".gp": true, |
|
".gq": true, |
|
".gr": true, |
|
".gs": true, |
|
".gt": true, |
|
".gu": true, |
|
".gw": true, |
|
".gy": true, |
|
".hk": true, |
|
".hm": true, |
|
".hn": true, |
|
".hr": true, |
|
".ht": true, |
|
".hu": true, |
|
".id": true, |
|
".ie": true, |
|
".il": true, |
|
".im": true, |
|
".in": true, |
|
".info": true, |
|
".int": true, |
|
".io": true, |
|
".iq": true, |
|
".ir": true, |
|
".is": true, |
|
".it": true, |
|
".je": true, |
|
".jm": true, |
|
".jo": true, |
|
".jobs": true, |
|
".jp": true, |
|
".ke": true, |
|
".kg": true, |
|
".kh": true, |
|
".ki": true, |
|
".km": true, |
|
".kn": true, |
|
".kp": true, |
|
".kr": true, |
|
".kw": true, |
|
".ky": true, |
|
".kz": true, |
|
".la": true, |
|
".lb": true, |
|
".lc": true, |
|
".li": true, |
|
".lk": true, |
|
".lr": true, |
|
".ls": true, |
|
".lt": true, |
|
".lu": true, |
|
".lv": true, |
|
".ly": true, |
|
".ma": true, |
|
".mc": true, |
|
".md": true, |
|
".me": true, |
|
".mg": true, |
|
".mh": true, |
|
".mil": true, |
|
".mk": true, |
|
".ml": true, |
|
".mm": true, |
|
".mn": true, |
|
".mo": true, |
|
".mobi": true, |
|
".mp": true, |
|
".mq": true, |
|
".mr": true, |
|
".ms": true, |
|
".mt": true, |
|
".mu": true, |
|
".museum": true, |
|
".mv": true, |
|
".mw": true, |
|
".mx": true, |
|
".my": true, |
|
".mz": true, |
|
".na": true, |
|
".name": true, |
|
".nc": true, |
|
".ne": true, |
|
".net": true, |
|
".nf": true, |
|
".ng": true, |
|
".ni": true, |
|
".nl": true, |
|
".no": true, |
|
".np": true, |
|
".nr": true, |
|
".nu": true, |
|
".nz": true, |
|
".om": true, |
|
".org": true, |
|
".pa": true, |
|
".pe": true, |
|
".pf": true, |
|
".pg": true, |
|
".ph": true, |
|
".pk": true, |
|
".pl": true, |
|
".pm": true, |
|
".pn": true, |
|
".post": true, |
|
".pr": true, |
|
".pro": true, |
|
".ps": true, |
|
".pt": true, |
|
".pw": true, |
|
".py": true, |
|
".qa": true, |
|
".re": true, |
|
".ro": true, |
|
".rs": true, |
|
".ru": true, |
|
".rw": true, |
|
".sa": true, |
|
".sb": true, |
|
".sc": true, |
|
".sd": true, |
|
".se": true, |
|
".sg": true, |
|
".sh": true, |
|
".si": true, |
|
".sj": true, |
|
".sk": true, |
|
".sl": true, |
|
".sm": true, |
|
".sn": true, |
|
".so": true, |
|
".sr": true, |
|
".st": true, |
|
".su": true, |
|
".sv": true, |
|
".sx": true, |
|
".sy": true, |
|
".sz": true, |
|
".tc": true, |
|
".td": true, |
|
".tel": true, |
|
".tf": true, |
|
".tg": true, |
|
".th": true, |
|
".tj": true, |
|
".tk": true, |
|
".tl": true, |
|
".tm": true, |
|
".tn": true, |
|
".to": true, |
|
".tp": true, |
|
".tr": true, |
|
".travel": true, |
|
".tt": true, |
|
".tv": true, |
|
".tw": true, |
|
".tz": true, |
|
".ua": true, |
|
".ug": true, |
|
".uk": true, |
|
".us": true, |
|
".uy": true, |
|
".uz": true, |
|
".va": true, |
|
".vc": true, |
|
".ve": true, |
|
".vg": true, |
|
".vi": true, |
|
".vn": true, |
|
".vu": true, |
|
".wf": true, |
|
".ws": true, |
|
".xn--0zwm56d": true, |
|
".xn--11b5bs3a9aj6g": true, |
|
".xn--3e0b707e": true, |
|
".xn--45brj9c": true, |
|
".xn--80akhbyknj4f": true, |
|
".xn--80ao21a": true, |
|
".xn--90a3ac": true, |
|
".xn--9t4b11yi5a": true, |
|
".xn--clchc0ea0b2g2a9gcd": true, |
|
".xn--deba0ad": true, |
|
".xn--fiqs8s": true, |
|
".xn--fiqz9s": true, |
|
".xn--fpcrj9c3d": true, |
|
".xn--fzc2c9e2c": true, |
|
".xn--g6w251d": true, |
|
".xn--gecrj9c": true, |
|
".xn--h2brj9c": true, |
|
".xn--hgbk6aj7f53bba": true, |
|
".xn--hlcj6aya9esc7a": true, |
|
".xn--j6w193g": true, |
|
".xn--jxalpdlp": true, |
|
".xn--kgbechtv": true, |
|
".xn--kprw13d": true, |
|
".xn--kpry57d": true, |
|
".xn--lgbbat1ad8j": true, |
|
".xn--mgb9awbf": true, |
|
".xn--mgbaam7a8h": true, |
|
".xn--mgbayh7gpa": true, |
|
".xn--mgbbh1a71e": true, |
|
".xn--mgbc0a9azcg": true, |
|
".xn--mgberp4a5d4ar": true, |
|
".xn--mgbx4cd0ab": true, |
|
".xn--o3cw4h": true, |
|
".xn--ogbpf8fl": true, |
|
".xn--p1ai": true, |
|
".xn--pgbs0dh": true, |
|
".xn--s9brj9c": true, |
|
".xn--wgbh1c": true, |
|
".xn--wgbl6a": true, |
|
".xn--xkc2al3hye2a": true, |
|
".xn--xkc2dl3a5ee0h": true, |
|
".xn--yfro4i67o": true, |
|
".xn--ygbi2ammx": true, |
|
".xn--zckzah": true, |
|
".xxx": true, |
|
".ye": true, |
|
".yt": true, |
|
".za": true, |
|
".zm": true, |
|
".zw": true, |
|
} |
|
|
|
var ( |
|
validHost = regexp.MustCompile(`^[-a-z0-9]+(?:\.[-a-z0-9]+)+$`) |
|
validPathElement = regexp.MustCompile(`^[-A-Za-z0-9~+][-A-Za-z0-9_.]*$`) |
|
) |
|
|
|
// IsValidRemotePath returns true if importPath is structurally valid for "go get". |
|
func IsValidRemotePath(importPath string) bool { |
|
|
|
parts := strings.Split(importPath, "/") |
|
|
|
if len(parts) <= 1 { |
|
// Import path must contain at least one "/". |
|
return false |
|
} |
|
|
|
if !validTLD[path.Ext(parts[0])] { |
|
return false |
|
} |
|
|
|
if !validHost.MatchString(parts[0]) { |
|
return false |
|
} |
|
for _, part := range parts[1:] { |
|
if !validPathElement.MatchString(part) || part == "testdata" { |
|
return false |
|
} |
|
} |
|
|
|
return true |
|
} |
|
|
|
var standardPath = map[string]bool{ |
|
"builtin": true, |
|
|
|
// go list -f '"{{.ImportPath}}": true,' std | grep -v 'cmd/|exp/' |
|
"cmd/api": true, |
|
"cmd/cgo": true, |
|
"cmd/fix": true, |
|
"cmd/go": true, |
|
"cmd/gofmt": true, |
|
"cmd/vet": true, |
|
"cmd/yacc": true, |
|
"archive/tar": true, |
|
"archive/zip": true, |
|
"bufio": true, |
|
"bytes": true, |
|
"compress/bzip2": true, |
|
"compress/flate": true, |
|
"compress/gzip": true, |
|
"compress/lzw": true, |
|
"compress/zlib": true, |
|
"container/heap": true, |
|
"container/list": true, |
|
"container/ring": true, |
|
"crypto": true, |
|
"crypto/aes": true, |
|
"crypto/cipher": true, |
|
"crypto/des": true, |
|
"crypto/dsa": true, |
|
"crypto/ecdsa": true, |
|
"crypto/elliptic": true, |
|
"crypto/hmac": true, |
|
"crypto/md5": true, |
|
"crypto/rand": true, |
|
"crypto/rc4": true, |
|
"crypto/rsa": true, |
|
"crypto/sha1": true, |
|
"crypto/sha256": true, |
|
"crypto/sha512": true, |
|
"crypto/subtle": true, |
|
"crypto/tls": true, |
|
"crypto/x509": true, |
|
"crypto/x509/pkix": true, |
|
"database/sql": true, |
|
"database/sql/driver": true, |
|
"debug/dwarf": true, |
|
"debug/elf": true, |
|
"debug/gosym": true, |
|
"debug/macho": true, |
|
"debug/pe": true, |
|
"encoding/ascii85": true, |
|
"encoding/asn1": true, |
|
"encoding/base32": true, |
|
"encoding/base64": true, |
|
"encoding/binary": true, |
|
"encoding/csv": true, |
|
"encoding/gob": true, |
|
"encoding/hex": true, |
|
"encoding/json": true, |
|
"encoding/pem": true, |
|
"encoding/xml": true, |
|
"errors": true, |
|
"expvar": true, |
|
"flag": true, |
|
"fmt": true, |
|
"go/ast": true, |
|
"go/build": true, |
|
"go/doc": true, |
|
"go/format": true, |
|
"go/parser": true, |
|
"go/printer": true, |
|
"go/scanner": true, |
|
"go/token": true, |
|
"hash": true, |
|
"hash/adler32": true, |
|
"hash/crc32": true, |
|
"hash/crc64": true, |
|
"hash/fnv": true, |
|
"html": true, |
|
"html/template": true, |
|
"image": true, |
|
"image/color": true, |
|
"image/draw": true, |
|
"image/gif": true, |
|
"image/jpeg": true, |
|
"image/png": true, |
|
"index/suffixarray": true, |
|
"io": true, |
|
"io/ioutil": true, |
|
"log": true, |
|
"log/syslog": true, |
|
"math": true, |
|
"math/big": true, |
|
"math/cmplx": true, |
|
"math/rand": true, |
|
"mime": true, |
|
"mime/multipart": true, |
|
"net": true, |
|
"net/http": true, |
|
"net/http/cgi": true, |
|
"net/http/cookiejar": true, |
|
"net/http/fcgi": true, |
|
"net/http/httptest": true, |
|
"net/http/httputil": true, |
|
"net/http/pprof": true, |
|
"net/mail": true, |
|
"net/rpc": true, |
|
"net/rpc/jsonrpc": true, |
|
"net/smtp": true, |
|
"net/textproto": true, |
|
"net/url": true, |
|
"os": true, |
|
"os/exec": true, |
|
"os/signal": true, |
|
"os/user": true, |
|
"path": true, |
|
"path/filepath": true, |
|
"reflect": true, |
|
"regexp": true, |
|
"regexp/syntax": true, |
|
"runtime": true, |
|
"runtime/cgo": true, |
|
"runtime/debug": true, |
|
"runtime/pprof": true, |
|
"runtime/race": true, |
|
"sort": true, |
|
"strconv": true, |
|
"strings": true, |
|
"sync": true, |
|
"sync/atomic": true, |
|
"syscall": true, |
|
"testing": true, |
|
"testing/iotest": true, |
|
"testing/quick": true, |
|
"text/scanner": true, |
|
"text/tabwriter": true, |
|
"text/template": true, |
|
"text/template/parse": true, |
|
"time": true, |
|
"unicode": true, |
|
"unicode/utf16": true, |
|
"unicode/utf8": true, |
|
"unsafe": true, |
|
} |
|
|
|
// IsGoRepoPath returns true if package is from standard library. |
|
func IsGoRepoPath(importPath string) bool { |
|
return standardPath[importPath] |
|
} |
|
|
|
func CheckNodeValue(v string) string { |
|
if len(v) == 0 { |
|
return "<UTD>" |
|
} |
|
return v |
|
}
|
|
|