зеркало из https://github.com/golang/gddo.git
Miscellaneous improvements
- Upgrade to BS3 final. - Improve crawling. - Protect GitHub API quota from unfriendly robots. - Other small fixes.
This commit is contained in:
Родитель
834a0afe85
Коммит
bc5d91c919
|
@ -170,12 +170,6 @@ var putScript = redis.NewScript(0, `
|
||||||
redis.call('SREM', 'index:' .. term, id)
|
redis.call('SREM', 'index:' .. term, id)
|
||||||
elseif x == 2 then
|
elseif x == 2 then
|
||||||
redis.call('SADD', 'index:' .. term, id)
|
redis.call('SADD', 'index:' .. term, id)
|
||||||
if string.sub(term, 1, 7) == 'import:' then
|
|
||||||
local import = string.sub(term, 8)
|
|
||||||
if redis.call('HEXISTS', 'ids', import) == 0 and redis.call('SISMEMBER', 'badCrawl', import) == 0 then
|
|
||||||
redis.call('SADD', 'newCrawl', import)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -190,6 +184,15 @@ var putScript = redis.NewScript(0, `
|
||||||
return redis.call('HMSET', 'pkg:' .. id, 'path', path, 'synopsis', synopsis, 'score', score, 'gob', gob, 'terms', terms, 'etag', etag, 'kind', kind)
|
return redis.call('HMSET', 'pkg:' .. id, 'path', path, 'synopsis', synopsis, 'score', score, 'gob', gob, 'terms', terms, 'etag', etag, 'kind', kind)
|
||||||
`)
|
`)
|
||||||
|
|
||||||
|
var addCrawlScript = redis.NewScript(0, `
|
||||||
|
for i=1,#ARGV do
|
||||||
|
local pkg = ARGV[i]
|
||||||
|
if redis.call('HEXISTS', 'ids', pkg) == 0 and redis.call('SISMEMBER', 'badCrawl', pkg) == 0 then
|
||||||
|
redis.call('SADD', 'newCrawl', pkg)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
`)
|
||||||
|
|
||||||
// Put adds the package documentation to the database.
|
// Put adds the package documentation to the database.
|
||||||
func (db *Database) Put(pdoc *doc.Package, nextCrawl time.Time) error {
|
func (db *Database) Put(pdoc *doc.Package, nextCrawl time.Time) error {
|
||||||
c := db.Pool.Get()
|
c := db.Pool.Get()
|
||||||
|
@ -204,7 +207,7 @@ func (db *Database) Put(pdoc *doc.Package, nextCrawl time.Time) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Truncate large documents.
|
// Truncate large documents.
|
||||||
if gobBuf.Len() > 700000 {
|
if gobBuf.Len() > 200000 {
|
||||||
pdocNew := *pdoc
|
pdocNew := *pdoc
|
||||||
pdoc = &pdocNew
|
pdoc = &pdocNew
|
||||||
pdoc.Truncated = true
|
pdoc.Truncated = true
|
||||||
|
@ -236,7 +239,45 @@ func (db *Database) Put(pdoc *doc.Package, nextCrawl time.Time) error {
|
||||||
if !nextCrawl.IsZero() {
|
if !nextCrawl.IsZero() {
|
||||||
t = nextCrawl.Unix()
|
t = nextCrawl.Unix()
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = putScript.Do(c, pdoc.ImportPath, pdoc.Synopsis, score, gobBytes, strings.Join(terms, " "), pdoc.Etag, kind, t)
|
_, err = putScript.Do(c, pdoc.ImportPath, pdoc.Synopsis, score, gobBytes, strings.Join(terms, " "), pdoc.Etag, kind, t)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if nextCrawl.IsZero() {
|
||||||
|
// Skip crawling related packages if this is not a full save.
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
paths := make(map[string]bool)
|
||||||
|
for _, p := range pdoc.Imports {
|
||||||
|
if doc.IsValidRemotePath(p) {
|
||||||
|
paths[p] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, p := range pdoc.TestImports {
|
||||||
|
if doc.IsValidRemotePath(p) {
|
||||||
|
paths[p] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, p := range pdoc.XTestImports {
|
||||||
|
if doc.IsValidRemotePath(p) {
|
||||||
|
paths[p] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if pdoc.ImportPath != pdoc.ProjectRoot && pdoc.ProjectRoot != "" {
|
||||||
|
paths[pdoc.ProjectRoot] = true
|
||||||
|
}
|
||||||
|
for _, p := range pdoc.Subdirectories {
|
||||||
|
paths[pdoc.ImportPath+"/"+p] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
args := make([]interface{}, 0, len(paths))
|
||||||
|
for p := range paths {
|
||||||
|
args = append(args, p)
|
||||||
|
}
|
||||||
|
_, err = addCrawlScript.Do(c, args...)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -456,7 +497,6 @@ var deleteScript = redis.NewScript(0, `
|
||||||
end
|
end
|
||||||
|
|
||||||
redis.call('ZREM', 'nextCrawl', id)
|
redis.call('ZREM', 'nextCrawl', id)
|
||||||
redis.call('SREM', 'badCrawl', path)
|
|
||||||
redis.call('SREM', 'newCrawl', path)
|
redis.call('SREM', 'newCrawl', path)
|
||||||
redis.call('ZREM', 'popular', id)
|
redis.call('ZREM', 'popular', id)
|
||||||
redis.call('DEL', 'pkg:' .. id)
|
redis.call('DEL', 'pkg:' .. id)
|
||||||
|
@ -670,6 +710,7 @@ type PackageInfo struct {
|
||||||
Pkgs []Package
|
Pkgs []Package
|
||||||
Score float64
|
Score float64
|
||||||
Kind string
|
Kind string
|
||||||
|
Size int
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do executes function f for each document in the database.
|
// Do executes function f for each document in the database.
|
||||||
|
@ -681,7 +722,7 @@ func (db *Database) Do(f func(*PackageInfo) error) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
values, err := redis.Values(c.Do("HMGET", key, "gob", "score", "kind", "path"))
|
values, err := redis.Values(c.Do("HMGET", key, "gob", "score", "kind", "path", "terms", "synopis"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -690,9 +731,11 @@ func (db *Database) Do(f func(*PackageInfo) error) error {
|
||||||
pi PackageInfo
|
pi PackageInfo
|
||||||
p []byte
|
p []byte
|
||||||
path string
|
path string
|
||||||
|
terms string
|
||||||
|
synopsis string
|
||||||
)
|
)
|
||||||
|
|
||||||
if _, err := redis.Scan(values, &p, &pi.Score, &pi.Kind, &path); err != nil {
|
if _, err := redis.Scan(values, &p, &pi.Score, &pi.Kind, &path, &terms, &synopsis); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -700,6 +743,8 @@ func (db *Database) Do(f func(*PackageInfo) error) error {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pi.Size = len(path) + len(p) + len(terms) + len(synopsis)
|
||||||
|
|
||||||
p, err = snappy.Decode(nil, p)
|
p, err = snappy.Decode(nil, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("snappy decoding %s: %v", path, err)
|
return fmt.Errorf("snappy decoding %s: %v", path, err)
|
||||||
|
@ -810,7 +855,7 @@ func (db *Database) GetGob(key string, value interface{}) error {
|
||||||
return gob.NewDecoder(bytes.NewReader(p)).Decode(value)
|
return gob.NewDecoder(bytes.NewReader(p)).Decode(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
var incrementPopularScore = redis.NewScript(0, `
|
var incrementPopularScoreScript = redis.NewScript(0, `
|
||||||
local path = ARGV[1]
|
local path = ARGV[1]
|
||||||
local n = ARGV[2]
|
local n = ARGV[2]
|
||||||
local t = ARGV[3]
|
local t = ARGV[3]
|
||||||
|
@ -832,20 +877,21 @@ var incrementPopularScore = redis.NewScript(0, `
|
||||||
|
|
||||||
const popularHalfLife = time.Hour * 24 * 7
|
const popularHalfLife = time.Hour * 24 * 7
|
||||||
|
|
||||||
func scaledTime(t time.Time) float64 {
|
func (db *Database) incrementPopularScoreInternal(path string, delta float64, t time.Time) error {
|
||||||
const lambda = math.Ln2 / float64(popularHalfLife)
|
|
||||||
return lambda * float64(t.Sub(time.Unix(1257894000, 0)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) IncrementPopularScore(path string) error {
|
|
||||||
// nt = n0 * math.Exp(-lambda * t)
|
// nt = n0 * math.Exp(-lambda * t)
|
||||||
// lambda = math.Ln2 / thalf
|
// lambda = math.Ln2 / thalf
|
||||||
c := db.Pool.Get()
|
c := db.Pool.Get()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
_, err := incrementPopularScore.Do(c, path, 1, scaledTime(time.Now()))
|
const lambda = math.Ln2 / float64(popularHalfLife)
|
||||||
|
scaledTime := lambda * float64(t.Sub(time.Unix(1257894000, 0)))
|
||||||
|
_, err := incrementPopularScoreScript.Do(c, path, delta, scaledTime)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (db *Database) IncrementPopularScore(path string) error {
|
||||||
|
return db.incrementPopularScoreInternal(path, 1, time.Now())
|
||||||
|
}
|
||||||
|
|
||||||
var popularScript = redis.NewScript(0, `
|
var popularScript = redis.NewScript(0, `
|
||||||
local stop = ARGV[1]
|
local stop = ARGV[1]
|
||||||
local ids = redis.call('ZREVRANGE', 'popular', '0', stop)
|
local ids = redis.call('ZREVRANGE', 'popular', '0', stop)
|
||||||
|
@ -892,26 +938,59 @@ func (db *Database) PopularWithScores() ([]Package, error) {
|
||||||
return pkgs, err
|
return pkgs, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (db *Database) GetNewCrawl() (string, error) {
|
func (db *Database) PopNewCrawl() (string, bool, error) {
|
||||||
c := db.Pool.Get()
|
c := db.Pool.Get()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
v, err := redis.String(c.Do("SRANDMEMBER", "newCrawl"))
|
|
||||||
if err == redis.ErrNil {
|
var subdirs []Package
|
||||||
|
|
||||||
|
path, err := redis.String(c.Do("SPOP", "newCrawl"))
|
||||||
|
switch {
|
||||||
|
case err == redis.ErrNil:
|
||||||
err = nil
|
err = nil
|
||||||
|
path = ""
|
||||||
|
case err == nil:
|
||||||
|
subdirs, err = db.getSubdirs(c, path, nil)
|
||||||
}
|
}
|
||||||
return v, err
|
return path, len(subdirs) > 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var setBadCrawlScript = redis.NewScript(0, `
|
func (db *Database) AddBadCrawl(path string) error {
|
||||||
local path = ARGV[1]
|
|
||||||
if redis.call('SREM', 'newCrawl', path) == 1 then
|
|
||||||
redis.call('SADD', 'badCrawl', path)
|
|
||||||
end
|
|
||||||
`)
|
|
||||||
|
|
||||||
func (db *Database) SetBadCrawl(path string) error {
|
|
||||||
c := db.Pool.Get()
|
c := db.Pool.Get()
|
||||||
defer c.Close()
|
defer c.Close()
|
||||||
_, err := setBadCrawlScript.Do(c, path)
|
_, err := c.Do("SADD", "badCrawl", path)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var incrementCounterScript = redis.NewScript(0, `
|
||||||
|
local key = 'counter:' .. ARGV[1]
|
||||||
|
local n = tonumber(ARGV[2])
|
||||||
|
local t = tonumber(ARGV[3])
|
||||||
|
local exp = tonumber(ARGV[4])
|
||||||
|
|
||||||
|
local counter = redis.call('GET', key)
|
||||||
|
if counter then
|
||||||
|
counter = cjson.decode(counter)
|
||||||
|
n = n + counter.n * math.exp(counter.t - t)
|
||||||
|
end
|
||||||
|
|
||||||
|
redis.call('SET', key, cjson.encode({n = n; t = t}))
|
||||||
|
redis.call('EXPIRE', key, exp)
|
||||||
|
return tostring(n)
|
||||||
|
`)
|
||||||
|
|
||||||
|
const counterHalflife = time.Hour
|
||||||
|
|
||||||
|
func (db *Database) incrementCounterInternal(key string, delta float64, t time.Time) (float64, error) {
|
||||||
|
// nt = n0 * math.Exp(-lambda * t)
|
||||||
|
// lambda = math.Ln2 / thalf
|
||||||
|
c := db.Pool.Get()
|
||||||
|
defer c.Close()
|
||||||
|
const lambda = math.Ln2 / float64(counterHalflife)
|
||||||
|
scaledTime := lambda * float64(t.Sub(time.Unix(1257894000, 0)))
|
||||||
|
return redis.Float64(incrementCounterScript.Do(c, key, delta, scaledTime, (4*counterHalflife)/time.Second))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (db *Database) IncrementCounter(key string, delta float64) (float64, error) {
|
||||||
|
return db.incrementCounterInternal(key, delta, time.Now())
|
||||||
|
}
|
||||||
|
|
|
@ -193,6 +193,8 @@ func TestPutGet(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const epsilon = 0.000001
|
||||||
|
|
||||||
func TestPopular(t *testing.T) {
|
func TestPopular(t *testing.T) {
|
||||||
db := newDB(t)
|
db := newDB(t)
|
||||||
defer closeDB(db)
|
defer closeDB(db)
|
||||||
|
@ -207,7 +209,7 @@ func TestPopular(t *testing.T) {
|
||||||
for id := 12; id >= 0; id-- {
|
for id := 12; id >= 0; id-- {
|
||||||
path := "github.com/user/repo/p" + strconv.Itoa(id)
|
path := "github.com/user/repo/p" + strconv.Itoa(id)
|
||||||
c.Do("HSET", "ids", path, id)
|
c.Do("HSET", "ids", path, id)
|
||||||
_, err := incrementPopularScore.Do(c, path, score, scaledTime(now))
|
err := db.incrementPopularScoreInternal(path, score, now)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -227,8 +229,39 @@ func TestPopular(t *testing.T) {
|
||||||
}
|
}
|
||||||
for i := 3; i < len(values); i += 2 {
|
for i := 3; i < len(values); i += 2 {
|
||||||
s, _ := redis.Float64(values[i], nil)
|
s, _ := redis.Float64(values[i], nil)
|
||||||
if math.Abs(score-s)/score > 0.0001 {
|
if math.Abs(score-s)/score > epsilon {
|
||||||
t.Errorf("Bad score, score[1]=%g, score[%d]=%g", score, i, s)
|
t.Errorf("Bad score, score[1]=%g, score[%d]=%g", score, i, s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCounter(t *testing.T) {
|
||||||
|
db := newDB(t)
|
||||||
|
defer closeDB(db)
|
||||||
|
|
||||||
|
const key = "127.0.0.1"
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
n, err := db.incrementCounterInternal(key, 1, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if math.Abs(n-1.0) > epsilon {
|
||||||
|
t.Errorf("1: got n=%g, want 1", n)
|
||||||
|
}
|
||||||
|
n, err = db.incrementCounterInternal(key, 1, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if math.Abs(n-2.0)/2.0 > epsilon {
|
||||||
|
t.Errorf("2: got n=%g, want 2", n)
|
||||||
|
}
|
||||||
|
now = now.Add(counterHalflife)
|
||||||
|
n, err = db.incrementCounterInternal(key, 1, now)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if math.Abs(n-2.0)/2.0 > epsilon {
|
||||||
|
t.Errorf("3: got n=%g, want 2", n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -63,18 +63,19 @@ func getBitbucketDoc(client *http.Client, match map[string]string, savedEtag str
|
||||||
return nil, ErrNotModified
|
return nil, ErrNotModified
|
||||||
}
|
}
|
||||||
|
|
||||||
var directory struct {
|
var contents struct {
|
||||||
|
Directories []string
|
||||||
Files []struct {
|
Files []struct {
|
||||||
Path string
|
Path string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := httpGetJSON(client, expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/src/{tag}{dir}/", match), nil, &directory); err != nil {
|
if err := httpGetJSON(client, expand("https://api.bitbucket.org/1.0/repositories/{owner}/{repo}/src/{tag}{dir}/", match), nil, &contents); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var files []*source
|
var files []*source
|
||||||
for _, f := range directory.Files {
|
for _, f := range contents.Files {
|
||||||
_, name := path.Split(f.Path)
|
_, name := path.Split(f.Path)
|
||||||
if isDocFile(name) {
|
if isDocFile(name) {
|
||||||
files = append(files, &source{
|
files = append(files, &source{
|
||||||
|
@ -99,6 +100,7 @@ func getBitbucketDoc(client *http.Client, match map[string]string, savedEtag str
|
||||||
BrowseURL: expand("https://bitbucket.org/{owner}/{repo}/src/{tag}{dir}", match),
|
BrowseURL: expand("https://bitbucket.org/{owner}/{repo}/src/{tag}{dir}", match),
|
||||||
Etag: etag,
|
Etag: etag,
|
||||||
VCS: match["vcs"],
|
VCS: match["vcs"],
|
||||||
|
Subdirectories: contents.Directories,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ package doc
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"errors"
|
||||||
"go/ast"
|
"go/ast"
|
||||||
"go/build"
|
"go/build"
|
||||||
"go/doc"
|
"go/doc"
|
||||||
|
@ -327,7 +328,10 @@ var packageNamePats = []*regexp.Regexp{
|
||||||
|
|
||||||
func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
|
func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, error) {
|
||||||
pkg := imports[path]
|
pkg := imports[path]
|
||||||
if pkg == nil {
|
if pkg != nil {
|
||||||
|
return pkg, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Guess the package name without importing it.
|
// Guess the package name without importing it.
|
||||||
for _, pat := range packageNamePats {
|
for _, pat := range packageNamePats {
|
||||||
m := pat.FindStringSubmatch(path)
|
m := pat.FindStringSubmatch(path)
|
||||||
|
@ -335,12 +339,12 @@ func simpleImporter(imports map[string]*ast.Object, path string) (*ast.Object, e
|
||||||
pkg = ast.NewObj(ast.Pkg, m[1])
|
pkg = ast.NewObj(ast.Pkg, m[1])
|
||||||
pkg.Data = ast.NewScope(nil)
|
pkg.Data = ast.NewScope(nil)
|
||||||
imports[path] = pkg
|
imports[path] = pkg
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pkg, nil
|
return pkg, nil
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("package not found")
|
||||||
|
}
|
||||||
|
|
||||||
type File struct {
|
type File struct {
|
||||||
Name string
|
Name string
|
||||||
|
|
|
@ -17,7 +17,6 @@ package doc
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -73,6 +72,47 @@ func getGitHubDoc(client *http.Client, match map[string]string, savedEtag string
|
||||||
return nil, ErrNotModified
|
return nil, ErrNotModified
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var contents []*struct {
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Git_URL string
|
||||||
|
HTML_URL string
|
||||||
|
}
|
||||||
|
|
||||||
|
err = httpGetJSON(client, expand("https://api.github.com/repos/{owner}/{repo}/contents{dir}?ref={tag}&{cred}", match), nil, &contents)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(contents) == 0 {
|
||||||
|
return nil, NotFoundError{"No files in directory."}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because Github API URLs are case-insensitive, we check that the owner
|
||||||
|
// and repo returned from Github matches the one that we are requesting.
|
||||||
|
if !strings.HasPrefix(contents[0].Git_URL, expand("https://api.github.com/repos/{owner}/{repo}/", match)) {
|
||||||
|
return nil, NotFoundError{"Github import path has incorrect case."}
|
||||||
|
}
|
||||||
|
|
||||||
|
var files []*source
|
||||||
|
var subdirs []string
|
||||||
|
|
||||||
|
for _, item := range contents {
|
||||||
|
switch {
|
||||||
|
case item.Type == "dir":
|
||||||
|
if isValidPathElement(item.Name) {
|
||||||
|
subdirs = append(subdirs, item.Name)
|
||||||
|
}
|
||||||
|
case isDocFile(item.Name):
|
||||||
|
files = append(files, &source{
|
||||||
|
name: item.Name,
|
||||||
|
browseURL: item.HTML_URL,
|
||||||
|
rawURL: item.Git_URL + "?" + gitHubCred,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
var tree struct {
|
var tree struct {
|
||||||
Tree []struct {
|
Tree []struct {
|
||||||
Url string
|
Url string
|
||||||
|
@ -116,6 +156,7 @@ func getGitHubDoc(client *http.Client, match map[string]string, savedEtag string
|
||||||
if !inTree {
|
if !inTree {
|
||||||
return nil, NotFoundError{"Directory tree does not contain Go files."}
|
return nil, NotFoundError{"Directory tree does not contain Go files."}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
if err := fetchFiles(client, files, gitHubRawHeader); err != nil {
|
if err := fetchFiles(client, files, gitHubRawHeader); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -136,6 +177,7 @@ func getGitHubDoc(client *http.Client, match map[string]string, savedEtag string
|
||||||
BrowseURL: browseURL,
|
BrowseURL: browseURL,
|
||||||
Etag: commit,
|
Etag: commit,
|
||||||
VCS: "git",
|
VCS: "git",
|
||||||
|
Subdirectories: subdirs,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ var (
|
||||||
googleRepoRe = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`)
|
googleRepoRe = regexp.MustCompile(`id="checkoutcmd">(hg|git|svn)`)
|
||||||
googleRevisionRe = regexp.MustCompile(`<h2>(?:[^ ]+ - )?Revision *([^:]+):`)
|
googleRevisionRe = regexp.MustCompile(`<h2>(?:[^ ]+ - )?Revision *([^:]+):`)
|
||||||
googleEtagRe = regexp.MustCompile(`^(hg|git|svn)-`)
|
googleEtagRe = regexp.MustCompile(`^(hg|git|svn)-`)
|
||||||
googleFileRe = regexp.MustCompile(`<li><a href="([^"/]+)"`)
|
googleFileRe = regexp.MustCompile(`<li><a href="([^"]+)"`)
|
||||||
googlePattern = regexp.MustCompile(`^code\.google\.com/p/(?P<repo>[a-z0-9\-]+)(:?\.(?P<subrepo>[a-z0-9\-]+))?(?P<dir>/[a-z0-9A-Z_.\-/]+)?$`)
|
googlePattern = regexp.MustCompile(`^code\.google\.com/p/(?P<repo>[a-z0-9\-]+)(:?\.(?P<subrepo>[a-z0-9\-]+))?(?P<dir>/[a-z0-9A-Z_.\-/]+)?$`)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -54,10 +54,17 @@ func getGoogleDoc(client *http.Client, match map[string]string, savedEtag string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var subdirs []string
|
||||||
var files []*source
|
var files []*source
|
||||||
for _, m := range googleFileRe.FindAllSubmatch(p, -1) {
|
for _, m := range googleFileRe.FindAllSubmatch(p, -1) {
|
||||||
fname := string(m[1])
|
fname := string(m[1])
|
||||||
if isDocFile(fname) {
|
switch {
|
||||||
|
case strings.HasSuffix(fname, "/"):
|
||||||
|
fname = fname[:len(fname)-1]
|
||||||
|
if isValidPathElement(fname) {
|
||||||
|
subdirs = append(subdirs, fname)
|
||||||
|
}
|
||||||
|
case isDocFile(fname):
|
||||||
files = append(files, &source{
|
files = append(files, &source{
|
||||||
name: fname,
|
name: fname,
|
||||||
browseURL: expand("http://code.google.com/p/{repo}/source/browse{dir}/{0}{query}", match, fname),
|
browseURL: expand("http://code.google.com/p/{repo}/source/browse{dir}/{0}{query}", match, fname),
|
||||||
|
|
|
@ -487,6 +487,10 @@ var validTLD = map[string]bool{
|
||||||
var validHost = regexp.MustCompile(`^[-a-z0-9]+(?:\.[-a-z0-9]+)+$`)
|
var validHost = regexp.MustCompile(`^[-a-z0-9]+(?:\.[-a-z0-9]+)+$`)
|
||||||
var validPathElement = regexp.MustCompile(`^[-A-Za-z0-9~+][-A-Za-z0-9_.]*$`)
|
var validPathElement = regexp.MustCompile(`^[-A-Za-z0-9~+][-A-Za-z0-9_.]*$`)
|
||||||
|
|
||||||
|
func isValidPathElement(s string) bool {
|
||||||
|
return validPathElement.MatchString(s) && s != "testdata"
|
||||||
|
}
|
||||||
|
|
||||||
// IsValidRemotePath returns true if importPath is structurally valid for "go get".
|
// IsValidRemotePath returns true if importPath is structurally valid for "go get".
|
||||||
func IsValidRemotePath(importPath string) bool {
|
func IsValidRemotePath(importPath string) bool {
|
||||||
|
|
||||||
|
@ -506,7 +510,7 @@ func IsValidRemotePath(importPath string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, part := range parts[1:] {
|
for _, part := range parts[1:] {
|
||||||
if !validPathElement.MatchString(part) || part == "testdata" {
|
if !isValidPathElement(part) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
12
doc/vcs.go
12
doc/vcs.go
|
@ -89,7 +89,7 @@ type vcsCmd struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
var vcsCmds = map[string]*vcsCmd{
|
var vcsCmds = map[string]*vcsCmd{
|
||||||
"git": &vcsCmd{
|
"git": {
|
||||||
schemes: []string{"http", "https", "git"},
|
schemes: []string{"http", "https", "git"},
|
||||||
download: downloadGit,
|
download: downloadGit,
|
||||||
},
|
},
|
||||||
|
@ -216,10 +216,14 @@ func getVCSDoc(client *http.Client, match map[string]string, etagSaved string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
var files []*source
|
var files []*source
|
||||||
|
var subdirs []string
|
||||||
for _, fi := range fis {
|
for _, fi := range fis {
|
||||||
if fi.IsDir() || !isDocFile(fi.Name()) {
|
switch {
|
||||||
continue
|
case fi.IsDir():
|
||||||
|
if isValidPathElement(fi.Name()) {
|
||||||
|
subdirs = append(subdirs, fi.Name())
|
||||||
}
|
}
|
||||||
|
case isDocFile(fi.Name()):
|
||||||
b, err := ioutil.ReadFile(path.Join(d, fi.Name()))
|
b, err := ioutil.ReadFile(path.Join(d, fi.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -230,6 +234,7 @@ func getVCSDoc(client *http.Client, match map[string]string, etagSaved string) (
|
||||||
data: b,
|
data: b,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create the documentation.
|
// Create the documentation.
|
||||||
|
|
||||||
|
@ -243,6 +248,7 @@ func getVCSDoc(client *http.Client, match map[string]string, etagSaved string) (
|
||||||
BrowseURL: "",
|
BrowseURL: "",
|
||||||
Etag: etag,
|
Etag: etag,
|
||||||
VCS: match["vcs"],
|
VCS: match["vcs"],
|
||||||
|
Subdirectories: subdirs,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
|
||||||
"github.com/garyburd/gddo/database"
|
"github.com/garyburd/gddo/database"
|
||||||
)
|
)
|
||||||
|
@ -27,14 +29,47 @@ var statsCommand = &command{
|
||||||
usage: "stats",
|
usage: "stats",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type itemSize struct {
|
||||||
|
path string
|
||||||
|
size int
|
||||||
|
}
|
||||||
|
|
||||||
|
type bySizeDesc []itemSize
|
||||||
|
|
||||||
|
func (p bySizeDesc) Len() int { return len(p) }
|
||||||
|
func (p bySizeDesc) Less(i, j int) bool { return p[i].size > p[j].size }
|
||||||
|
func (p bySizeDesc) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||||
|
|
||||||
func stats(c *command) {
|
func stats(c *command) {
|
||||||
if len(c.flag.Args()) != 0 {
|
if len(c.flag.Args()) != 0 {
|
||||||
c.printUsage()
|
c.printUsage()
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
_, err := database.New()
|
db, err := database.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Println("DONE")
|
|
||||||
|
var packageSizes []itemSize
|
||||||
|
projectSizes := make(map[string]int)
|
||||||
|
err = db.Do(func(pi *database.PackageInfo) error {
|
||||||
|
packageSizes = append(packageSizes, itemSize{pi.PDoc.ImportPath, pi.Size})
|
||||||
|
projectSizes[pi.PDoc.ProjectRoot] += pi.Size
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
var sizes []itemSize
|
||||||
|
for path, size := range projectSizes {
|
||||||
|
sizes = append(sizes, itemSize{path, size})
|
||||||
|
}
|
||||||
|
sort.Sort(bySizeDesc(sizes))
|
||||||
|
for _, size := range sizes {
|
||||||
|
fmt.Printf("%6d %s\n", size.size, size.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Sort(bySizeDesc(packageSizes))
|
||||||
|
for _, size := range packageSizes {
|
||||||
|
fmt.Printf("%6d %s\n", size.size, size.path)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
Двоичный файл не отображается.
До Ширина: | Высота: | Размер: 198 B После Ширина: | Высота: | Размер: 1.4 KiB |
|
@ -12,10 +12,13 @@ h4 { margin-top: 20px; }
|
||||||
}
|
}
|
||||||
|
|
||||||
#x-footer {
|
#x-footer {
|
||||||
padding-top: 15px;
|
padding-top: 14px;
|
||||||
padding-bottom: 15px;
|
padding-bottom: 15px;
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
background-color: #eee;
|
background-color: #eee;
|
||||||
|
border-top-style: solid;
|
||||||
|
border-top-width: 1px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#x-pkginfo {
|
#x-pkginfo {
|
||||||
|
@ -25,12 +28,6 @@ h4 { margin-top: 20px; }
|
||||||
margin-bottom: 15px;
|
margin-bottom: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 768px) {
|
|
||||||
#x-search {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
code {
|
code {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -55,22 +52,29 @@ pre .com {
|
||||||
color: rgb(147, 161, 161);
|
color: rgb(147, 161, 161);
|
||||||
}
|
}
|
||||||
|
|
||||||
a, .navbar-brand {
|
a, .navbar-default .navbar-brand {
|
||||||
color: #375eab;
|
color: #375eab;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-default {
|
.navbar-default, #x-footer {
|
||||||
background-color: #375eab;
|
background-color: hsl(209, 51%, 92%);
|
||||||
|
border-color: hsl(209, 51%, 88%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar, #x-footer {
|
.navbar-default .navbar-nav > .active > a,
|
||||||
background-color: #E0EBF5;
|
.navbar-default .navbar-nav > .active > a:hover,
|
||||||
|
.navbar-default .navbar-nav > .active > a:focus {
|
||||||
|
background-color: hsl(209, 51%, 88%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav > .active > a,
|
.navbar-default .navbar-nav > li > a:hover,
|
||||||
.navbar-nav > .active > a:hover,
|
.navbar-default .navbar-nav > li > a:focus {
|
||||||
.navbar-nav > .active > a:focus {
|
color: #000;
|
||||||
background-color: #e7e7e7;
|
}
|
||||||
|
|
||||||
|
.panel-default > .panel-heading {
|
||||||
|
color: #333;
|
||||||
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
#x-file .highlight {
|
#x-file .highlight {
|
||||||
|
|
|
@ -72,8 +72,10 @@ $(function() {
|
||||||
|
|
||||||
$('span.timeago').timeago();
|
$('span.timeago').timeago();
|
||||||
if (window.location.hash.substring(0, 9) == '#example-') {
|
if (window.location.hash.substring(0, 9) == '#example-') {
|
||||||
console.log(window.location.hash.substring(1, 9));
|
var id = '#ex-' + window.location.hash.substring(9);
|
||||||
$('#ex-' + window.location.hash.substring(9)).addClass('in').height('auto');
|
console.log(id);
|
||||||
|
console.log($(id));
|
||||||
|
$(id).addClass('in').removeClass('collapse').height('auto');
|
||||||
}
|
}
|
||||||
|
|
||||||
var highlighted;
|
var highlighted;
|
||||||
|
|
|
@ -15,7 +15,7 @@ and more.
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-sm-6">
|
<div class="col-sm-6">
|
||||||
{{with .Popular}}
|
{{with .Popular}}
|
||||||
<h4>Popular Packages</h4>
|
<h4>Popular Projects</h4>
|
||||||
<ul class="list-unstyled">
|
<ul class="list-unstyled">
|
||||||
{{range .}}<li><a href="/{{.Path}}">{{.Path}}</a>{{end}}
|
{{range .}}<li><a href="/{{.Path}}">{{.Path}}</a>{{end}}
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -28,6 +28,7 @@ and more.
|
||||||
<li><a href="/-/go">Go Standard Packages</a>
|
<li><a href="/-/go">Go Standard Packages</a>
|
||||||
<li><a href="/-/subrepo">Go Sub-repository Packages</a>
|
<li><a href="/-/subrepo">Go Sub-repository Packages</a>
|
||||||
<li><a href="https://code.google.com/p/go-wiki/wiki/Projects">Projects @ go-wiki</a>
|
<li><a href="https://code.google.com/p/go-wiki/wiki/Projects">Projects @ go-wiki</a>
|
||||||
|
<li><a href="http://go-search.org/">Go Search</a>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,34 +6,38 @@
|
||||||
{{template "Head" $}}
|
{{template "Head" $}}
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="navbar navbar-static-top">
|
<nav class="navbar navbar-default" role="navigation">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-responsive-collapse">
|
<div class="navbar-header">
|
||||||
|
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
|
||||||
|
<span class="sr-only">Toggle navigation</span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
<span class="icon-bar"></span>
|
<span class="icon-bar"></span>
|
||||||
</button>
|
</button>
|
||||||
<a class="navbar-brand" href="/">GoDoc</a>
|
<a class="navbar-brand" href="/"><strong>GoDoc</strong></a>
|
||||||
<div class="nav-collapse collapse navbar-responsive-collapse">
|
</div>
|
||||||
|
<div class="collapse navbar-collapse">
|
||||||
<ul class="nav navbar-nav">
|
<ul class="nav navbar-nav">
|
||||||
<li{{if equal "home.html" templateName}} class="active"{{end}}><a href="/">Home</a></li>
|
<li{{if equal "home.html" templateName}} class="active"{{end}}><a href="/">Home</a></li>
|
||||||
<li{{if equal "index.html" templateName}} class="active"{{end}}><a href="/-/index">Index</a></li>
|
<li{{if equal "index.html" templateName}} class="active"{{end}}><a href="/-/index">Index</a></li>
|
||||||
<li{{if equal "about.html" templateName}} class="active"{{end}}><a href="/-/about">About</a></li>
|
<li{{if equal "about.html" templateName}} class="active"{{end}}><a href="/-/about">About</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<form class="navbar-form" id="x-search" action="/"><input class="form-control" id="x-search-query" type="text" name="q" placeholder="Search"></form>
|
<form class="navbar-nav navbar-form navbar-right" id="x-search" action="/" role="search"><input class="form-control" id="x-search-query" type="text" name="q" placeholder="Search"></form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</nav>
|
||||||
<div class="container"><div class="row"><div class="col-12">
|
|
||||||
|
<div class="container">
|
||||||
{{template "Body" $}}
|
{{template "Body" $}}
|
||||||
</div></div></div>
|
</div>
|
||||||
<div id="x-footer" class="clearfix">
|
<div id="x-footer" class="clearfix">
|
||||||
<div class="container"><div class="row"><div class="col-12">
|
<div class="container">
|
||||||
<a href="mailto:info@godoc.org">Feedback</a>
|
<a href="mailto:info@godoc.org">Feedback</a>
|
||||||
<span class="text-muted">|</span> <a href="https://github.com/garyburd/gddo/issues">Website Issues</a>
|
<span class="text-muted">|</span> <a href="https://github.com/garyburd/gddo/issues">Website Issues</a>
|
||||||
<span class="text-muted">|</span> <a href="http://golang.org/">Go Language</a>
|
<span class="text-muted">|</span> <a href="http://golang.org/">Go Language</a>
|
||||||
<span class="pull-right"><a href="#">Back to top</a></span>
|
<span class="pull-right"><a href="#">Back to top</a></span>
|
||||||
</div></div></div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="x-shortcuts" tabindex="-1" class="modal fade">
|
<div id="x-shortcuts" tabindex="-1" class="modal fade">
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
{{with .AllExamples}}<h4 id="pkg-examples">Examples <a class="permalink" href="#pkg-examples">¶</a></h4><ul class="list-unstyled">{{range . }}
|
{{with .AllExamples}}<h4 id="pkg-examples">Examples <a class="permalink" href="#pkg-examples">¶</a></h4><ul class="list-unstyled">{{range . }}
|
||||||
<li><a href="#example-{{.Id}}" onclick="$('#ex-{{.Id}}').addClass('in').height('auto')">{{.Label}}</a>{{end}}
|
<li><a href="#example-{{.Id}}" onclick="$('#ex-{{.Id}}').addClass('in').removeClass('collapse').height('auto')">{{.Label}}</a>{{end}}
|
||||||
</ul>{{else}}<span id="pkg-examples"></span>{{end}}
|
</ul>{{else}}<span id="pkg-examples"></span>{{end}}
|
||||||
|
|
||||||
<h4 id="pkg-files">{{with .BrowseURL}}<a href="{{.}}">Package Files</a>{{else}}Package Files{{end}} <a class="permalink" href="#pkg-files">¶</a></h4>
|
<h4 id="pkg-files">{{with .BrowseURL}}<a href="{{.}}">Package Files</a>{{else}}Package Files{{end}} <a class="permalink" href="#pkg-files">¶</a></h4>
|
||||||
|
@ -65,10 +65,10 @@
|
||||||
{{template "PkgCmdFooter" $}}
|
{{template "PkgCmdFooter" $}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "Examples"}}{{if .}}<div class="accordian">{{range .}}
|
{{define "Examples"}}{{if .}}<div class="panel-group">{{range .}}
|
||||||
<div class="accordion-group">
|
<div class="panel panel-default" id="example-{{.Id}}">
|
||||||
<div class="accordion-heading"><a class="accordion-toggle" data-toggle="collapse" id="example-{{.Id}}" href="#ex-{{.Id}}">Example{{with .Example.Name}} ({{.}}){{end}}</a></div>
|
<div class="panel-heading"><a class="accordion-toggle" data-toggle="collapse" href="#ex-{{.Id}}">Example{{with .Example.Name}} ({{.}}){{end}}</a></div>
|
||||||
<div id="ex-{{.Id}}" class="accordion-body collapse"><div class="accordion-inner">
|
<div id="ex-{{.Id}}" class="panel-collapse collapse"><div class="panel-body">
|
||||||
{{with .Example.Doc}}<p>{{.|comment}}{{end}}
|
{{with .Example.Doc}}<p>{{.|comment}}{{end}}
|
||||||
<p>Code:{{if .Example.Play}}<span class="pull-right"><a href="?play={{.Id}}">play</a> </span>{{end}}
|
<p>Code:{{if .Example.Play}}<span class="pull-right"><a href="?play={{.Id}}">play</a> </span>{{end}}
|
||||||
<pre>{{code .Example.Code nil}}</pre>
|
<pre>{{code .Example.Code nil}}</pre>
|
||||||
|
|
|
@ -4,6 +4,8 @@
|
||||||
<div class="well">
|
<div class="well">
|
||||||
{{template "SearchBox" .q}}
|
{{template "SearchBox" .q}}
|
||||||
</div>
|
</div>
|
||||||
|
<p>Search on <a href="http://go-search.org/search?q={{.q}}">Go-Search</a>
|
||||||
|
or <a href="https://github.com/search?q={{.q}}+language:go">GitHub</a>.
|
||||||
{{if .pkgs}}
|
{{if .pkgs}}
|
||||||
{{template "Pkgs" .pkgs}}
|
{{template "Pkgs" .pkgs}}
|
||||||
{{else}}
|
{{else}}
|
||||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -314,6 +314,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
Carousel.DEFAULTS = {
|
Carousel.DEFAULTS = {
|
||||||
interval: 5000
|
interval: 5000
|
||||||
, pause: 'hover'
|
, pause: 'hover'
|
||||||
|
, wrap: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Carousel.prototype.cycle = function (e) {
|
Carousel.prototype.cycle = function (e) {
|
||||||
|
@ -378,12 +379,15 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
var fallback = type == 'next' ? 'first' : 'last'
|
var fallback = type == 'next' ? 'first' : 'last'
|
||||||
var that = this
|
var that = this
|
||||||
|
|
||||||
|
if (!$next.length) {
|
||||||
|
if (!this.options.wrap) return
|
||||||
|
$next = this.$element.find('.item')[fallback]()
|
||||||
|
}
|
||||||
|
|
||||||
this.sliding = true
|
this.sliding = true
|
||||||
|
|
||||||
isCycling && this.pause()
|
isCycling && this.pause()
|
||||||
|
|
||||||
$next = $next.length ? $next : this.$element.find('.item')[fallback]()
|
|
||||||
|
|
||||||
var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
|
var e = $.Event('slide.bs.carousel', { relatedTarget: $next[0], direction: direction })
|
||||||
|
|
||||||
if ($next.hasClass('active')) return
|
if ($next.hasClass('active')) return
|
||||||
|
@ -535,7 +539,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
this.$element.trigger(startEvent)
|
this.$element.trigger(startEvent)
|
||||||
if (startEvent.isDefaultPrevented()) return
|
if (startEvent.isDefaultPrevented()) return
|
||||||
|
|
||||||
var actives = this.$parent && this.$parent.find('> .accordion-group > .in')
|
var actives = this.$parent && this.$parent.find('> .panel > .in')
|
||||||
|
|
||||||
if (actives && actives.length) {
|
if (actives && actives.length) {
|
||||||
var hasData = actives.data('bs.collapse')
|
var hasData = actives.data('bs.collapse')
|
||||||
|
@ -656,7 +660,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
var $parent = parent && $(parent)
|
var $parent = parent && $(parent)
|
||||||
|
|
||||||
if (!data || !data.transitioning) {
|
if (!data || !data.transitioning) {
|
||||||
if ($parent) $parent.find('[data-toggle=collapse][data-parent=' + parent + ']').not($this).addClass('collapsed')
|
if ($parent) $parent.find('[data-toggle=collapse][data-parent="' + parent + '"]').not($this).addClass('collapsed')
|
||||||
$this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
|
$this[$target.hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -707,7 +711,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
clearMenus()
|
clearMenus()
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
if ('ontouchstart' in document.documentElement) {
|
if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
|
||||||
// if mobile we we use a backdrop because click events don't delegate
|
// if mobile we we use a backdrop because click events don't delegate
|
||||||
$('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
|
$('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus)
|
||||||
}
|
}
|
||||||
|
@ -719,9 +723,9 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
$parent
|
$parent
|
||||||
.toggleClass('open')
|
.toggleClass('open')
|
||||||
.trigger('shown.bs.dropdown')
|
.trigger('shown.bs.dropdown')
|
||||||
}
|
|
||||||
|
|
||||||
$this.focus()
|
$this.focus()
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -847,11 +851,11 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
|
|
||||||
var Modal = function (element, options) {
|
var Modal = function (element, options) {
|
||||||
this.options = options
|
this.options = options
|
||||||
this.$element = $(element).on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
|
this.$element = $(element)
|
||||||
this.$backdrop =
|
this.$backdrop =
|
||||||
this.isShown = null
|
this.isShown = null
|
||||||
|
|
||||||
if (this.options.remote) this.$element.find('.modal-body').load(this.options.remote)
|
if (this.options.remote) this.$element.load(this.options.remote)
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.DEFAULTS = {
|
Modal.DEFAULTS = {
|
||||||
|
@ -860,13 +864,13 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
, show: true
|
, show: true
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.prototype.toggle = function () {
|
Modal.prototype.toggle = function (_relatedTarget) {
|
||||||
return this[!this.isShown ? 'show' : 'hide']()
|
return this[!this.isShown ? 'show' : 'hide'](_relatedTarget)
|
||||||
}
|
}
|
||||||
|
|
||||||
Modal.prototype.show = function () {
|
Modal.prototype.show = function (_relatedTarget) {
|
||||||
var that = this
|
var that = this
|
||||||
var e = $.Event('show.bs.modal')
|
var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })
|
||||||
|
|
||||||
this.$element.trigger(e)
|
this.$element.trigger(e)
|
||||||
|
|
||||||
|
@ -876,6 +880,8 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
|
|
||||||
this.escape()
|
this.escape()
|
||||||
|
|
||||||
|
this.$element.on('click.dismiss.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))
|
||||||
|
|
||||||
this.backdrop(function () {
|
this.backdrop(function () {
|
||||||
var transition = $.support.transition && that.$element.hasClass('fade')
|
var transition = $.support.transition && that.$element.hasClass('fade')
|
||||||
|
|
||||||
|
@ -895,13 +901,15 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
|
|
||||||
that.enforceFocus()
|
that.enforceFocus()
|
||||||
|
|
||||||
|
var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })
|
||||||
|
|
||||||
transition ?
|
transition ?
|
||||||
that.$element
|
that.$element.find('.modal-dialog') // wait for modal to slide in
|
||||||
.one($.support.transition.end, function () {
|
.one($.support.transition.end, function () {
|
||||||
that.$element.focus().trigger('shown.bs.modal')
|
that.$element.focus().trigger(e)
|
||||||
})
|
})
|
||||||
.emulateTransitionEnd(300) :
|
.emulateTransitionEnd(300) :
|
||||||
that.$element.focus().trigger('shown.bs.modal')
|
that.$element.focus().trigger(e)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -923,6 +931,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
this.$element
|
this.$element
|
||||||
.removeClass('in')
|
.removeClass('in')
|
||||||
.attr('aria-hidden', true)
|
.attr('aria-hidden', true)
|
||||||
|
.off('click.dismiss.modal')
|
||||||
|
|
||||||
$.support.transition && this.$element.hasClass('fade') ?
|
$.support.transition && this.$element.hasClass('fade') ?
|
||||||
this.$element
|
this.$element
|
||||||
|
@ -975,7 +984,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
|
this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
|
||||||
.appendTo(document.body)
|
.appendTo(document.body)
|
||||||
|
|
||||||
this.$element.on('click', $.proxy(function (e) {
|
this.$element.on('click.dismiss.modal', $.proxy(function (e) {
|
||||||
if (e.target !== e.currentTarget) return
|
if (e.target !== e.currentTarget) return
|
||||||
this.options.backdrop == 'static'
|
this.options.backdrop == 'static'
|
||||||
? this.$element[0].focus.call(this.$element[0])
|
? this.$element[0].focus.call(this.$element[0])
|
||||||
|
@ -1014,15 +1023,15 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
|
|
||||||
var old = $.fn.modal
|
var old = $.fn.modal
|
||||||
|
|
||||||
$.fn.modal = function (option) {
|
$.fn.modal = function (option, _relatedTarget) {
|
||||||
return this.each(function () {
|
return this.each(function () {
|
||||||
var $this = $(this)
|
var $this = $(this)
|
||||||
var data = $this.data('bs.modal')
|
var data = $this.data('bs.modal')
|
||||||
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)
|
||||||
|
|
||||||
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
|
||||||
if (typeof option == 'string') data[option]()
|
if (typeof option == 'string') data[option](_relatedTarget)
|
||||||
else if (options.show) data.show()
|
else if (options.show) data.show(_relatedTarget)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1050,23 +1059,21 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
$target
|
$target
|
||||||
.modal(option)
|
.modal(option, this)
|
||||||
.one('hide', function () {
|
.one('hide', function () {
|
||||||
$this.is(':visible') && $this.focus()
|
$this.is(':visible') && $this.focus()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
$(function () {
|
$(document)
|
||||||
var $body = $(document.body)
|
.on('show.bs.modal', '.modal', function () { $(document.body).addClass('modal-open') })
|
||||||
.on('shown.bs.modal', '.modal', function () { $body.addClass('modal-open') })
|
.on('hidden.bs.modal', '.modal', function () { $(document.body).removeClass('modal-open') })
|
||||||
.on('hidden.bs.modal', '.modal', function () { $body.removeClass('modal-open') })
|
|
||||||
})
|
|
||||||
|
|
||||||
}(window.jQuery);
|
}(window.jQuery);
|
||||||
|
|
||||||
/* ========================================================================
|
/* ========================================================================
|
||||||
* Bootstrap: tooltip.js v3.0.0
|
* Bootstrap: tooltip.js v3.0.0
|
||||||
* http://twbs.github.com/bootstrap/javascript.html#affix
|
* http://twbs.github.com/bootstrap/javascript.html#tooltip
|
||||||
* Inspired by the original jQuery.tipsy by Jason Frame
|
* Inspired by the original jQuery.tipsy by Jason Frame
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* Copyright 2012 Twitter, Inc.
|
* Copyright 2012 Twitter, Inc.
|
||||||
|
@ -1157,22 +1164,27 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
return options
|
return options
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.enter = function (obj) {
|
Tooltip.prototype.getDelegateOptions = function () {
|
||||||
var defaults = this.getDefaults()
|
|
||||||
var options = {}
|
var options = {}
|
||||||
|
var defaults = this.getDefaults()
|
||||||
|
|
||||||
this._options && $.each(this._options, function (key, value) {
|
this._options && $.each(this._options, function (key, value) {
|
||||||
if (defaults[key] != value) options[key] = value
|
if (defaults[key] != value) options[key] = value
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
|
||||||
|
Tooltip.prototype.enter = function (obj) {
|
||||||
var self = obj instanceof this.constructor ?
|
var self = obj instanceof this.constructor ?
|
||||||
obj : $(obj.currentTarget)[this.type](options).data('bs.' + this.type)
|
obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
|
||||||
|
|
||||||
clearTimeout(self.timeout)
|
clearTimeout(self.timeout)
|
||||||
|
|
||||||
|
self.hoverState = 'in'
|
||||||
|
|
||||||
if (!self.options.delay || !self.options.delay.show) return self.show()
|
if (!self.options.delay || !self.options.delay.show) return self.show()
|
||||||
|
|
||||||
self.hoverState = 'in'
|
|
||||||
self.timeout = setTimeout(function () {
|
self.timeout = setTimeout(function () {
|
||||||
if (self.hoverState == 'in') self.show()
|
if (self.hoverState == 'in') self.show()
|
||||||
}, self.options.delay.show)
|
}, self.options.delay.show)
|
||||||
|
@ -1180,13 +1192,14 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
|
|
||||||
Tooltip.prototype.leave = function (obj) {
|
Tooltip.prototype.leave = function (obj) {
|
||||||
var self = obj instanceof this.constructor ?
|
var self = obj instanceof this.constructor ?
|
||||||
obj : $(obj.currentTarget)[this.type](this._options).data('bs.' + this.type)
|
obj : $(obj.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type)
|
||||||
|
|
||||||
clearTimeout(self.timeout)
|
clearTimeout(self.timeout)
|
||||||
|
|
||||||
|
self.hoverState = 'out'
|
||||||
|
|
||||||
if (!self.options.delay || !self.options.delay.hide) return self.hide()
|
if (!self.options.delay || !self.options.delay.hide) return self.hide()
|
||||||
|
|
||||||
self.hoverState = 'out'
|
|
||||||
self.timeout = setTimeout(function () {
|
self.timeout = setTimeout(function () {
|
||||||
if (self.hoverState == 'out') self.hide()
|
if (self.hoverState == 'out') self.hide()
|
||||||
}, self.options.delay.hide)
|
}, self.options.delay.hide)
|
||||||
|
@ -1245,7 +1258,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
.addClass(placement)
|
.addClass(placement)
|
||||||
}
|
}
|
||||||
|
|
||||||
var calculatedOffset = this.getCalcuatedOffset(placement, pos, actualWidth, actualHeight)
|
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
|
||||||
|
|
||||||
this.applyPlacement(calculatedOffset, placement)
|
this.applyPlacement(calculatedOffset, placement)
|
||||||
this.$element.trigger('shown.bs.' + this.type)
|
this.$element.trigger('shown.bs.' + this.type)
|
||||||
|
@ -1320,7 +1333,9 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
var $tip = this.tip()
|
var $tip = this.tip()
|
||||||
var e = $.Event('hide.bs.' + this.type)
|
var e = $.Event('hide.bs.' + this.type)
|
||||||
|
|
||||||
function complete() { $tip.detach() }
|
function complete() {
|
||||||
|
if (that.hoverState != 'in') $tip.detach()
|
||||||
|
}
|
||||||
|
|
||||||
this.$element.trigger(e)
|
this.$element.trigger(e)
|
||||||
|
|
||||||
|
@ -1358,7 +1373,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
}, this.$element.offset())
|
}, this.$element.offset())
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.getCalcuatedOffset = function (placement, pos, actualWidth, actualHeight) {
|
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
|
||||||
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
||||||
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
|
||||||
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
|
placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
|
||||||
|
@ -1405,7 +1420,7 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
}
|
}
|
||||||
|
|
||||||
Tooltip.prototype.toggle = function (e) {
|
Tooltip.prototype.toggle = function (e) {
|
||||||
var self = e ? $(e.currentTarget)[this.type](this._options).data('bs.' + this.type) : this
|
var self = e ? $(e.currentTarget)[this.type](this.getDelegateOptions()).data('bs.' + this.type) : this
|
||||||
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
|
self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1503,7 +1518,9 @@ if (!jQuery) { throw new Error("Bootstrap requires jQuery") }
|
||||||
|
|
||||||
$tip.removeClass('fade top bottom left right in')
|
$tip.removeClass('fade top bottom left right in')
|
||||||
|
|
||||||
$tip.find('.popover-title:empty').hide()
|
// IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
|
||||||
|
// this manually by checking the contents.
|
||||||
|
if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
Popover.prototype.hasContent = function () {
|
Popover.prototype.hasContent = function () {
|
||||||
|
|
Различия файлов скрыты, потому что одна или несколько строк слишком длинны
|
@ -65,15 +65,15 @@ func runBackgroundTasks() {
|
||||||
|
|
||||||
func doCrawl() error {
|
func doCrawl() error {
|
||||||
// Look for new package to crawl.
|
// Look for new package to crawl.
|
||||||
importPath, err := db.GetNewCrawl()
|
importPath, hasSubdirs, err := db.PopNewCrawl()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("db.GetNewCrawl() returned error %v", err)
|
log.Printf("db.PopNewCrawl() returned error %v", err)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if importPath != "" {
|
if importPath != "" {
|
||||||
if pdoc, err := crawlDoc("new", importPath, nil, false, time.Time{}); err != nil || pdoc == nil {
|
if pdoc, err := crawlDoc("new", importPath, nil, hasSubdirs, time.Time{}); pdoc == nil && err == nil {
|
||||||
if err := db.SetBadCrawl(importPath); err != nil {
|
if err := db.AddBadCrawl(importPath); err != nil {
|
||||||
log.Printf("ERROR db.SetBadCrawl(%q): %v", importPath, err)
|
log.Printf("ERROR db.AddBadCrawl(%q): %v", importPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -34,10 +34,10 @@ func exists(path string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// crawlDoc fetches the package documentation from the VCS and updates the database.
|
// crawlDoc fetches the package documentation from the VCS and updates the database.
|
||||||
func crawlDoc(source string, path string, pdoc *doc.Package, hasSubdirs bool, nextCrawl time.Time) (*doc.Package, error) {
|
func crawlDoc(source string, importPath string, pdoc *doc.Package, hasSubdirs bool, nextCrawl time.Time) (*doc.Package, error) {
|
||||||
message := []interface{}{source}
|
message := []interface{}{source}
|
||||||
defer func() {
|
defer func() {
|
||||||
message = append(message, path)
|
message = append(message, importPath)
|
||||||
log.Println(message...)
|
log.Println(message...)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
@ -56,49 +56,56 @@ func crawlDoc(source string, path string, pdoc *doc.Package, hasSubdirs bool, ne
|
||||||
|
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
var err error
|
var err error
|
||||||
if i := strings.Index(path, "/src/pkg/"); i > 0 && doc.IsGoRepoPath(path[i+len("/src/pkg/"):]) {
|
if i := strings.Index(importPath, "/src/pkg/"); i > 0 && doc.IsGoRepoPath(importPath[i+len("/src/pkg/"):]) {
|
||||||
// Go source tree mirror.
|
// Go source tree mirror.
|
||||||
pdoc = nil
|
pdoc = nil
|
||||||
err = doc.NotFoundError{Message: "Go source tree mirror."}
|
err = doc.NotFoundError{Message: "Go source tree mirror."}
|
||||||
} else if i := strings.Index(path, "/libgo/go/"); i > 0 && doc.IsGoRepoPath(path[i+len("/libgo/go/"):]) {
|
} else if i := strings.Index(importPath, "/libgo/go/"); i > 0 && doc.IsGoRepoPath(importPath[i+len("/libgo/go/"):]) {
|
||||||
// Go Frontend source tree mirror.
|
// Go Frontend source tree mirror.
|
||||||
pdoc = nil
|
pdoc = nil
|
||||||
err = doc.NotFoundError{Message: "Go Frontend source tree mirror."}
|
err = doc.NotFoundError{Message: "Go Frontend source tree mirror."}
|
||||||
} else if m := nestedProjectPat.FindStringIndex(path); m != nil && exists(path[m[0]+1:]) {
|
} else if m := nestedProjectPat.FindStringIndex(importPath); m != nil && exists(importPath[m[0]+1:]) {
|
||||||
pdoc = nil
|
pdoc = nil
|
||||||
err = doc.NotFoundError{Message: "Copy of other project."}
|
err = doc.NotFoundError{Message: "Copy of other project."}
|
||||||
} else if blocked, e := db.IsBlocked(path); blocked && e == nil {
|
} else if blocked, e := db.IsBlocked(importPath); blocked && e == nil {
|
||||||
pdoc = nil
|
pdoc = nil
|
||||||
err = doc.NotFoundError{Message: "Blocked."}
|
err = doc.NotFoundError{Message: "Blocked."}
|
||||||
} else {
|
} else {
|
||||||
var pdocNew *doc.Package
|
var pdocNew *doc.Package
|
||||||
pdocNew, err = doc.Get(httpClient, path, etag)
|
pdocNew, err = doc.Get(httpClient, importPath, etag)
|
||||||
message = append(message, "fetch:", int64(time.Since(start)/time.Millisecond))
|
message = append(message, "fetch:", int64(time.Since(start)/time.Millisecond))
|
||||||
if err != doc.ErrNotModified {
|
if err == nil && pdocNew.Name == "" && !hasSubdirs {
|
||||||
|
pdoc = nil
|
||||||
|
err = doc.NotFoundError{Message: "No Go files or subdirs"}
|
||||||
|
} else if err != doc.ErrNotModified {
|
||||||
pdoc = pdocNew
|
pdoc = pdocNew
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextCrawl = start.Add(*maxAge)
|
nextCrawl = start.Add(*maxAge)
|
||||||
if strings.HasPrefix(path, "github.com/") || (pdoc != nil && len(pdoc.Errors) > 0) {
|
switch {
|
||||||
|
case strings.HasPrefix(importPath, "github.com/") || (pdoc != nil && len(pdoc.Errors) > 0):
|
||||||
nextCrawl = start.Add(*maxAge * 7)
|
nextCrawl = start.Add(*maxAge * 7)
|
||||||
|
case strings.HasPrefix(importPath, "gist.github.com/"):
|
||||||
|
// Don't spend time on gists. It's silly thing to do.
|
||||||
|
nextCrawl = start.Add(*maxAge * 30)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch {
|
switch {
|
||||||
case err == nil:
|
case err == nil:
|
||||||
message = append(message, "put:", pdoc.Etag)
|
message = append(message, "put:", pdoc.Etag)
|
||||||
if err := db.Put(pdoc, nextCrawl); err != nil {
|
if err := db.Put(pdoc, nextCrawl); err != nil {
|
||||||
log.Printf("ERROR db.Put(%q): %v", path, err)
|
log.Printf("ERROR db.Put(%q): %v", importPath, err)
|
||||||
}
|
}
|
||||||
case err == doc.ErrNotModified:
|
case err == doc.ErrNotModified:
|
||||||
message = append(message, "touch")
|
message = append(message, "touch")
|
||||||
if err := db.SetNextCrawlEtag(pdoc.ProjectRoot, pdoc.Etag, nextCrawl); err != nil {
|
if err := db.SetNextCrawlEtag(pdoc.ProjectRoot, pdoc.Etag, nextCrawl); err != nil {
|
||||||
log.Printf("ERROR db.SetNextCrawl(%q): %v", path, err)
|
log.Printf("ERROR db.SetNextCrawl(%q): %v", importPath, err)
|
||||||
}
|
}
|
||||||
case doc.IsNotFound(err):
|
case doc.IsNotFound(err):
|
||||||
message = append(message, "notfound:", err)
|
message = append(message, "notfound:", err)
|
||||||
if err := db.Delete(path); err != nil {
|
if err := db.Delete(importPath); err != nil {
|
||||||
log.Printf("ERROR db.Delete(%q): %v", path, err)
|
log.Printf("ERROR db.Delete(%q): %v", importPath, err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
message = append(message, "ERROR:", err)
|
message = append(message, "ERROR:", err)
|
||||||
|
|
|
@ -130,11 +130,27 @@ func templateExt(req *web.Request) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
robotPat = regexp.MustCompile(`(:?\+https?://)|(?:\Wbot\W)`)
|
robotPat = regexp.MustCompile(`(:?\+https?://)|(?:\Wbot\W)|(?:^Python-urllib)|(?:^Go )|(?:^Java/)`)
|
||||||
)
|
)
|
||||||
|
|
||||||
func isRobot(req *web.Request) bool {
|
func isRobot(req *web.Request) bool {
|
||||||
return *robot || robotPat.MatchString(req.Header.Get(web.HeaderUserAgent))
|
if robotPat.MatchString(req.Header.Get(web.HeaderUserAgent)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
host := req.RemoteAddr
|
||||||
|
if h, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
||||||
|
host = h
|
||||||
|
}
|
||||||
|
n, err := db.IncrementCounter(host, 1)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error incrementing counter for %s, %v\n", host, err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if n > *robot {
|
||||||
|
log.Printf("robot %.2f %s %s", n, host, req.Header.Get(web.HeaderUserAgent))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func popularLinkReferral(req *web.Request) bool {
|
func popularLinkReferral(req *web.Request) bool {
|
||||||
|
@ -300,7 +316,12 @@ func servePackage(resp web.Response, req *web.Request) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return executeTemplate(resp, "importers.html", web.StatusOK, nil, map[string]interface{}{
|
template := "importers.html"
|
||||||
|
if requestType == robotRequest {
|
||||||
|
// Hide back links from robots.
|
||||||
|
template = "importers_robot.html"
|
||||||
|
}
|
||||||
|
return executeTemplate(resp, template, web.StatusOK, nil, map[string]interface{}{
|
||||||
"pkgs": pkgs,
|
"pkgs": pkgs,
|
||||||
"pdoc": newTDoc(pdoc),
|
"pdoc": newTDoc(pdoc),
|
||||||
})
|
})
|
||||||
|
@ -736,7 +757,7 @@ func defaultBase(path string) string {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
db *database.Database
|
db *database.Database
|
||||||
robot = flag.Bool("robot", false, "Robot mode")
|
robot = flag.Float64("robot", 100, "Request counter threshold for robots")
|
||||||
assetsDir = flag.String("assets", filepath.Join(defaultBase("github.com/garyburd/gddo/gddo-server"), "assets"), "Base directory for templates and static files.")
|
assetsDir = flag.String("assets", filepath.Join(defaultBase("github.com/garyburd/gddo/gddo-server"), "assets"), "Base directory for templates and static files.")
|
||||||
gzAssetsDir = flag.String("gzassets", "", "Base directory for compressed static files.")
|
gzAssetsDir = flag.String("gzassets", "", "Base directory for compressed static files.")
|
||||||
presentDir = flag.String("present", defaultBase("code.google.com/p/go.talks/present"), "Base directory for templates and static files.")
|
presentDir = flag.String("present", defaultBase("code.google.com/p/go.talks/present"), "Base directory for templates and static files.")
|
||||||
|
@ -827,6 +848,7 @@ func main() {
|
||||||
{"dir.html", "common.html", "layout.html"},
|
{"dir.html", "common.html", "layout.html"},
|
||||||
{"home.html", "common.html", "layout.html"},
|
{"home.html", "common.html", "layout.html"},
|
||||||
{"importers.html", "common.html", "layout.html"},
|
{"importers.html", "common.html", "layout.html"},
|
||||||
|
{"importers_robot.html", "common.html", "layout.html"},
|
||||||
{"imports.html", "common.html", "layout.html"},
|
{"imports.html", "common.html", "layout.html"},
|
||||||
{"file.html", "common.html", "layout.html"},
|
{"file.html", "common.html", "layout.html"},
|
||||||
{"index.html", "common.html", "layout.html"},
|
{"index.html", "common.html", "layout.html"},
|
||||||
|
@ -946,7 +968,10 @@ func main() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer listener.Close()
|
defer listener.Close()
|
||||||
s := &server.Server{Listener: listener, Handler: h} // add logger
|
s := &server.Server{
|
||||||
|
Listener: listener,
|
||||||
|
Handler: web.ProxyHeaderHandler("X-Real-Ip", "X-Scheme", h),
|
||||||
|
}
|
||||||
err = s.Serve()
|
err = s.Serve()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Server", err)
|
log.Fatal("Server", err)
|
||||||
|
|
|
@ -157,7 +157,8 @@ func (pdoc *tdoc) Breadcrumbs(templateName string) htemp.HTML {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
buf.WriteString(`<span class="text-muted">/</span>`)
|
buf.WriteString(`<span class="text-muted">/</span>`)
|
||||||
}
|
}
|
||||||
link := j < len(pdoc.ImportPath) || (templateName != "cmd.html" && templateName != "pkg.html")
|
link := j < len(pdoc.ImportPath) ||
|
||||||
|
(templateName != "dir.html" && templateName != "cmd.html" && templateName != "pkg.html")
|
||||||
if link {
|
if link {
|
||||||
buf.WriteString(`<a href="`)
|
buf.WriteString(`<a href="`)
|
||||||
buf.WriteString(formatPathFrag(pdoc.ImportPath[:j], ""))
|
buf.WriteString(formatPathFrag(pdoc.ImportPath[:j], ""))
|
||||||
|
|
Загрузка…
Ссылка в новой задаче