@ -16,99 +16,181 @@
package doc
package doc
import (
import (
"archive/tar"
"bytes"
"compress/gzip"
"io"
"net/http"
"net/http"
"os"
"path"
"path"
"regexp"
"regexp"
"strings"
"github.com/Unknwon/gowalker/utils"
)
)
var (
var (
b itbucketPattern = regexp . MustCompile ( ` ^bitbucket\.org/(?P<owner>[a-z0-9A-Z_.\-]+)/(?P<repo>[a-z0-9A-Z_.\-]+)(?P<dir>/[a-z0-9A-Z_.\-/]*)?$ ` )
B itbucketPattern = 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)- ` )
bitbucketEtagRe = regexp . MustCompile ( ` ^(hg|git)- ` )
)
)
func getBitbucketDoc ( client * http . Client , match map [ string ] string , savedEtag string ) ( * Package , error ) {
// GetBitbucketDoc downloads tarball from bitbucket.org.
func GetBitbucketDoc ( client * http . Client , match map [ string ] string , installGOPATH , commit string , cmdFlags map [ string ] bool ) ( * Package , [ ] string , error ) {
if m := bitbucketEtagRe . FindStringSubmatch ( savedEtag ) ; m != nil {
// Check version control.
if m := bitbucketEtagRe . FindStringSubmatch ( commit ) ; m != nil {
match [ "vcs" ] = m [ 1 ]
match [ "vcs" ] = m [ 1 ]
} else {
} else {
var repo struct {
var repo struct {
Scm string
Scm string
}
}
if err := httpGetJSON ( client , expand ( "https://api.bitbucket.org/1.0/repositories/{owner}/{repo}" , match ) , & repo ) ; err != nil {
if err := httpGetJSON ( client , expand ( "https://api.bitbucket.org/1.0/repositories/{owner}/{repo}" , match ) , & repo ) ; err != nil {
return nil , err
return nil , nil , err
}
}
match [ "vcs" ] = repo . Scm
match [ "vcs" ] = repo . Scm
}
}
tags := make ( map [ string ] string )
// bundle and snapshot will have commit 'B' and 'S',
for _ , nodeType := range [ ] string { "branches" , "tags" } {
// but does not need to download dependencies.
var nodes map [ string ] struct {
isCheckImport := len ( commit ) == 0
Node string
}
// Check if download with specific revision.
if err := httpGetJSON ( client , expand ( "https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/{0}" , match , nodeType ) , & nodes ) ; err != nil {
if isCheckImport || len ( commit ) == 1 {
return nil , err
tags := make ( map [ string ] string )
for _ , nodeType := range [ ] string { "branches" , "tags" } {
var nodes map [ string ] struct {
Node string
}
if err := httpGetJSON ( client , expand ( "https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/{0}" , match , nodeType ) , & nodes ) ; err != nil {
return nil , nil , err
}
for t , n := range nodes {
tags [ t ] = n . Node
}
}
}
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 , nil , err
}
}
} else {
match [ "commit" ] = commit
}
}
var err error
// We use .tar.gz here.
match [ "tag" ] , match [ "commit" ] , err = bestTag ( tags , defaultTags [ match [ "vcs" ] ] )
// zip : https://bitbucket.org/{owner}/{repo}/get/{commit}.zip
// tarball : https://bitbucket.org/{owner}/{repo}/get/{commit}.tar.gz
// Downlaod archive.
p , err := httpGetBytes ( client , expand ( "https://bitbucket.org/{owner}/{repo}/get/{commit}.tar.gz" , match ) , nil )
if err != nil {
if err != nil {
return nil , err
return nil , nil , err
}
}
// Check revision tag.
importPath := "bitbucket.org/" + expand ( "{owner}/{repo}" , match )
etag := expand ( "{vcs}-{commit}" , match )
installPath := installGOPATH + "/src/" + importPath
if etag == savedEtag {
return nil , errNotModified
}
var node struct {
// Remove old files.
Files [ ] struct {
os . RemoveAll ( installPath + "/" )
Path string
// Create destination directory.
}
os . MkdirAll ( installPath + "/" , os . ModePerm )
Directories [ ] string
}
if err := httpGetJSON ( client , expand ( "https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/src/{tag}{dir}/" , match ) , & node ) ; err != nil {
gzr , err := gzip . NewReader ( bytes . NewReader ( p ) )
return nil , err
if err != nil {
return nil , 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.
// Get source file data.
files := make ( [ ] * source , 0 , 5 )
dirs := make ( [ ] string , 0 , 5 )
for _ , f := range node . Files {
for {
_ , name := path . Split ( f . Path )
h , err := tr . Next ( )
if utils . IsDocFile ( name ) {
if err == io . EOF {
files = append ( files , & source {
break
name : name ,
} else if err != nil {
browseURL : expand ( "https://bitbucket.org/{owner}/{repo}/src/{tag}/{0}" , match , f . Path ) ,
return nil , nil , err
rawURL : expand ( "https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/raw/{tag}/{0}" , match , f . Path ) ,
}
} )
fn := h . FileInfo ( ) . Name ( )
// In case that we find directory, usually we should not.
if ! strings . HasSuffix ( fn , "/" ) {
// 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 ) {
dirs = append ( dirs , dir )
os . MkdirAll ( dir + "/" , os . ModePerm )
}
// Get data from archive.
fbytes := make ( [ ] byte , h . Size )
if _ , err := io . ReadFull ( tr , fbytes ) ; err != nil {
return nil , nil , err
}
// Write data to file
fw , err := os . Create ( absPath )
if err != nil {
return nil , nil , err
}
_ , err = fw . Write ( fbytes )
fw . Close ( )
if err != nil {
return nil , nil , err
}
}
}
}
}
if len ( files ) == 0 && len ( node . Directories ) == 0 {
pkg := & Package {
return nil , NotFoundError { "Directory tree does not contain Go files and subdirs." }
ImportPath : importPath ,
AbsPath : installPath ,
Commit : commit ,
}
}
// Fetch file from VCS.
var imports [ ] string
if err := fetchFiles ( client , files , nil ) ; err != nil {
return nil , err
// Check if need to check imports.
if isCheckImport {
rootdir , err := os . Open ( installPath + "/" )
if err != nil {
return nil , nil , err
}
defer rootdir . Close ( )
dirs , err := rootdir . Readdir ( 0 )
if err != nil {
return nil , nil , err
}
for _ , d := range dirs {
if d . IsDir ( ) {
absPath := installPath + "/" + d . Name ( ) + "/"
imports , err = checkImports ( absPath , importPath )
if err != nil {
return nil , nil , err
}
}
}
}
}
// Start generating data.
return pkg , imports , err
w := & walker {
}
lineFmt : "#cl-%d" ,
pdoc : & Package {
// checkDir checks if current directory has been saved.
ImportPath : match [ "importPath" ] ,
func checkDir ( dir string , dirs [ ] string ) bool {
ProjectName : match [ "repo" ] ,
for _ , d := range dirs {
Etag : etag ,
if dir == d {
Dirs : node . Directories ,
return true
} ,
}
}
}
return w . build ( files )
return false
}
}