Major rewrite

* use dep for vendoring
* lets encrypt
* moved web to transfer.sh-web repo
* single command install
* added first tests
This commit is contained in:
Remco
2017-03-22 18:09:21 +01:00
parent 6d68ad982f
commit cb6e5cb0c7
1917 changed files with 424197 additions and 260688 deletions

10
vendor/github.com/golang/gddo/gddo-server/app.yaml generated vendored Normal file
View File

@@ -0,0 +1,10 @@
# This YAML file is used for local deployment with GAE development environment.
runtime: go
vm: true
api_version: 1
threadsafe: true
handlers:
- url: /.*
script: IGNORED
secure: always

View File

@@ -0,0 +1,4 @@
<?xml version="1.0"?>
<users>
<user>6F3E495D5591D0B1308072CA245E8849</user>
</users>

View File

@@ -0,0 +1,2 @@
User-agent: *
Disallow: *

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

View File

@@ -0,0 +1 @@
google-site-verification: google3d2f3cd4cc2bb44b.html

View File

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,8 @@
User-agent: *
Disallow: /*?imports
Disallow: /*?importers
Disallow: /*?import-graph*
Disallow: /*?gosrc*
Disallow: /*?file*
Disallow: /*?play*
Disallow: /*?tools

View File

@@ -0,0 +1,82 @@
.container { max-width: 970px; }
.section-header {
padding-bottom: 4px;
margin: 20px 0 10px;
border-bottom: 1px solid #eeeeee;
}
/* Sidebar navigation (copied from bootstrap docs.css) */
/* First level of nav */
.gddo-sidebar {
margin-top: 5px;
margin-bottom: 30px;
padding-bottom: 10px;
text-shadow: 0 1px 0 #fff;
border-radius: 5px;
}
/* By default it's not affixed in mobile views, so undo that */
.gddo-sidebar .nav.affix {
position: static;
}
.gddo-sidebar .nav {
overflow: auto;
height: 95%;
}
/* All levels of nav */
.gddo-sidebar .nav > li > a {
display: block;
color: #716b7a;
padding: 5px 0px;
}
.gddo-sidebar .nav > li > a:hover,
.gddo-sidebar .nav > li > a:focus {
text-decoration: none;
background-color: #e5e3e9;
}
.gddo-sidebar .nav > .active > a,
.gddo-sidebar .nav > .active:hover > a,
.gddo-sidebar .nav > .active:focus > a {
font-weight: bold;
color: #563d7c;
background-color: transparent;
}
/* Nav: second level (shown on .active) */
.gddo-sidebar .nav .nav {
display: none; /* Hide by default, but at >768px, show it */
margin-bottom: 8px;
}
.gddo-sidebar .nav .nav > li > a {
padding-top: 3px;
padding-bottom: 3px;
padding-left: 15px;
font-size: 90%;
}
/* Show and affix the side nav when space allows it */
@media screen and (min-width: 992px) {
.gddo-sidebar .nav > .active > ul {
display: block;
}
/* Widen the fixed sidebar */
.gddo-sidebar .nav.affix,
.gddo-sidebar .nav.affix-bottom {
width: 213px;
}
.gddo-sidebar .nav.affix {
position: fixed; /* Undo the static from mobile first approach */
top: 10px;
}
.gddo-sidebar .nav.affix-bottom {
position: absolute; /* Undo the static from mobile first approach */
}
.gddo-sidebar .nav.affix-bottom .bs-sidenav,
.gddo-sidebar .nav.affix .bs-sidenav {
margin-top: 0;
margin-bottom: 0;
}
}

View File

@@ -0,0 +1,139 @@
html { background-color: whitesmoke; }
body { background-color: white; }
h4 { margin-top: 20px; }
.container { max-width: 728px; }
#x-projnav {
min-height: 20px;
margin-bottom: 20px;
background-color: #eee;
padding: 9px;
border-radius: 3px;
}
#x-footer {
padding-top: 14px;
padding-bottom: 15px;
margin-top: 5px;
background-color: #eee;
border-top-style: solid;
border-top-width: 1px;
}
.highlighted {
background-color: #FDFF9E;
}
#x-pkginfo {
margin-top: 25px;
border-top: 1px solid #ccc;
padding-top: 20px;
margin-bottom: 15px;
}
code {
background-color: inherit;
border: none;
color: #222;
padding: 0;
}
pre {
color: #222;
overflow: auto;
white-space: pre;
word-break: normal;
word-wrap: normal;
}
.funcdecl > pre {
white-space: pre-wrap;
word-break: break-all;
word-wrap: break-word;
}
pre .com {
color: #006600;
}
.decl {
position: relative;
}
.decl > a {
position: absolute;
top: 0px;
right: 0px;
display: none;
border: 1px solid #ccc;
border-top-right-radius: 4px;
border-bottom-left-radius: 4px;
padding-left: 4px;
padding-right: 4px;
}
.decl > a:hover {
background-color: white;
text-decoration: none;
}
.decl:hover > a {
display: block;
}
a, .navbar-default .navbar-brand {
color: #375eab;
}
.navbar-default, #x-footer {
background-color: hsl(209, 51%, 92%);
border-color: hsl(209, 51%, 88%);
}
.navbar-default .navbar-nav > .active > a,
.navbar-default .navbar-nav > .active > a:hover,
.navbar-default .navbar-nav > .active > a:focus {
background-color: hsl(209, 51%, 88%);
}
.navbar-default .navbar-nav > li > a:hover,
.navbar-default .navbar-nav > li > a:focus {
color: #000;
}
.panel-default > .panel-heading {
color: #333;
background-color: transparent;
}
a.permalink {
display: none;
}
a.uses {
display: none;
color: #666;
font-size: 0.8em;
}
h1:hover .permalink, h2:hover .permalink, h3:hover .permalink, h4:hover .permalink, h5:hover .permalink, h6:hover .permalink, h1:hover .uses, h2:hover .uses, h3:hover .uses, h4:hover .uses, h5:hover .uses, h6:hover .uses {
display: inline;
}
@media (max-width : 768px) {
.form-control {
font-size:16px;
}
}
.synopsis {
opacity: 0.87;
}
.additional-info {
display: block;
opacity: 0.54;
text-transform: uppercase;
font-size: 0.75em;
}

View File

@@ -0,0 +1,231 @@
// jump modal
$(function() {
var all;
var visible;
var active = -1;
var lastFilter = '';
var $body = $('#x-jump-body');
var $list = $('#x-jump-list');
var $filter = $('#x-jump-filter');
var $modal = $('#x-jump');
var update = function(filter) {
lastFilter = filter;
if (active >= 0) {
visible[active].e.removeClass('active');
active = -1;
}
visible = []
var re = new RegExp(filter.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"), "gi");
all.forEach(function (id) {
id.e.detach();
var text = id.text;
if (filter) {
text = id.text.replace(re, function (s) { return '<b>' + s + '</b>'; });
if (text == id.text) {
return
}
}
id.e.html(text + ' ' + '<i>' + id.kind + '</i>');
visible.push(id);
});
$body.scrollTop(0);
if (visible.length > 0) {
active = 0;
visible[active].e.addClass('active');
}
$list.append($.map(visible, function(identifier) { return identifier.e; }));
}
var incrActive = function(delta) {
if (visible.length == 0) {
return
}
visible[active].e.removeClass('active');
active += delta;
if (active < 0) {
active = 0;
$body.scrollTop(0);
} else if (active >= visible.length) {
active = visible.length - 1;
$body.scrollTop($body[0].scrollHeight - $body[0].clientHeight);
} else {
var $e = visible[active].e;
var t = $e.position().top;
var b = t + $e.outerHeight(false);
if (t <= 0) {
$body.scrollTop($body.scrollTop() + t);
} else if (b >= $body.outerHeight(false)) {
$body.scrollTop($body.scrollTop() + b - $body.outerHeight(false));
}
}
visible[active].e.addClass('active');
}
$modal.on('show.bs.modal', function() {
if (!all) {
all = []
var kinds = {'c': 'constant', 'v': 'variable', 'f': 'function', 't': 'type', 'd': 'field', 'm': 'method'}
$('*[id]').each(function() {
var e = $(this);
var id = e.attr('id');
if (/^[^_][^-]*$/.test(id)) {
all.push({
text: id,
ltext: id.toLowerCase(),
kind: kinds[e.closest('[data-kind]').attr('data-kind')],
e: $('<a/>', {href: '#' + id, 'class': 'list-group-item', tabindex: '-1'})
});
}
});
all.sort(function (a, b) {
if (a.ltext > b.ltext) { return 1; }
if (a.ltext < b.ltext) { return -1; }
return 0
});
}
}).on('shown.bs.modal', function() {
update('');
$filter.val('').focus();
}).on('hide.bs.modal', function() {
$filter.blur();
}).on('click', '.list-group-item', function() {
$modal.modal('hide');
});
$filter.on('change keyup', function() {
var filter = $filter.val();
if (filter.toUpperCase() != lastFilter.toUpperCase()) {
update(filter);
}
}).on('keydown', function(e) {
switch(e.which) {
case 38: // up
incrActive(-1);
e.preventDefault();
break;
case 40: // down
incrActive(1);
e.preventDefault();
break;
case 13: // enter
if (active >= 0) {
visible[active].e[0].click();
}
break
}
});
});
$(function() {
if ("onhashchange" in window) {
var highlightedSel = "";
window.onhashchange = function() {
if (highlightedSel) {
$(highlightedSel).removeClass("highlighted");
}
highlightedSel = window.location.hash.replace( /(:|\.|\[|\]|,)/g, "\\$1" );
if (highlightedSel && (highlightedSel.indexOf("example-") == -1)) {
$(highlightedSel).addClass("highlighted");
}
};
window.onhashchange();
}
});
// keyboard shortcuts
$(function() {
var prevCh = null, prevTime = 0, modal = false;
$('.modal').on({
show: function() { modal = true; },
hidden: function() { modal = false; }
});
$(document).on('keypress', function(e) {
var combo = e.timeStamp - prevTime <= 1000;
prevTime = 0;
if (modal) {
return true;
}
var t = e.target.tagName
if (t == 'INPUT' ||
t == 'SELECT' ||
t == 'TEXTAREA' ) {
return true;
}
if (e.target.contentEditable && e.target.contentEditable == 'true') {
return true;
}
if (e.metaKey || e.ctrlKey) {
return true;
}
var ch = String.fromCharCode(e.which);
if (combo) {
switch (prevCh + ch) {
case "gg":
$('html,body').animate({scrollTop: 0},'fast');
return false;
case "gb":
$('html,body').animate({scrollTop: $(document).height()},'fast');
return false;
case "gi":
if ($('#pkg-index').length > 0) {
$('html,body').animate({scrollTop: $("#pkg-index").offset().top},'fast');
return false;
}
case "ge":
if ($('#pkg-examples').length > 0) {
$('html,body').animate({scrollTop: $("#pkg-examples").offset().top},'fast');
return false;
}
}
}
switch (ch) {
case "/":
$('#x-search-query').focus();
return false;
case "?":
$('#x-shortcuts').modal();
return false;
case "f":
if ($('#x-jump').length > 0) {
$('#x-jump').modal();
return false;
}
}
prevCh = ch
prevTime = e.timeStamp
return true;
});
});
// misc
$(function() {
$('span.timeago').timeago();
if (window.location.hash.substring(0, 9) == '#example-') {
var id = '#ex-' + window.location.hash.substring(9);
$(id).addClass('in').removeClass('collapse').height('auto');
}
$(document).on("click", "input.click-select", function(e) {
$(e.target).select();
});
$('body').scrollspy({
target: '.gddo-sidebar',
offset: 10
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="109" height="20"><linearGradient id="a" x2="0" y2="100%"><stop offset="0" stop-color="#bbb" stop-opacity=".1"/><stop offset="1" stop-opacity=".1"/></linearGradient><rect rx="3" width="109" height="20" fill="#555"/><rect rx="3" x="44" width="65" height="20" fill="#5272B4"/><path fill="#5272B4" d="M44 0h4v20h-4z"/><rect rx="3" width="109" height="20" fill="url(#a)"/><g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11"><text x="23" y="15" fill="#010101" fill-opacity=".3">godoc</text><text x="23" y="14">godoc</text><text x="75.5" y="15" fill="#010101" fill-opacity=".3">reference</text><text x="75.5" y="14">reference</text></g></svg>

After

Width:  |  Height:  |  Size: 733 B

View File

@@ -0,0 +1,72 @@
{{define "Head"}}<title>About - GoDoc</title>{{end}}
{{define "Body"}}
<h1>About</h1>
<p>GoDoc hosts documentation for <a href="http://golang.org/">Go</a>
packages on <a href="https://bitbucket.org/">Bitbucket</a>, <a
href="https://github.com/">GitHub</a>, <a
href="https://launchpad.net/">Launchpad</a> and <a
href="http://code.google.com/hosting/">Google Project Hosting</a>.
<p>The source code for GoDoc is available <a
href="https://github.com/golang/gddo">on GitHub</a>.
<p>GoDoc displays documentation for GOOS=linux unless otherwise noted at the
bottom of the documentation page.
<h4 id="howto">Add a package to GoDoc</h4>
<p>GoDoc generates documentation from Go source code. The <a
href="http://blog.golang.org/godoc-documenting-go-code">guidelines</a>
for writing documentation for the <a
href="http://golang.org/cmd/godoc/">godoc</a> tool apply to GoDoc.
<p>It's important to write a good summary of the package in the first sentence
of the package comment. GoDoc indexes the first sentence and displays the first
sentence in package lists.
<p>To add a package to GoDoc, <a href="/">search</a> for the package by import
path. If GoDoc does not already have the documentation for the package, then
GoDoc will fetch the source from the version control system on the fly and add
the documentation.
<p>GoDoc checks for package updates once per day. You can force GoDoc to update
the documentation immediately by clicking the refresh link at the bottom of the
package documentation page.
<p>GoDoc crawls package imports and child directories to find new packages.
<h4 id="remove">Remove a package from GoDoc</h4>
GoDoc automatically removes packages deleted from the version control system
when GoDoc checks for updates to the package. You can force GoDoc to remove a
deleted package immediately by clicking the refresh link at the bottom of the
package documentation page.
If you do not want GoDoc to display documentation for your package, send mail
to golang-dev@googlegroups.com with the import path of the path of the package
that you want to remove.
<h4 id="feedback">Feedback</h4>
<p>Send your ideas, feature requests and questions to the <a href="https://groups.google.com/group/golang-dev">golang-dev mailing list</a>.
Report bugs using the <a href="https://github.com/golang/gddo/issues/new">GitHub Issue Tracker</a>.
<h4 id="shortcuts">Keyboard Shortcuts</h4>
<p>GoDoc has keyboard shortcuts for navigating package documentation
pages. Type '?' on a package page for help.
<h4 id="bookmarklet">Bookmarklet</h4>
<p>The GoDoc bookmarklet navigates from pages on Bitbucket, GitHub Launchpad
and Google Project Hosting to the package documentation. To install the
bookmarklet, click and drag the following link to your bookmark bar: <a
href="javascript:window.location='http://{{.Host}}/?q='+encodeURIComponent(window.location)">GoDoc</a>
<h4>More Documentation</h4>
<p>More documentation about GoDoc is available on <a href="https://github.com/golang/gddo/wiki">the project's GitHub wiki</a>.
{{end}}

View File

@@ -0,0 +1,6 @@
{{define "Head"}}<title>Bot - GoDoc</title>{{end}}
{{define "Body"}}
<p>GoDocBot is godoc.org's robot for fetching Go documentation from version control systems.
<p>Contact: golang-dev@googlegroups.com
{{end}}

View File

@@ -0,0 +1,9 @@
{{define "Head"}}{{template "PkgCmdHeader" $}}{{end}}
{{define "Body"}}
{{template "ProjectNav" $}}
<h2>Command {{$.pdoc.PageName}}</h2>
{{$.pdoc.Doc|comment}}
{{template "PkgFiles" $}}
{{template "PkgCmdFooter" $}}
{{end}}

View File

@@ -0,0 +1,5 @@
{{define "ROOT"}}{{with .pdoc}}
COMMAND DOCUMENTATION
{{.Doc|comment}}
{{template "Subdirs" $}}{{end}}{{end}}

View File

@@ -0,0 +1,126 @@
{{define "Analytics"}}{{with gaAccount}}<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', '{{.}}']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>{{end}}{{end}}
{{define "SearchBox"}}
<form>
<div class="input-group">
<input class="form-control" name="q" autofocus="autofocus" value="{{.}}" placeholder="Search for package by import path or keyword." type="text">
<span class="input-group-btn">
<button class="btn btn-default" type="submit">Go!</button>
</span>
</div>
</form>
{{end}}
{{define "ProjectNav"}}{{template "FlashMessages" .flashMessages}}<div class="clearfix" id="x-projnav">
{{if .pdoc.ProjectRoot}}{{if .pdoc.ProjectURL}}<a href="{{.pdoc.ProjectURL}}"><strong>{{.pdoc.ProjectName}}:</strong></a>{{else}}<strong>{{.pdoc.ProjectName}}:</strong>{{end}}{{else}}<a href="/-/go">Go:</a>{{end}}
{{.pdoc.Breadcrumbs templateName}}
{{if and .pdoc.Name (or templateName "pkg.html" templateName "cmd.html")}}
<span class="pull-right">
{{if not .pdoc.IsCmd}}
<a href="#pkg-index">Index</a>
{{if .pdoc.AllExamples}}<span class="text-muted">|</span> <a href="#pkg-examples">Examples</a>{{end}}
<span class="text-muted">|</span>
{{end}}
<a href="#pkg-files">Files</a>
{{if .pkgs}}<span class="text-muted">|</span> <a href="#pkg-subdirectories">Directories</a>{{end}}
</span>
{{end}}
</div>{{end}}
{{define "Pkgs"}}
<table class="table table-condensed">
<thead><tr><th>Path</th><th>Synopsis</th></tr></thead>
<tbody>{{range .}}<tr><td>{{if .Path|isValidImportPath}}<a href="/{{.Path}}">{{.Path|importPath}}</a>{{else}}{{.Path|importPath}}{{end}}</td><td>{{.Synopsis|importPath}}</td></tr>
{{end}}</tbody>
</table>
{{end}}
{{define "SearchPkgs"}}
<table class="table table-condensed">
<thead><tr><th>Path</th><th>Synopsis</th></tr></thead>
<tbody>{{range .}}
<tr><td>
{{if .Path|isValidImportPath}}
<a href="/{{.Path}}">{{.Path|importPath}}</a>
<ul class="list-inline">
<li class="additional-info">{{.ImportCount}} imports</li>
{{if .Fork}}<li class="additional-info">· fork</li>{{end}}
{{if .Stars}}<li class="additional-info">· {{.Stars}} stars</li>{{end}}
</ul>
{{else}}{{.Path|importPath}}</td>
{{end}}
<td class="synopsis">{{.Synopsis|importPath}}</td></tr>
{{end}}</tbody>
</table>
{{end}}
{{define "PkgCmdHeader"}}{{with .pdoc}}
<title>{{.PageName}} - GoDoc</title>
{{if .Synopsis}}
<meta name="twitter:title" content="{{if .IsCmd}}Command{{else}}Package{{end}} {{.PageName}}">
<meta property="og:title" content="{{if .IsCmd}}Command{{else}}Package{{end}} {{.PageName}}">
<meta name="description" content="{{.Synopsis}}">
<meta name="twitter:description" content="{{.Synopsis}}">
<meta property="og:description" content="{{.Synopsis}}">
<meta name="twitter:card" content="summary">
<meta name="twitter:site" content="@golang">
{{end}}
{{if .Errors}}<meta name="robots" content="NOINDEX">{{end}}
{{end}}{{end}}
{{define "PkgFiles"}}{{with .pdoc}}
<h4 id="pkg-files">
{{with .BrowseURL}}<a href="{{.}}">Package Files</a>{{else}}Package Files{{end}}
<a class="permalink" href="#pkg-files">&para;</a>
</h4>
<p>{{range .Files}}{{if .URL}}<a href="{{.URL}}">{{.Name}}</a>{{else}}{{.Name}}{{end}} {{end}}</p>
{{end}}{{end}}
{{define "PkgCmdFooter"}}
<!-- Bugs -->
{{with .pdoc}}{{with .Notes}}{{with .BUG}}
<h3 id="pkg-note-bug">Bugs <a class="permalink" href="#pkg-note-bug">&para;</a></h3>{{range .}}<p>{{$.pdoc.SourceLink .Pos "☞" true}} {{.Body}}{{end}}
{{end}}{{end}}{{end}}
{{if $.pkgs}}<h3 id="pkg-subdirectories">Directories <a class="permalink" href="#pkg-subdirectories">&para;</a></h3>
<table class="table table-condensed">
<thead><tr><th>Path</th><th>Synopsis</th></tr></thead>
<tbody>{{range $.pkgs}}<tr><td><a href="/{{.Path}}">{{relativePath .Path $.pdoc.ImportPath}}</a><td>{{.Synopsis}}</td></tr>{{end}}</tbody>
</table>
{{end}}
<div id="x-pkginfo">
{{with $.pdoc}}
<form name="x-refresh" method="POST" action="/-/refresh"><input type="hidden" name="path" value="{{.ImportPath}}"></form>
<p>{{if or .Imports $.importerCount}}Package {{.Name}} {{if .Imports}}imports <a href="?imports">{{.Imports|len}} packages</a> (<a href="?import-graph">graph</a>){{end}}{{if and .Imports $.importerCount}} and {{end}}{{if $.importerCount}}is imported by <a href="?importers">{{$.importerCount}} packages</a>{{end}}.{{end}}
{{if not .Updated.IsZero}}Updated <span class="timeago" title="{{.Updated.Format "2006-01-02T15:04:05Z"}}">{{.Updated.Format "2006-01-02"}}</span>{{if or (equal .GOOS "windows") (equal .GOOS "darwin")}} with GOOS={{.GOOS}}{{end}}.{{end}}
<a href="javascript:document.getElementsByName('x-refresh')[0].submit();" title="Refresh this page from the source.">Refresh now</a>.
<a href="?tools">Tools</a> for package owners.
{{.StatusDescription}}
{{end}}
{{with $.pdoc.Errors}}
<p>The <a href="http://golang.org/cmd/go/#Download_and_install_packages_and_dependencies">go get</a>
command cannot install this package because of the following issues:
<ul>
{{range .}}<li>{{.}}{{end}}
</ul>
{{end}}
</div>
{{end}}
{{define "FlashMessages"}}{{range .}}
{{if eq .ID "redir"}}{{if eq (len .Args) 1}}<div class="alert alert-warning">Redirected from {{index .Args 0}}.</div>{{end}}
{{else if eq .ID "refresh"}}{{if eq (len .Args) 1}}<div class="alert alert-danger">Error refreshing package: {{index .Args 0}}</div>{{end}}
{{end}}
{{end}}{{end}}

View File

@@ -0,0 +1,3 @@
{{define "Subdirs"}}{{with $.pkgs}}SUBDIRECTORIES
{{range .}}
{{.Path}}{{end}}{{end}}{{end}}

View File

@@ -0,0 +1,10 @@
{{define "Head"}}
{{template "PkgCmdHeader" $}}
<meta name="robots" content="NOINDEX">
{{end}}
{{define "Body"}}
{{template "ProjectNav" $}}
{{template "PkgCmdFooter" $}}
{{end}}

View File

@@ -0,0 +1 @@
{{define "ROOT"}}{{with .pdoc}}{{template "Subdirs" $}}{{end}}{{end}}

View File

@@ -0,0 +1,23 @@
{{define "ROOT"}}<!DOCTYPE html><html lang="en">
<head>
<title>{{.pdoc.PageName}} graph - GoDoc</title>
<meta name="robots" content="NOINDEX, NOFOLLOW">
<link href="{{staticPath "/-/bootstrap.min.css"}}" rel="stylesheet">
<link href="{{staticPath "/-/site.css"}}" rel="stylesheet">
</head>
<body>
<div class="well-small">
Package <a href="/{{.pdoc.ImportPath}}">{{.pdoc.Name}}</a>
{{if .pdoc.ProjectRoot}}<span class="text-muted">|</span>
{{if .hide}}
<a href="?import-graph">Show</a>
{{else}}
<a href="?import-graph&hide=1">Hide</a> (<a href="?import-graph&hide=2">all</a>)
{{end}}
standard package dependencies.
{{end}}
</div>
{{.svg}}
</body>
{{template "Analytics"}}
</html>{{end}}

View File

@@ -0,0 +1,37 @@
{{define "Head"}}<title>GoDoc</title>
{{/* <link type="application/opensearchdescription+xml" rel="search" href="/-/opensearch.xml?v={{fileHash "templates/opensearch.xml"}}"/> */}}{{end}}
{{define "Body"}}
<div class="jumbotron">
<h2>Search for Go Packages</h2>
{{template "SearchBox" ""}}
</div>
<p>GoDoc hosts documentation for <a href="http://golang.org/">Go</a> packages
on Bitbucket, GitHub, Google Project Hosting and Launchpad. Read the <a
href="/-/about">About Page</a> for information about adding packages to GoDoc
and more.
<div class="row">
<div class="col-sm-6">
{{with .Popular}}
<h4>Popular Packages</h4>
<ul class="list-unstyled">
{{range .}}<li><a href="/{{.Path}}">{{.Path}}</a>{{end}}
</ul>
{{end}}
</div>
<div class="col-sm-6">
<h4>More Packages</h4>
<ul class="list-unstyled">
<li><a href="/-/go">Go Standard Packages</a>
<li><a href="/-/subrepo">Go Sub-repository Packages</a>
<li><a href="https://golang.org/wiki/Projects">Projects @ go-wiki</a>
<li><a href="https://github.com/search?o=desc&amp;q=language%3Ago&amp;s=stars&amp;type=Repositories">Most stars</a>,
<a href="https://github.com/search?o=desc&amp;q=language%3Ago&amp;s=forks&amp;type=Repositories">most forks</a>,
<a href="https://github.com/search?o=desc&amp;q=language%3Ago&amp;s=updated&amp;type=Repositories">recently updated</a> on GitHub
</ul>
</div>
</div>
{{end}}

View File

@@ -0,0 +1,2 @@
{{define "ROOT"}}
{{end}}

View File

@@ -0,0 +1,7 @@
{{define "Head"}}<title>{{.pdoc.PageName}} importers - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
{{define "Body"}}
{{template "ProjectNav" $}}
<h3>Packages that import {{$.pdoc.Name}}</h3>
{{template "Pkgs" $.pkgs}}
{{end}}

View File

@@ -0,0 +1,10 @@
{{define "Head"}}<title>{{.pdoc.PageName}} importers - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
{{define "Body"}}
{{template "ProjectNav" $}}
<h3>Packages that import {{$.pdoc.Name}}</h3>
<table class="table table-condensed">
<thead><tr><th>Path</th><th>Synopsis</th></tr></thead>
<tbody>{{range .pkgs}}<tr><td>{{.Path|importPath}}</td><td>{{.Synopsis|importPath}}</td></tr>{{end}}</tbody>
</table>
{{end}}

View File

@@ -0,0 +1,7 @@
{{define "Head"}}<title>{{.pdoc.PageName}} imports - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
{{define "Body"}}
{{template "ProjectNav" $}}
<h3>Packages imported by {{.pdoc.Name}}</h3>
{{template "Pkgs" $.pkgs}}
{{end}}

View File

@@ -0,0 +1,73 @@
{{define "ROOT"}}<!DOCTYPE html><html lang="en">
<head profile="http://a9.com/-/spec/opensearch/1.1/">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="{{staticPath "/-/bootstrap.min.css"}}" rel="stylesheet">
<link href="{{staticPath "/-/site.css"}}" rel="stylesheet">
{{template "Head" $}}
</head>
<body>
<nav class="navbar navbar-default" role="navigation">
<div class="container">
<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>
</button>
<a class="navbar-brand" href="/"><strong>GoDoc</strong></a>
</div>
<div class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li{{if equal "home.html" templateName}} class="active"{{end}}><a href="/">Home</a></li>
<li{{if equal "about.html" templateName}} class="active"{{end}}><a href="/-/about">About</a></li>
</ul>
<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>
</nav>
<div class="container">
{{template "Body" $}}
</div>
<div id="x-footer" class="clearfix">
<div class="container">
<a href="https://github.com/golang/gddo/issues">Website Issues</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>
</div>
</div>
<div id="x-shortcuts" tabindex="-1" class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Keyboard shortcuts</h4>
</div>
<div class="modal-body">
<table>{{$mutePkg := not (equal "pkg.html" templateName)}}
<tr><td align="right"><b>?</b></td><td> : This menu</td></tr>
<tr><td align="right"><b>/</b></td><td> : Search site</td></tr>
<tr{{if $mutePkg}} class="text-muted"{{end}}><td align="right"><b>f</b></td><td> : Jump to identifier</td></tr>
<tr><td align="right"><b>g</b> then <b>g</b></td><td> : Go to top of page</td></tr>
<tr><td align="right"><b>g</b> then <b>b</b></td><td> : Go to end of page</td></tr>
<tr{{if $mutePkg}} class="text-muted"{{end}}><td align="right"><b>g</b> then <b>i</b></td><td> : Go to index</td></tr>
<tr{{if $mutePkg}} class="text-muted"{{end}}><td align="right"><b>g</b> then <b>e</b></td><td> : Go to examples</td></tr>
</table>
</div>
<div class="modal-footer">
<button type="button" class="btn" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
<script src="{{staticPath "/-/jquery-2.0.3.min.js"}}"></script>
<script src="{{staticPath "/-/bootstrap.min.js"}}"></script>
<script src="{{staticPath "/-/site.js"}}"></script>
{{template "Analytics"}}
</body>
</html>
{{end}}

View File

@@ -0,0 +1,10 @@
{{define "Head"}}<title>Not Found - GoDoc</title>{{end}}
{{define "Body"}}
{{template "FlashMessages" .flashMessages}}
<h1>Not Found</h1>
<p>Oh snap! Our team of gophers could not find the web page you are looking for. Try one of these pages:
<ul>
<li><a href="/">Home</a>
</ul>
{{end}}

View File

@@ -0,0 +1,2 @@
{{define "ROOT"}}NOT FOUND
{{end}}

View File

@@ -0,0 +1,9 @@
{{define "ROOT"}}<?xml version="1.0"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<InputEncoding>UTF-8</InputEncoding>
<ShortName>GoDoc</ShortName>
<Description>GoDoc: Go Documentation Service</Description>
<Url type="text/html" method="get" template="http://{{.}}/?q={searchTerms}"/>
<Url type="application/x-suggestions+json" template="http://{{.}}/-/suggest?q={searchTerms}"/>
</OpenSearchDescription>
{{end}}

View File

@@ -0,0 +1,183 @@
{{define "Head"}}
{{template "PkgCmdHeader" $}}
{{if sidebarEnabled}}
<link href="{{staticPath "/-/sidebar.css"}}" rel="stylesheet">
{{end}}
{{end}}
{{define "Body"}}
{{with .pdoc}}
{{if sidebarEnabled}}
<div class="row">
<!-- Sidebar -->
<div class="gddo-sidebar col-md-3 hidden-xs hidden-sm">
<ul id="sidebar-nav" class="nav" data-spy="affix" data-offset-top="70">
<li class="active"><a href="#pkg-overview">Overview</a></li>
<li><a href="#pkg-index">Index</a></li>
{{if .Examples}}<li><a href="#pkg-examples">Examples</a></li>{{end}}
{{if .Consts}}<li><a href="#pkg-constants">Constants</a></li>{{end}}
{{if .Vars}}<li><a href="#pkg-variables">Variables</a></li>{{end}}
{{if .Funcs}}
<li>
<a href="#pkg-functions">Functions</a>
<ul class="nav">
{{range .Funcs}}<li><a href="#{{.Name}}">{{.Name}}</a></li>{{end}}
</ul>
</li>
{{end}}
{{if .Types}}
<li>
<a href="#pkg-types">Types</a>
<ul class="nav">
{{range .Types}}<li><a href="#{{.Name}}">{{.Name}}</a></li>{{end}}
</ul>
</li>
{{end}}
{{if .Notes.BUG}}<li><a href="#pkg-note-bug">Bugs</a></li>{{end}}
{{if $.pkgs}}<li><a href="#pkg-subdirectories">Directories</a></li>{{end}}
</ul>
</div>
<!-- Content -->
<div class="col-md-9">
{{end}}<!-- end sidebarEnabled -->
{{template "ProjectNav" $}}
<h2 id="pkg-overview">package {{.Name}}</h2>
<p><code>import "{{.ImportPath}}"</code>
{{.Doc|comment}}
{{template "Examples" .|$.pdoc.ObjExamples}}
<!-- Index -->
<h3 id="pkg-index" class="section-header">Index <a class="permalink" href="#pkg-index">&para;</a></h3>
{{if .Truncated}}
<div class="alert">The documentation displayed here is incomplete. Use the godoc command to read the complete documentation.</div>
{{end}}
<ul class="list-unstyled">
{{if .Consts}}<li><a href="#pkg-constants">Constants</a></li>{{end}}
{{if .Vars}}<li><a href="#pkg-variables">Variables</a></li>{{end}}
{{range .Funcs}}<li><a href="#{{.Name}}">{{.Decl.Text}}</a></li>{{end}}
{{range $t := .Types}}
<li><a href="#{{.Name}}">type {{.Name}}</a></li>
{{if or .Funcs .Methods}}<ul>{{end}}
{{range .Funcs}}<li><a href="#{{.Name}}">{{.Decl.Text}}</a></li>{{end}}
{{range .Methods}}<li><a href="#{{$t.Name}}.{{.Name}}">{{.Decl.Text}}</a></li>{{end}}
{{if or .Funcs .Methods}}</ul>{{end}}
{{end}}
{{if .Notes.BUG}}<li><a href="#pkg-note-bug">Bugs</a></li>{{end}}
</ul>
<!-- Examples -->
{{with .AllExamples}}
<h4 id="pkg-examples">Examples <a class="permalink" href="#pkg-examples">&para;</a></h4>
<ul class="list-unstyled">
{{range . }}<li><a href="#example-{{.ID}}" onclick="$('#ex-{{.ID}}').addClass('in').removeClass('collapse').height('auto')">{{.Label}}</a></li>{{end}}
</ul>
{{else}}
<span id="pkg-examples"></span>
{{end}}
<!-- Files -->
{{template "PkgFiles" $}}
<!-- Contants -->
{{if .Consts}}
<h3 id="pkg-constants">Constants <a class="permalink" href="#pkg-constants">&para;</a></h3>
{{range .Consts}}<div class="decl" data-kind="c">{{$.pdoc.SourceLink .Pos "\u2756" false}}{{code .Decl nil}}</div>{{.Doc|comment}}{{end}}
{{end}}
<!-- Variables -->
{{if .Vars}}
<h3 id="pkg-variables">Variables <a class="permalink" href="#pkg-variables">&para;</a></h3>
{{range .Vars}}<div class="decl" data-kind="v">{{$.pdoc.SourceLink .Pos "\u2756" false}}{{code .Decl nil}}</div>{{.Doc|comment}}{{end}}
{{end}}
<!-- Functions -->
{{if sidebarEnabled}}{{if .Funcs}}
<h3 id="pkg-functions" class="section-header">Functions <a class="permalink" href="#pkg-functions">&para;</a></h3>
{{end}}{{end}}
{{range .Funcs}}
<h3 id="{{.Name}}" data-kind="f">func {{$.pdoc.SourceLink .Pos .Name true}} <a class="permalink" href="#{{.Name}}">&para;</a> {{$.pdoc.UsesLink "List Function Callers" .Name}}</h3>
<div class="funcdecl decl">{{$.pdoc.SourceLink .Pos "\u2756" false}}{{code .Decl nil}}</div>{{.Doc|comment}}
{{template "Examples" .|$.pdoc.ObjExamples}}
{{end}}
<!-- Types -->
{{if sidebarEnabled}}{{if .Types}}
<h3 id="pkg-types" class="section-header">Types <a class="permalink" href="#pkg-types">&para;</a></h3>
{{end}}{{end}}
{{range $t := .Types}}
<h3 id="{{.Name}}" data-kind="t">type {{$.pdoc.SourceLink .Pos .Name true}} <a class="permalink" href="#{{.Name}}">&para;</a> {{$.pdoc.UsesLink "List Uses of This Type" .Name}}</h3>
<div class="decl" data-kind="{{if isInterface $t}}m{{else}}d{{end}}">{{$.pdoc.SourceLink .Pos "\u2756" false}}{{code .Decl $t}}</div>{{.Doc|comment}}
{{range .Consts}}<div class="decl" data-kind="c">{{$.pdoc.SourceLink .Pos "\u2756" false}}{{code .Decl nil}}</div>{{.Doc|comment}}{{end}}
{{range .Vars}}<div class="decl" data-kind="v">{{$.pdoc.SourceLink .Pos "\u2756" false}}{{code .Decl nil}}</div>{{.Doc|comment}}{{end}}
{{template "Examples" .|$.pdoc.ObjExamples}}
{{range .Funcs}}
<h4 id="{{.Name}}" data-kind="f">func {{$.pdoc.SourceLink .Pos .Name true}} <a class="permalink" href="#{{.Name}}">&para;</a> {{$.pdoc.UsesLink "List Function Callers" .Name}}</h4>
<div class="funcdecl decl">{{$.pdoc.SourceLink .Pos "\u2756" false}}{{code .Decl nil}}</div>{{.Doc|comment}}
{{template "Examples" .|$.pdoc.ObjExamples}}
{{end}}
{{range .Methods}}
<h4 id="{{$t.Name}}.{{.Name}}" data-kind="m">func ({{.Recv}}) {{$.pdoc.SourceLink .Pos .Name true}} <a class="permalink" href="#{{$t.Name}}.{{.Name}}">&para;</a> {{$.pdoc.UsesLink "List Method Callers" .Orig .Recv .Name}}</h4>
<div class="funcdecl decl">{{$.pdoc.SourceLink .Pos "\u2756" false}}{{code .Decl nil}}</div>{{.Doc|comment}}
{{template "Examples" .|$.pdoc.ObjExamples}}
{{end}}
{{end}}
{{template "PkgCmdFooter" $}}
<div id="x-jump" tabindex="-1" class="modal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">Jump to identifier</h4>
<br class="clearfix">
<input id="x-jump-filter" class="form-control" autocomplete="off" type="text">
</div>
<div id="x-jump-body" class="modal-body" style="height: 260px; overflow: auto;">
<div id="x-jump-list" class="list-group" style="margin-bottom: 0;"></div>
</div>
<div class="modal-footer">
<button type="button" class="btn" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
{{if sidebarEnabled}}
</div>
</div>
{{end}}
{{end}}
{{end}}
{{define "Examples"}}
{{if .}}
<div class="panel-group">
{{range .}}
<div class="panel panel-default" id="example-{{.ID}}">
<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="panel-collapse collapse"><div class="panel-body">
{{with .Example.Doc}}<p>{{.|comment}}{{end}}
<p>Code:{{if .Play}}<span class="pull-right"><a href="?play={{.ID}}">play</a>&nbsp;</span>{{end}}
{{code .Example.Code nil}}
{{with .Example.Output}}<p>Output:<pre>{{.}}</pre>{{end}}
</div></div>
</div>
{{end}}
</div>
{{end}}
{{end}}

View File

@@ -0,0 +1,38 @@
{{define "ROOT"}}{{with .pdoc}}PACKAGE{{if .Name}}
package {{.Name}}
import "{{.ImportPath}}"
{{.Doc|comment}}
{{if .Consts}}
CONSTANTS
{{range .Consts}}{{.Decl.Text}}
{{.Doc|comment}}{{end}}
{{end}}{{if .Vars}}
VARIABLES
{{range .Vars}}{{.Decl.Text}}
{{.Doc|comment}}{{end}}
{{end}}{{if .Funcs}}
FUNCTIONS
{{range .Funcs}}{{.Decl.Text}}
{{.Doc|comment}}
{{end}}{{end}}{{if .Types}}
TYPES
{{range .Types}}{{.Decl.Text}}
{{.Doc|comment}}
{{range .Consts}}{{.Decl.Text}}
{{.Doc|comment}}
{{end}}{{range .Vars}}{{.Decl.Text}}
{{.Doc|comment}}
{{end}}{{range .Funcs}}{{.Decl.Text}}
{{.Doc|comment}}
{{end}}{{range .Methods}}{{.Decl.Text}}
{{.Doc|comment}}
{{end}}{{end}}
{{end}}
{{template "Subdirs" $}}
{{end}}{{end}}{{end}}

View File

@@ -0,0 +1,14 @@
{{define "Head"}}<title>{{.q}} - GoDoc</title><meta name="robots" content="NOINDEX">{{end}}
{{define "Body"}}
<div class="well">
{{template "SearchBox" .q}}
</div>
<p>Try this 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}}
{{template "SearchPkgs" .pkgs}}
{{else}}
<p>No packages found.
{{end}}
{{end}}

View File

@@ -0,0 +1,2 @@
{{define "ROOT"}}{{range .pkgs}}{{.Path}} {{.Synopsis}}
{{end}}{{end}}

View File

@@ -0,0 +1,8 @@
{{define "Head"}}<title>Standard Packages - GoDoc</title><meta name="robots" content="NOINDEX">{{end}}
{{define "Body"}}
<h1>Go Standard Packages</h1>
{{template "Pkgs" .pkgs}}
<p>View the official documentation at <a href="http://golang.org/pkg/">golang.org</a>.
{{end}}

View File

@@ -0,0 +1,23 @@
{{define "Head"}}<title>Go Sub-Repository Packages - GoDoc</title><meta name="robots" content="NOINDEX">{{end}}
{{define "Body"}}
<h1>Go Sub-repository Packages</h1>
These packages are part of the Go Project but outside the main Go tree. They are developed under looser compatibility requirements than the Go core.
<h2>Repositories</h2>
<ul class="list-unstyled">
{{template "subrepo" map "name" "blog" "desc" "the content and server program for blog.golang.org."}}
{{template "subrepo" map "name" "crypto" "desc" "additional cryptography packages."}}
{{template "subrepo" map "name" "exp" "desc" "experimental code (handle with care)."}}
{{template "subrepo" map "name" "image" "desc" "additional imaging packages."}}
{{template "subrepo" map "name" "mobile" "desc" "libraries and build tools for Go on Android."}}
{{template "subrepo" map "name" "net" "desc" "additional networking packages."}}
{{template "subrepo" map "name" "sys" "desc" "for low-level interactions with the operating system."}}
{{template "subrepo" map "name" "talks" "desc" "the content and server program for talks.golang.org."}}
{{template "subrepo" map "name" "text" "desc" "packages for working with text."}}
{{template "subrepo" map "name" "tools" "desc" "godoc, vet, cover, and other tools."}}
</ul>
<h2>Packages</h2>
{{template "Pkgs" .pkgs}}
{{end}}
{{define "subrepo"}}<li><a href="https://go.googlesource.com/{{.name}}/+/master">golang.org/x/{{.name}}</a> — {{.desc}}{{end}}

View File

@@ -0,0 +1,36 @@
{{define "Head"}}<title>{{.pdoc.PageName}} tools - GoDoc</title><meta name="robots" content="NOINDEX, NOFOLLOW">{{end}}
{{define "Body"}}
{{template "ProjectNav" $}}
<h2>Tools for {{$.pdoc.PageName}}</h2>
<h3>Badge</h3>
<p><a href="{{.uri}}"><img src="{{.uri}}?status.svg" alt="GoDoc"></a>
<p>Use one of the snippets below to add a link to GoDoc from your project
website or README file:</a>
<h5>HTML</h5>
<input type="text" value='<a href="{{.uri}}"><img src="{{.uri}}?status.svg" alt="GoDoc"></a>' class="click-select form-control">
<h5>Markdown</h5>
<input type="text" value="[![GoDoc]({{.uri}}?status.svg)]({{.uri}})" class="click-select form-control">
{{if .pdoc.Name}}
<h3>Lint</h3>
<form name="x-lint" method="POST" action="http://go-lint.appspot.com/-/refresh"><input name="importPath" type="hidden" value="{{.pdoc.ImportPath}}"></form>
<p><a href="javascript:document.getElementsByName('x-lint')[0].submit();">Run lint</a> on {{.pdoc.PageName}}.
{{if and (not .pdoc.IsCmd) (not .pdoc.Doc)}}
<p>The {{.pdoc.Name}} package does not have a package declaration
comment. See the <a
href="http://blog.golang.org/godoc-documenting-go-code">Go
documentation guidelines</a> for information on how to write a package
comment. It's important to write a good summary of the package in the
first sentence of the package comment. GoDoc indexes the first sentence
and displays the first sentence in package lists.
{{end}}
{{end}}
<p>&nbsp;
{{end}}

View File

@@ -0,0 +1,184 @@
/**
* Timeago is a jQuery plugin that makes it easy to support automatically
* updating fuzzy timestamps (e.g. "4 minutes ago" or "about 1 day ago").
*
* @name timeago
* @version 1.1.0
* @requires jQuery v1.2.3+
* @author Ryan McGeary
* @license MIT License - http://www.opensource.org/licenses/mit-license.php
*
* For usage and examples, visit:
* http://timeago.yarp.com/
*
* Copyright (c) 2008-2013, Ryan McGeary (ryan -[at]- mcgeary [*dot*] org)
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define(['jquery'], factory);
} else {
// Browser globals
factory(jQuery);
}
}(function ($) {
$.timeago = function(timestamp) {
if (timestamp instanceof Date) {
return inWords(timestamp);
} else if (typeof timestamp === "string") {
return inWords($.timeago.parse(timestamp));
} else if (typeof timestamp === "number") {
return inWords(new Date(timestamp));
} else {
return inWords($.timeago.datetime(timestamp));
}
};
var $t = $.timeago;
$.extend($.timeago, {
settings: {
refreshMillis: 60000,
allowFuture: false,
localeTitle: false,
strings: {
prefixAgo: null,
prefixFromNow: null,
suffixAgo: "ago",
suffixFromNow: "from now",
seconds: "less than a minute",
minute: "about a minute",
minutes: "%d minutes",
hour: "about an hour",
hours: "about %d hours",
day: "a day",
days: "%d days",
month: "about a month",
months: "%d months",
year: "about a year",
years: "%d years",
wordSeparator: " ",
numbers: []
}
},
inWords: function(distanceMillis) {
var $l = this.settings.strings;
var prefix = $l.prefixAgo;
var suffix = $l.suffixAgo;
if (this.settings.allowFuture) {
if (distanceMillis < 0) {
prefix = $l.prefixFromNow;
suffix = $l.suffixFromNow;
}
}
var seconds = Math.abs(distanceMillis) / 1000;
var minutes = seconds / 60;
var hours = minutes / 60;
var days = hours / 24;
var years = days / 365;
function substitute(stringOrFunction, number) {
var string = $.isFunction(stringOrFunction) ? stringOrFunction(number, distanceMillis) : stringOrFunction;
var value = ($l.numbers && $l.numbers[number]) || number;
return string.replace(/%d/i, value);
}
var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) ||
seconds < 90 && substitute($l.minute, 1) ||
minutes < 45 && substitute($l.minutes, Math.round(minutes)) ||
minutes < 90 && substitute($l.hour, 1) ||
hours < 24 && substitute($l.hours, Math.round(hours)) ||
hours < 42 && substitute($l.day, 1) ||
days < 30 && substitute($l.days, Math.round(days)) ||
days < 45 && substitute($l.month, 1) ||
days < 365 && substitute($l.months, Math.round(days / 30)) ||
years < 1.5 && substitute($l.year, 1) ||
substitute($l.years, Math.round(years));
var separator = $l.wordSeparator || "";
if ($l.wordSeparator === undefined) { separator = " "; }
return $.trim([prefix, words, suffix].join(separator));
},
parse: function(iso8601) {
var s = $.trim(iso8601);
s = s.replace(/\.\d+/,""); // remove milliseconds
s = s.replace(/-/,"/").replace(/-/,"/");
s = s.replace(/T/," ").replace(/Z/," UTC");
s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400
return new Date(s);
},
datetime: function(elem) {
var iso8601 = $t.isTime(elem) ? $(elem).attr("datetime") : $(elem).attr("title");
return $t.parse(iso8601);
},
isTime: function(elem) {
// jQuery's `is()` doesn't play well with HTML5 in IE
return $(elem).get(0).tagName.toLowerCase() === "time"; // $(elem).is("time");
}
});
// functions that can be called via $(el).timeago('action')
// init is default when no action is given
// functions are called with context of a single element
var functions = {
init: function(){
var refresh_el = $.proxy(refresh, this);
refresh_el();
var $s = $t.settings;
if ($s.refreshMillis > 0) {
setInterval(refresh_el, $s.refreshMillis);
}
},
update: function(time){
$(this).data('timeago', { datetime: $t.parse(time) });
refresh.apply(this);
}
};
$.fn.timeago = function(action, options) {
var fn = action ? functions[action] : functions.init;
if(!fn){
throw new Error("Unknown function name '"+ action +"' for timeago");
}
// each over objects here and call the requested function
this.each(function(){
fn.call(this, options);
});
return this;
};
function refresh() {
var data = prepareData(this);
if (!isNaN(data.datetime)) {
$(this).text(inWords(data.datetime));
}
return this;
}
function prepareData(element) {
element = $(element);
if (!element.data("timeago")) {
element.data("timeago", { datetime: $t.datetime(element) });
var text = $.trim(element.text());
if ($t.settings.localeTitle) {
element.attr("title", element.data('timeago').datetime.toLocaleString());
} else if (text.length > 0 && !($t.isTime(element) && element.attr("title"))) {
element.attr("title", text);
}
}
return element.data("timeago");
}
function inWords(date) {
return $t.inWords(distance(date));
}
function distance(date) {
return (new Date().getTime() - date.getTime());
}
// fix for IE6 suckage
document.createElement("abbr");
document.createElement("time");
}));

134
vendor/github.com/golang/gddo/gddo-server/background.go generated vendored Normal file
View File

@@ -0,0 +1,134 @@
// Copyright 2017 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"log"
"time"
"github.com/spf13/viper"
"google.golang.org/appengine"
"github.com/golang/gddo/database"
"github.com/golang/gddo/gosrc"
)
type BackgroundTask struct {
name string
fn func() error
interval time.Duration
next time.Time
}
func runBackgroundTasks() {
defer log.Println("ERROR: Background exiting!")
var backgroundTasks = []BackgroundTask{
{
name: "GitHub updates",
fn: readGitHubUpdates,
interval: viper.GetDuration(ConfigGithubInterval),
},
{
name: "Crawl",
fn: doCrawl,
interval: viper.GetDuration(ConfigCrawlInterval),
},
}
sleep := time.Minute
for _, task := range backgroundTasks {
if task.interval > 0 && sleep > task.interval {
sleep = task.interval
}
}
for {
for _, task := range backgroundTasks {
start := time.Now()
if task.interval > 0 && start.After(task.next) {
if err := task.fn(); err != nil {
log.Printf("Task %s: %v", task.name, err)
}
task.next = time.Now().Add(task.interval)
}
}
time.Sleep(sleep)
}
}
func doCrawl() error {
// Look for new package to crawl.
importPath, hasSubdirs, err := db.PopNewCrawl()
if err != nil {
log.Printf("db.PopNewCrawl() returned error %v", err)
return nil
}
if importPath != "" {
if pdoc, err := crawlDoc("new", importPath, nil, hasSubdirs, time.Time{}); pdoc == nil && err == nil {
if err := db.AddBadCrawl(importPath); err != nil {
log.Printf("ERROR db.AddBadCrawl(%q): %v", importPath, err)
}
}
return nil
}
// Crawl existing doc.
pdoc, pkgs, nextCrawl, err := db.Get("-")
if err != nil {
log.Printf("db.Get(\"-\") returned error %v", err)
return nil
}
if pdoc == nil || nextCrawl.After(time.Now()) {
return nil
}
if _, err = crawlDoc("crawl", pdoc.ImportPath, pdoc, len(pkgs) > 0, nextCrawl); err != nil {
// Touch package so that crawl advances to next package.
if err := db.SetNextCrawl(pdoc.ImportPath, time.Now().Add(viper.GetDuration(ConfigMaxAge)/3)); err != nil {
log.Printf("ERROR db.SetNextCrawl(%q): %v", pdoc.ImportPath, err)
}
}
return nil
}
func readGitHubUpdates() error {
const key = "gitHubUpdates"
var last string
if err := db.GetGob(key, &last); err != nil {
return err
}
last, names, err := gosrc.GetGitHubUpdates(httpClient, last)
if err != nil {
return err
}
for _, name := range names {
log.Printf("bump crawl github.com/%s", name)
if err := db.BumpCrawl("github.com/" + name); err != nil {
log.Println("ERROR force crawl:", err)
}
}
if err := db.PutGob(key, last); err != nil {
return err
}
return nil
}
func reindex() {
c := appengine.BackgroundContext()
if err := db.Reindex(c); err != nil {
log.Println("reindex:", err)
}
}
func purgeIndex() {
c := appengine.BackgroundContext()
if err := database.PurgeIndex(c); err != nil {
log.Println("purgeIndex:", err)
}
}

97
vendor/github.com/golang/gddo/gddo-server/browse.go generated vendored Normal file
View File

@@ -0,0 +1,97 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"net/url"
"path"
"regexp"
"strings"
)
func importPathFromGoogleBrowse(m []string) string {
project := m[1]
dir := m[2]
if dir == "" {
dir = "/"
} else if dir[len(dir)-1] == '/' {
dir = dir[:len(dir)-1]
}
subrepo := ""
if len(m[3]) > 0 {
v, _ := url.ParseQuery(m[3][1:])
subrepo = v.Get("repo")
if len(subrepo) > 0 {
subrepo = "." + subrepo
}
}
if strings.HasPrefix(m[4], "#hg%2F") {
d, _ := url.QueryUnescape(m[4][len("#hg%2f"):])
if i := strings.IndexRune(d, '%'); i >= 0 {
d = d[:i]
}
dir = dir + "/" + d
}
return "code.google.com/p/" + project + subrepo + dir
}
var browsePatterns = []struct {
pat *regexp.Regexp
fn func([]string) string
}{
{
// GitHub tree browser.
regexp.MustCompile(`^https?://(github\.com/[^/]+/[^/]+)(?:/tree/[^/]+(/.*))?$`),
func(m []string) string { return m[1] + m[2] },
},
{
// GitHub file browser.
regexp.MustCompile(`^https?://(github\.com/[^/]+/[^/]+)/blob/[^/]+/(.*)$`),
func(m []string) string {
d := path.Dir(m[2])
if d == "." {
return m[1]
}
return m[1] + "/" + d
},
},
{
// GitHub issues, pulls, etc.
regexp.MustCompile(`^https?://(github\.com/[^/]+/[^/]+)(.*)$`),
func(m []string) string { return m[1] },
},
{
// Bitbucket source borwser.
regexp.MustCompile(`^https?://(bitbucket\.org/[^/]+/[^/]+)(?:/src/[^/]+(/[^?]+)?)?`),
func(m []string) string { return m[1] + m[2] },
},
{
// Google Project Hosting source browser.
regexp.MustCompile(`^http:/+code\.google\.com/p/([^/]+)/source/browse(/[^?#]*)?(\?[^#]*)?(#.*)?$`),
importPathFromGoogleBrowse,
},
{
// Launchpad source browser.
regexp.MustCompile(`^https?:/+bazaar\.(launchpad\.net/.*)/files$`),
func(m []string) string { return m[1] },
},
{
regexp.MustCompile(`^https?://(.+)$`),
func(m []string) string { return strings.Trim(m[1], "/") },
},
}
// isBrowserURL returns importPath and true if URL looks like a URL for a VCS
// source browser.
func isBrowseURL(s string) (importPath string, ok bool) {
for _, c := range browsePatterns {
if m := c.pat.FindStringSubmatch(s); m != nil {
return c.fn(m), true
}
}
return "", false
}

View File

@@ -0,0 +1,40 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"testing"
)
var isBrowseURLTests = []struct {
s string
importPath string
ok bool
}{
{"https://github.com/garyburd/gddo/blob/master/doc/code.go", "github.com/garyburd/gddo/doc", true},
{"https://github.com/garyburd/go-oauth/blob/master/.gitignore", "github.com/garyburd/go-oauth", true},
{"https://github.com/garyburd/gddo/issues/154", "github.com/garyburd/gddo", true},
{"https://bitbucket.org/user/repo/src/bd0b661a263e/p1/p2?at=default", "bitbucket.org/user/repo/p1/p2", true},
{"https://bitbucket.org/user/repo/src", "bitbucket.org/user/repo", true},
{"https://bitbucket.org/user/repo", "bitbucket.org/user/repo", true},
{"https://github.com/user/repo", "github.com/user/repo", true},
{"https://github.com/user/repo/tree/master/p1", "github.com/user/repo/p1", true},
{"http://code.google.com/p/project", "code.google.com/p/project", true},
}
func TestIsBrowseURL(t *testing.T) {
for _, tt := range isBrowseURLTests {
importPath, ok := isBrowseURL(tt.s)
if tt.ok {
if importPath != tt.importPath || ok != true {
t.Errorf("IsBrowseURL(%q) = %q, %v; want %q %v", tt.s, importPath, ok, tt.importPath, true)
}
} else if ok {
t.Errorf("IsBrowseURL(%q) = %q, %v; want _, false", tt.s, importPath, ok)
}
}
}

60
vendor/github.com/golang/gddo/gddo-server/client.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2017 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// This file implements an http.Client with request timeouts set by command
// line flags.
package main
import (
"fmt"
"net"
"net/http"
"os"
"github.com/gregjones/httpcache"
"github.com/gregjones/httpcache/memcache"
"github.com/spf13/viper"
"github.com/golang/gddo/httputil"
)
func newHTTPClient() *http.Client {
t := newCacheTransport()
requestTimeout := viper.GetDuration(ConfigRequestTimeout)
t.Transport = &http.Transport{
Proxy: http.ProxyFromEnvironment,
Dial: (&net.Dialer{
Timeout: viper.GetDuration(ConfigDialTimeout),
KeepAlive: requestTimeout / 2,
}).Dial,
ResponseHeaderTimeout: requestTimeout / 2,
TLSHandshakeTimeout: requestTimeout / 2,
}
return &http.Client{
// Wrap the cached transport with GitHub authentication.
Transport: httputil.NewAuthTransport(t),
Timeout: requestTimeout,
}
}
func newCacheTransport() *httpcache.Transport {
// host and port are set by GAE Flex runtime, can be left blank locally.
host := os.Getenv("MEMCACHE_PORT_11211_TCP_ADDR")
if host == "" {
host = "localhost"
}
port := os.Getenv("MEMCACHE_PORT_11211_TCP_PORT")
if port == "" {
port = "11211"
}
addr := fmt.Sprintf("%s:%s", host, port)
return httpcache.NewTransport(
memcache.New(addr),
)
}

127
vendor/github.com/golang/gddo/gddo-server/config.go generated vendored Normal file
View File

@@ -0,0 +1,127 @@
package main
import (
"context"
"os"
"path/filepath"
"strings"
"time"
"github.com/golang/gddo/database"
"github.com/golang/gddo/log"
"github.com/spf13/pflag"
"github.com/spf13/viper"
)
const (
gaeProjectEnvVar = "GAE_LONG_APP_ID"
)
const (
ConfigMaxAge = "max_age"
ConfigGetTimeout = "get_timeout"
ConfigRobotThreshold = "robot"
ConfigAssetsDir = "assets"
ConfigFirstGetTimeout = "first_get_timeout"
ConfigBindAddress = "http"
ConfigProject = "project"
ConfigTrustProxyHeaders = "trust_proxy_headers"
ConfigSidebar = "sidebar"
ConfigDefaultGOOS = "default_goos"
ConfigSourcegraphURL = "sourcegraph_url"
ConfigGithubInterval = "github_interval"
ConfigCrawlInterval = "crawl_interval"
ConfigDialTimeout = "dial_timeout"
ConfigRequestTimeout = "request_timeout"
)
// Initialize configuration
func init() {
ctx := context.Background()
// Automatically detect if we are on App Engine.
if os.Getenv(gaeProjectEnvVar) != "" {
viper.Set("on_appengine", true)
} else {
viper.Set("on_appengine", false)
}
// Setup command line flags
flags := buildFlags()
flags.Parse(os.Args)
if err := viper.BindPFlags(flags); err != nil {
panic(err)
}
// Also fetch from enviorment
viper.SetEnvPrefix("gddo")
viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_"))
viper.AutomaticEnv()
// Automatically get project ID from env on Google App Engine
viper.BindEnv(ConfigProject, gaeProjectEnvVar)
// Read from config.
readViperConfig(ctx)
log.Info(ctx, "config values loaded", "values", viper.AllSettings())
}
func buildFlags() *pflag.FlagSet {
flags := pflag.NewFlagSet("default", pflag.ExitOnError)
flags.StringP("config", "c", "", "path to motd config file")
flags.String("project", "", "Google Cloud Platform project used for Google services")
// TODO(stephenmw): flags.Bool("enable-admin-pages", false, "When true, enables /admin pages")
flags.Float64(ConfigRobotThreshold, 100, "Request counter threshold for robots.")
flags.String(ConfigAssetsDir, filepath.Join(defaultBase("github.com/golang/gddo/gddo-server"), "assets"), "Base directory for templates and static files.")
flags.Duration(ConfigGetTimeout, 8*time.Second, "Time to wait for package update from the VCS.")
flags.Duration(ConfigFirstGetTimeout, 5*time.Second, "Time to wait for first fetch of package from the VCS.")
flags.Duration(ConfigMaxAge, 24*time.Hour, "Update package documents older than this age.")
flags.String(ConfigBindAddress, ":8080", "Listen for HTTP connections on this address.")
flags.Bool(ConfigSidebar, false, "Enable package page sidebar.")
flags.String(ConfigDefaultGOOS, "", "Default GOOS to use when building package documents.")
flags.Bool(ConfigTrustProxyHeaders, false, "If enabled, identify the remote address of the request using X-Real-Ip in header.")
flags.String(ConfigSourcegraphURL, "https://sourcegraph.com", "Link to global uses on Sourcegraph based at this URL (no need for trailing slash).")
flags.Duration(ConfigGithubInterval, 0, "Github updates crawler sleeps for this duration between fetches. Zero disables the crawler.")
flags.Duration(ConfigCrawlInterval, 0, "Package updater sleeps for this duration between package updates. Zero disables updates.")
flags.Duration(ConfigDialTimeout, 5*time.Second, "Timeout for dialing an HTTP connection.")
flags.Duration(ConfigRequestTimeout, 20*time.Second, "Time out for roundtripping an HTTP request.")
// TODO(stephenmw): pass these variables at database creation time.
flags.StringVar(&database.RedisServer, "db-server", database.RedisServer, "URI of Redis server.")
flags.DurationVar(&database.RedisIdleTimeout, "db-idle-timeout", database.RedisIdleTimeout, "Close Redis connections after remaining idle for this duration.")
flags.BoolVar(&database.RedisLog, "db-log", database.RedisLog, "Log database commands")
return flags
}
// readViperConfig finds and then parses a config file. It will log.Fatal if the
// config file was specified or could not parse. Otherwise it will only warn
// that it failed to load the config.
func readViperConfig(ctx context.Context) {
viper.AddConfigPath(".")
viper.AddConfigPath("/etc")
viper.SetConfigName("gddo")
if viper.GetString("config") != "" {
viper.SetConfigFile(viper.GetString("config"))
}
if err := viper.ReadInConfig(); err != nil {
// If a config exists but could not be parsed, we should bail.
if _, ok := err.(viper.ConfigParseError); ok {
log.Fatal(ctx, "failed to parse config", "error", err)
}
// If the user specified a config file location in flags or env and
// we failed to load it, we should bail. If not, it is just a warning.
if viper.GetString("config") != "" {
log.Fatal(ctx, "failed to load configuration file", "error", err)
} else {
log.Warn(ctx, "failed to load configuration file", "error", err)
}
} else {
log.Info(ctx, "loaded configuration file successfully", "path", viper.ConfigFileUsed())
}
}

144
vendor/github.com/golang/gddo/gddo-server/crawl.go generated vendored Normal file
View File

@@ -0,0 +1,144 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"fmt"
"log"
"regexp"
"strings"
"time"
"github.com/spf13/viper"
"github.com/golang/gddo/doc"
"github.com/golang/gddo/gosrc"
)
var testdataPat = regexp.MustCompile(`/testdata(?:/|$)`)
// crawlDoc fetches the package documentation from the VCS and updates the database.
func crawlDoc(source string, importPath string, pdoc *doc.Package, hasSubdirs bool, nextCrawl time.Time) (*doc.Package, error) {
message := []interface{}{source}
defer func() {
message = append(message, importPath)
log.Println(message...)
}()
if !nextCrawl.IsZero() {
d := time.Since(nextCrawl) / time.Hour
if d > 0 {
message = append(message, "late:", int64(d))
}
}
etag := ""
if pdoc != nil {
etag = pdoc.Etag
message = append(message, "etag:", etag)
}
start := time.Now()
var err error
if strings.HasPrefix(importPath, "code.google.com/p/go.") {
// Old import path for Go sub-repository.
pdoc = nil
err = gosrc.NotFoundError{Message: "old Go sub-repo", Redirect: "golang.org/x/" + importPath[len("code.google.com/p/go."):]}
} else if blocked, e := db.IsBlocked(importPath); blocked && e == nil {
pdoc = nil
err = gosrc.NotFoundError{Message: "blocked."}
} else if testdataPat.MatchString(importPath) {
pdoc = nil
err = gosrc.NotFoundError{Message: "testdata."}
} else {
var pdocNew *doc.Package
pdocNew, err = doc.Get(httpClient, importPath, etag)
message = append(message, "fetch:", int64(time.Since(start)/time.Millisecond))
if err == nil && pdocNew.Name == "" && !hasSubdirs {
for _, e := range pdocNew.Errors {
message = append(message, "err:", e)
}
pdoc = nil
err = gosrc.NotFoundError{Message: "no Go files or subdirs"}
} else if _, ok := err.(gosrc.NotModifiedError); !ok {
pdoc = pdocNew
}
}
maxAge := viper.GetDuration(ConfigMaxAge)
nextCrawl = start.Add(maxAge)
switch {
case strings.HasPrefix(importPath, "github.com/") || (pdoc != nil && len(pdoc.Errors) > 0):
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)
}
if err == nil {
message = append(message, "put:", pdoc.Etag)
if err := put(pdoc, nextCrawl); err != nil {
log.Println(err)
}
return pdoc, nil
} else if e, ok := err.(gosrc.NotModifiedError); ok {
if pdoc.Status == gosrc.Active && !isActivePkg(importPath, e.Status) {
if e.Status == gosrc.NoRecentCommits {
e.Status = gosrc.Inactive
}
message = append(message, "archive", e)
pdoc.Status = e.Status
if err := db.Put(pdoc, nextCrawl, false); err != nil {
log.Printf("ERROR db.Put(%q): %v", importPath, err)
}
} else {
// Touch the package without updating and move on to next one.
message = append(message, "touch")
if err := db.SetNextCrawl(importPath, nextCrawl); err != nil {
log.Printf("ERROR db.SetNextCrawl(%q): %v", importPath, err)
}
}
return pdoc, nil
} else if e, ok := err.(gosrc.NotFoundError); ok {
message = append(message, "notfound:", e)
if err := db.Delete(importPath); err != nil {
log.Printf("ERROR db.Delete(%q): %v", importPath, err)
}
return nil, e
} else {
message = append(message, "ERROR:", err)
return nil, err
}
}
func put(pdoc *doc.Package, nextCrawl time.Time) error {
if pdoc.Status == gosrc.NoRecentCommits &&
isActivePkg(pdoc.ImportPath, gosrc.NoRecentCommits) {
pdoc.Status = gosrc.Active
}
if err := db.Put(pdoc, nextCrawl, false); err != nil {
return fmt.Errorf("ERROR db.Put(%q): %v", pdoc.ImportPath, err)
}
return nil
}
// isActivePkg reports whether a package is considered active,
// either because its directory is active or because it is imported by another package.
func isActivePkg(pkg string, status gosrc.DirectoryStatus) bool {
switch status {
case gosrc.Active:
return true
case gosrc.NoRecentCommits:
// It should be inactive only if it has no imports as well.
n, err := db.ImporterCount(pkg)
if err != nil {
log.Printf("ERROR db.ImporterCount(%q): %v", pkg, err)
}
return n > 0
}
return false
}

48
vendor/github.com/golang/gddo/gddo-server/graph.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"bytes"
"errors"
"fmt"
"os/exec"
"strings"
"github.com/golang/gddo/database"
"github.com/golang/gddo/doc"
)
func renderGraph(pdoc *doc.Package, pkgs []database.Package, edges [][2]int) ([]byte, error) {
var in, out bytes.Buffer
fmt.Fprintf(&in, "digraph %s { \n", pdoc.Name)
for i, pkg := range pkgs {
fmt.Fprintf(&in, " n%d [label=\"%s\", URL=\"/%s\", tooltip=\"%s\"];\n",
i, pkg.Path, pkg.Path,
strings.Replace(pkg.Synopsis, `"`, `\"`, -1))
}
for _, edge := range edges {
fmt.Fprintf(&in, " n%d -> n%d;\n", edge[0], edge[1])
}
in.WriteString("}")
cmd := exec.Command("dot", "-Tsvg")
cmd.Stdin = &in
cmd.Stdout = &out
if err := cmd.Run(); err != nil {
return nil, err
}
p := out.Bytes()
i := bytes.Index(p, []byte("<svg"))
if i < 0 {
return nil, errors.New("<svg not found")
}
p = p[i:]
return p, nil
}

77
vendor/github.com/golang/gddo/gddo-server/logging.go generated vendored Normal file
View File

@@ -0,0 +1,77 @@
// Copyright 2016 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"crypto/rand"
"encoding/hex"
"log"
"net/http"
"time"
"cloud.google.com/go/logging"
"github.com/golang/gddo/database"
)
// newGCELogger returns a handler that wraps h but logs each request
// using Google Cloud Logging service.
func newGCELogger(cli *logging.Logger) *GCELogger {
return &GCELogger{cli}
}
type GCELogger struct {
cli *logging.Logger
}
// LogEvent creates an entry in Cloud Logging to record user's behavior. We should only
// use this to log events we are interested in. General request logs are handled by GAE
// automatically in request_log and stderr.
func (g *GCELogger) LogEvent(w http.ResponseWriter, r *http.Request, content interface{}) {
const sessionCookieName = "GODOC_ORG_SESSION_ID"
cookie, err := r.Cookie(sessionCookieName)
if err != nil {
// Generates a random session id and sends it in response.
rs, err := randomString()
if err != nil {
log.Println("error generating a random session id: ", err)
return
}
// This cookie is intentionally short-lived and contains no information
// that might identify the user. Its sole purpose is to tie query
// terms and destination pages together to measure search quality.
cookie = &http.Cookie{
Name: sessionCookieName,
Value: rs,
Expires: time.Now().Add(time.Hour),
}
http.SetCookie(w, cookie)
}
// We must not record the client's IP address, or any other information
// that might compromise the user's privacy.
payload := map[string]interface{}{
sessionCookieName: cookie.Value,
"path": r.URL.RequestURI(),
"method": r.Method,
"referer": r.Referer(),
}
if pkgs, ok := content.([]database.Package); ok {
payload["packages"] = pkgs
}
// Log queues the entry to its internal buffer, or discarding the entry
// if the buffer was full.
g.cli.Log(logging.Entry{
Payload: payload,
})
}
func randomString() (string, error) {
b := make([]byte, 8)
_, err := rand.Read(b)
return hex.EncodeToString(b), err
}

997
vendor/github.com/golang/gddo/gddo-server/main.go generated vendored Normal file
View File

@@ -0,0 +1,997 @@
// Copyright 2017 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
// Command gddo-server is the GoPkgDoc server.
package main
import (
"bytes"
"crypto/md5"
"encoding/json"
"errors"
"fmt"
"go/build"
"html/template"
"io"
"log"
"net/http"
"os"
"path"
"regexp"
"runtime/debug"
"sort"
"strconv"
"strings"
"time"
"cloud.google.com/go/compute/metadata"
"cloud.google.com/go/logging"
"github.com/spf13/viper"
"golang.org/x/net/context"
"google.golang.org/appengine"
"github.com/golang/gddo/database"
"github.com/golang/gddo/doc"
"github.com/golang/gddo/gosrc"
"github.com/golang/gddo/httputil"
)
const (
jsonMIMEType = "application/json; charset=utf-8"
textMIMEType = "text/plain; charset=utf-8"
htmlMIMEType = "text/html; charset=utf-8"
)
var errUpdateTimeout = errors.New("refresh timeout")
type httpError struct {
status int // HTTP status code.
err error // Optional reason for the HTTP error.
}
func (err *httpError) Error() string {
if err.err != nil {
return fmt.Sprintf("status %d, reason %s", err.status, err.err.Error())
}
return fmt.Sprintf("Status %d", err.status)
}
const (
humanRequest = iota
robotRequest
queryRequest
refreshRequest
apiRequest
)
type crawlResult struct {
pdoc *doc.Package
err error
}
// getDoc gets the package documentation from the database or from the version
// control system as needed.
func getDoc(path string, requestType int) (*doc.Package, []database.Package, error) {
if path == "-" {
// A hack in the database package uses the path "-" to represent the
// next document to crawl. Block "-" here so that requests to /- always
// return not found.
return nil, nil, &httpError{status: http.StatusNotFound}
}
pdoc, pkgs, nextCrawl, err := db.Get(path)
if err != nil {
return nil, nil, err
}
needsCrawl := false
switch requestType {
case queryRequest, apiRequest:
needsCrawl = nextCrawl.IsZero() && len(pkgs) == 0
case humanRequest:
needsCrawl = nextCrawl.Before(time.Now())
case robotRequest:
needsCrawl = nextCrawl.IsZero() && len(pkgs) > 0
}
if !needsCrawl {
return pdoc, pkgs, nil
}
c := make(chan crawlResult, 1)
go func() {
pdoc, err := crawlDoc("web ", path, pdoc, len(pkgs) > 0, nextCrawl)
c <- crawlResult{pdoc, err}
}()
timeout := viper.GetDuration(ConfigGetTimeout)
if pdoc == nil {
timeout = viper.GetDuration(ConfigFirstGetTimeout)
}
select {
case cr := <-c:
err = cr.err
if err == nil {
pdoc = cr.pdoc
}
case <-time.After(timeout):
err = errUpdateTimeout
}
switch {
case err == nil:
return pdoc, pkgs, nil
case gosrc.IsNotFound(err):
return nil, nil, err
case pdoc != nil:
log.Printf("Serving %q from database after error getting doc: %v", path, err)
return pdoc, pkgs, nil
case err == errUpdateTimeout:
log.Printf("Serving %q as not found after timeout getting doc", path)
return nil, nil, &httpError{status: http.StatusNotFound}
default:
return nil, nil, err
}
}
func templateExt(req *http.Request) string {
if httputil.NegotiateContentType(req, []string{"text/html", "text/plain"}, "text/html") == "text/plain" {
return ".txt"
}
return ".html"
}
var (
robotPat = regexp.MustCompile(`(:?\+https?://)|(?:\Wbot\W)|(?:^Python-urllib)|(?:^Go )|(?:^Java/)`)
)
func isRobot(req *http.Request) bool {
if robotPat.MatchString(req.Header.Get("User-Agent")) {
return true
}
host := httputil.StripPort(req.RemoteAddr)
n, err := db.IncrementCounter(host, 1)
if err != nil {
log.Printf("error incrementing counter for %s, %v", host, err)
return false
}
if n > viper.GetFloat64(ConfigRobotThreshold) {
log.Printf("robot %.2f %s %s", n, host, req.Header.Get("User-Agent"))
return true
}
return false
}
func popularLinkReferral(req *http.Request) bool {
return strings.HasSuffix(req.Header.Get("Referer"), "//"+req.Host+"/")
}
func isView(req *http.Request, key string) bool {
rq := req.URL.RawQuery
return strings.HasPrefix(rq, key) &&
(len(rq) == len(key) || rq[len(key)] == '=' || rq[len(key)] == '&')
}
// httpEtag returns the package entity tag used in HTTP transactions.
func httpEtag(pdoc *doc.Package, pkgs []database.Package, importerCount int, flashMessages []flashMessage) string {
b := make([]byte, 0, 128)
b = strconv.AppendInt(b, pdoc.Updated.Unix(), 16)
b = append(b, 0)
b = append(b, pdoc.Etag...)
if importerCount >= 8 {
importerCount = 8
}
b = append(b, 0)
b = strconv.AppendInt(b, int64(importerCount), 16)
for _, pkg := range pkgs {
b = append(b, 0)
b = append(b, pkg.Path...)
b = append(b, 0)
b = append(b, pkg.Synopsis...)
}
if viper.GetBool(ConfigSidebar) {
b = append(b, "\000xsb"...)
}
for _, m := range flashMessages {
b = append(b, 0)
b = append(b, m.ID...)
for _, a := range m.Args {
b = append(b, 1)
b = append(b, a...)
}
}
h := md5.New()
h.Write(b)
b = h.Sum(b[:0])
return fmt.Sprintf("\"%x\"", b)
}
func servePackage(resp http.ResponseWriter, req *http.Request) error {
p := path.Clean(req.URL.Path)
if strings.HasPrefix(p, "/pkg/") {
p = p[len("/pkg"):]
}
if p != req.URL.Path {
http.Redirect(resp, req, p, http.StatusMovedPermanently)
return nil
}
if isView(req, "status.svg") {
statusImageHandlerSVG.ServeHTTP(resp, req)
return nil
}
if isView(req, "status.png") {
statusImageHandlerPNG.ServeHTTP(resp, req)
return nil
}
requestType := humanRequest
if isRobot(req) {
requestType = robotRequest
}
importPath := strings.TrimPrefix(req.URL.Path, "/")
pdoc, pkgs, err := getDoc(importPath, requestType)
if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
// To prevent dumb clients from following redirect loops, respond with
// status 404 if the target document is not found.
if _, _, err := getDoc(e.Redirect, requestType); gosrc.IsNotFound(err) {
return &httpError{status: http.StatusNotFound}
}
u := "/" + e.Redirect
if req.URL.RawQuery != "" {
u += "?" + req.URL.RawQuery
}
setFlashMessages(resp, []flashMessage{{ID: "redir", Args: []string{importPath}}})
http.Redirect(resp, req, u, http.StatusFound)
return nil
}
if err != nil {
return err
}
flashMessages := getFlashMessages(resp, req)
if pdoc == nil {
if len(pkgs) == 0 {
return &httpError{status: http.StatusNotFound}
}
pdocChild, _, _, err := db.Get(pkgs[0].Path)
if err != nil {
return err
}
pdoc = &doc.Package{
ProjectName: pdocChild.ProjectName,
ProjectRoot: pdocChild.ProjectRoot,
ProjectURL: pdocChild.ProjectURL,
ImportPath: importPath,
}
}
switch {
case len(req.Form) == 0:
importerCount := 0
if pdoc.Name != "" {
importerCount, err = db.ImporterCount(importPath)
if err != nil {
return err
}
}
etag := httpEtag(pdoc, pkgs, importerCount, flashMessages)
status := http.StatusOK
if req.Header.Get("If-None-Match") == etag {
status = http.StatusNotModified
}
if requestType == humanRequest &&
pdoc.Name != "" && // not a directory
pdoc.ProjectRoot != "" && // not a standard package
!pdoc.IsCmd &&
len(pdoc.Errors) == 0 &&
!popularLinkReferral(req) {
if err := db.IncrementPopularScore(pdoc.ImportPath); err != nil {
log.Printf("ERROR db.IncrementPopularScore(%s): %v", pdoc.ImportPath, err)
}
}
if gceLogger != nil {
gceLogger.LogEvent(resp, req, nil)
}
template := "dir"
switch {
case pdoc.IsCmd:
template = "cmd"
case pdoc.Name != "":
template = "pkg"
}
template += templateExt(req)
return executeTemplate(resp, template, status, http.Header{"Etag": {etag}}, map[string]interface{}{
"flashMessages": flashMessages,
"pkgs": pkgs,
"pdoc": newTDoc(pdoc),
"importerCount": importerCount,
})
case isView(req, "imports"):
if pdoc.Name == "" {
break
}
pkgs, err = db.Packages(pdoc.Imports)
if err != nil {
return err
}
return executeTemplate(resp, "imports.html", http.StatusOK, nil, map[string]interface{}{
"flashMessages": flashMessages,
"pkgs": pkgs,
"pdoc": newTDoc(pdoc),
})
case isView(req, "tools"):
proto := "http"
if req.Host == "godoc.org" {
proto = "https"
}
return executeTemplate(resp, "tools.html", http.StatusOK, nil, map[string]interface{}{
"flashMessages": flashMessages,
"uri": fmt.Sprintf("%s://%s/%s", proto, req.Host, importPath),
"pdoc": newTDoc(pdoc),
})
case isView(req, "importers"):
if pdoc.Name == "" {
break
}
pkgs, err = db.Importers(importPath)
if err != nil {
return err
}
template := "importers.html"
if requestType == robotRequest {
// Hide back links from robots.
template = "importers_robot.html"
}
return executeTemplate(resp, template, http.StatusOK, nil, map[string]interface{}{
"flashMessages": flashMessages,
"pkgs": pkgs,
"pdoc": newTDoc(pdoc),
})
case isView(req, "import-graph"):
if requestType == robotRequest {
return &httpError{status: http.StatusForbidden}
}
if pdoc.Name == "" {
break
}
hide := database.ShowAllDeps
switch req.Form.Get("hide") {
case "1":
hide = database.HideStandardDeps
case "2":
hide = database.HideStandardAll
}
pkgs, edges, err := db.ImportGraph(pdoc, hide)
if err != nil {
return err
}
b, err := renderGraph(pdoc, pkgs, edges)
if err != nil {
return err
}
return executeTemplate(resp, "graph.html", http.StatusOK, nil, map[string]interface{}{
"flashMessages": flashMessages,
"svg": template.HTML(b),
"pdoc": newTDoc(pdoc),
"hide": hide,
})
case isView(req, "play"):
u, err := playURL(pdoc, req.Form.Get("play"), req.Header.Get("X-AppEngine-Country"))
if err != nil {
return err
}
http.Redirect(resp, req, u, http.StatusMovedPermanently)
return nil
case req.Form.Get("view") != "":
// Redirect deprecated view= queries.
var q string
switch view := req.Form.Get("view"); view {
case "imports", "importers":
q = view
case "import-graph":
if req.Form.Get("hide") == "1" {
q = "import-graph&hide=1"
} else {
q = "import-graph"
}
}
if q != "" {
u := *req.URL
u.RawQuery = q
http.Redirect(resp, req, u.String(), http.StatusMovedPermanently)
return nil
}
}
return &httpError{status: http.StatusNotFound}
}
func serveRefresh(resp http.ResponseWriter, req *http.Request) error {
importPath := req.Form.Get("path")
_, pkgs, _, err := db.Get(importPath)
if err != nil {
return err
}
c := make(chan error, 1)
go func() {
_, err := crawlDoc("rfrsh", importPath, nil, len(pkgs) > 0, time.Time{})
c <- err
}()
select {
case err = <-c:
case <-time.After(viper.GetDuration(ConfigGetTimeout)):
err = errUpdateTimeout
}
if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
setFlashMessages(resp, []flashMessage{{ID: "redir", Args: []string{importPath}}})
importPath = e.Redirect
err = nil
} else if err != nil {
setFlashMessages(resp, []flashMessage{{ID: "refresh", Args: []string{errorText(err)}}})
}
http.Redirect(resp, req, "/"+importPath, http.StatusFound)
return nil
}
func serveGoIndex(resp http.ResponseWriter, req *http.Request) error {
pkgs, err := db.GoIndex()
if err != nil {
return err
}
return executeTemplate(resp, "std.html", http.StatusOK, nil, map[string]interface{}{
"pkgs": pkgs,
})
}
func serveGoSubrepoIndex(resp http.ResponseWriter, req *http.Request) error {
pkgs, err := db.GoSubrepoIndex()
if err != nil {
return err
}
return executeTemplate(resp, "subrepo.html", http.StatusOK, nil, map[string]interface{}{
"pkgs": pkgs,
})
}
func runReindex(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintln(resp, "Reindexing...")
go reindex()
}
func runPurgeIndex(resp http.ResponseWriter, req *http.Request) {
fmt.Fprintln(resp, "Purging the search index...")
go purgeIndex()
}
type byPath struct {
pkgs []database.Package
rank []int
}
func (bp *byPath) Len() int { return len(bp.pkgs) }
func (bp *byPath) Less(i, j int) bool { return bp.pkgs[i].Path < bp.pkgs[j].Path }
func (bp *byPath) Swap(i, j int) {
bp.pkgs[i], bp.pkgs[j] = bp.pkgs[j], bp.pkgs[i]
bp.rank[i], bp.rank[j] = bp.rank[j], bp.rank[i]
}
type byRank struct {
pkgs []database.Package
rank []int
}
func (br *byRank) Len() int { return len(br.pkgs) }
func (br *byRank) Less(i, j int) bool { return br.rank[i] < br.rank[j] }
func (br *byRank) Swap(i, j int) {
br.pkgs[i], br.pkgs[j] = br.pkgs[j], br.pkgs[i]
br.rank[i], br.rank[j] = br.rank[j], br.rank[i]
}
func popular() ([]database.Package, error) {
const n = 25
pkgs, err := db.Popular(2 * n)
if err != nil {
return nil, err
}
rank := make([]int, len(pkgs))
for i := range pkgs {
rank[i] = i
}
sort.Sort(&byPath{pkgs, rank})
j := 0
prev := "."
for i, pkg := range pkgs {
if strings.HasPrefix(pkg.Path, prev) {
if rank[j-1] < rank[i] {
rank[j-1] = rank[i]
}
continue
}
prev = pkg.Path + "/"
pkgs[j] = pkg
rank[j] = rank[i]
j++
}
pkgs = pkgs[:j]
sort.Sort(&byRank{pkgs, rank})
if len(pkgs) > n {
pkgs = pkgs[:n]
}
sort.Sort(&byPath{pkgs, rank})
return pkgs, nil
}
func serveHome(resp http.ResponseWriter, req *http.Request) error {
if req.URL.Path != "/" {
return servePackage(resp, req)
}
q := strings.TrimSpace(req.Form.Get("q"))
if q == "" {
pkgs, err := popular()
if err != nil {
return err
}
return executeTemplate(resp, "home"+templateExt(req), http.StatusOK, nil,
map[string]interface{}{"Popular": pkgs})
}
if path, ok := isBrowseURL(q); ok {
q = path
}
if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
pdoc, pkgs, err := getDoc(q, queryRequest)
if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
http.Redirect(resp, req, "/"+e.Redirect, http.StatusFound)
return nil
}
if err == nil && (pdoc != nil || len(pkgs) > 0) {
http.Redirect(resp, req, "/"+q, http.StatusFound)
return nil
}
}
ctx := appengine.NewContext(req)
pkgs, err := database.Search(ctx, q)
if err != nil {
return err
}
if gceLogger != nil {
// Log up to top 10 packages we served upon a search.
logPkgs := pkgs
if len(pkgs) > 10 {
logPkgs = pkgs[:10]
}
gceLogger.LogEvent(resp, req, logPkgs)
}
return executeTemplate(resp, "results"+templateExt(req), http.StatusOK, nil,
map[string]interface{}{"q": q, "pkgs": pkgs})
}
func serveAbout(resp http.ResponseWriter, req *http.Request) error {
return executeTemplate(resp, "about.html", http.StatusOK, nil,
map[string]interface{}{"Host": req.Host})
}
func serveBot(resp http.ResponseWriter, req *http.Request) error {
return executeTemplate(resp, "bot.html", http.StatusOK, nil, nil)
}
func serveHealthCheck(resp http.ResponseWriter, req *http.Request) {
resp.Write([]byte("Health check: ok\n"))
}
func logError(req *http.Request, err error, rv interface{}) {
if err != nil {
var buf bytes.Buffer
fmt.Fprintf(&buf, "Error serving %s: %v\n", req.URL, err)
if rv != nil {
fmt.Fprintln(&buf, rv)
buf.Write(debug.Stack())
}
log.Print(buf.String())
}
}
func serveAPISearch(resp http.ResponseWriter, req *http.Request) error {
q := strings.TrimSpace(req.Form.Get("q"))
var pkgs []database.Package
if gosrc.IsValidRemotePath(q) || (strings.Contains(q, "/") && gosrc.IsGoRepoPath(q)) {
pdoc, _, err := getDoc(q, apiRequest)
if e, ok := err.(gosrc.NotFoundError); ok && e.Redirect != "" {
pdoc, _, err = getDoc(e.Redirect, robotRequest)
}
if err == nil && pdoc != nil {
pkgs = []database.Package{{Path: pdoc.ImportPath, Synopsis: pdoc.Synopsis}}
}
}
if pkgs == nil {
var err error
ctx := appengine.NewContext(req)
pkgs, err = database.Search(ctx, q)
if err != nil {
return err
}
}
var data = struct {
Results []database.Package `json:"results"`
}{
pkgs,
}
resp.Header().Set("Content-Type", jsonMIMEType)
return json.NewEncoder(resp).Encode(&data)
}
func serveAPIPackages(resp http.ResponseWriter, req *http.Request) error {
pkgs, err := db.AllPackages()
if err != nil {
return err
}
data := struct {
Results []database.Package `json:"results"`
}{
pkgs,
}
resp.Header().Set("Content-Type", jsonMIMEType)
return json.NewEncoder(resp).Encode(&data)
}
func serveAPIImporters(resp http.ResponseWriter, req *http.Request) error {
importPath := strings.TrimPrefix(req.URL.Path, "/importers/")
pkgs, err := db.Importers(importPath)
if err != nil {
return err
}
data := struct {
Results []database.Package `json:"results"`
}{
pkgs,
}
resp.Header().Set("Content-Type", jsonMIMEType)
return json.NewEncoder(resp).Encode(&data)
}
func serveAPIImports(resp http.ResponseWriter, req *http.Request) error {
importPath := strings.TrimPrefix(req.URL.Path, "/imports/")
pdoc, _, err := getDoc(importPath, robotRequest)
if err != nil {
return err
}
if pdoc == nil || pdoc.Name == "" {
return &httpError{status: http.StatusNotFound}
}
imports, err := db.Packages(pdoc.Imports)
if err != nil {
return err
}
testImports, err := db.Packages(pdoc.TestImports)
if err != nil {
return err
}
data := struct {
Imports []database.Package `json:"imports"`
TestImports []database.Package `json:"testImports"`
}{
imports,
testImports,
}
resp.Header().Set("Content-Type", jsonMIMEType)
return json.NewEncoder(resp).Encode(&data)
}
func serveAPIHome(resp http.ResponseWriter, req *http.Request) error {
return &httpError{status: http.StatusNotFound}
}
func runHandler(resp http.ResponseWriter, req *http.Request,
fn func(resp http.ResponseWriter, req *http.Request) error, errfn httputil.Error) {
defer func() {
if rv := recover(); rv != nil {
err := errors.New("handler panic")
logError(req, err, rv)
errfn(resp, req, http.StatusInternalServerError, err)
}
}()
// TODO(stephenmw): choose headers based on if we are on App Engine
if viper.GetBool(ConfigTrustProxyHeaders) {
// If running on GAE, use X-Appengine-User-Ip to identify real ip of requests.
if s := req.Header.Get("X-Appengine-User-Ip"); s != "" {
req.RemoteAddr = s
} else if s := req.Header.Get("X-Real-Ip"); s != "" {
req.RemoteAddr = s
}
}
req.Body = http.MaxBytesReader(resp, req.Body, 2048)
req.ParseForm()
var rb httputil.ResponseBuffer
err := fn(&rb, req)
if err == nil {
rb.WriteTo(resp)
} else if e, ok := err.(*httpError); ok {
if e.status >= 500 {
logError(req, err, nil)
}
errfn(resp, req, e.status, e.err)
} else if gosrc.IsNotFound(err) {
errfn(resp, req, http.StatusNotFound, nil)
} else {
logError(req, err, nil)
errfn(resp, req, http.StatusInternalServerError, err)
}
}
type handler func(resp http.ResponseWriter, req *http.Request) error
func (h handler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
runHandler(resp, req, h, handleError)
}
type apiHandler func(resp http.ResponseWriter, req *http.Request) error
func (h apiHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
runHandler(resp, req, h, handleAPIError)
}
func errorText(err error) string {
if err == errUpdateTimeout {
return "Timeout getting package files from the version control system."
}
if e, ok := err.(*gosrc.RemoteError); ok {
return "Error getting package files from " + e.Host + "."
}
return "Internal server error."
}
func handleError(resp http.ResponseWriter, req *http.Request, status int, err error) {
switch status {
case http.StatusNotFound:
executeTemplate(resp, "notfound"+templateExt(req), status, nil, map[string]interface{}{
"flashMessages": getFlashMessages(resp, req),
})
default:
resp.Header().Set("Content-Type", textMIMEType)
resp.WriteHeader(http.StatusInternalServerError)
io.WriteString(resp, errorText(err))
}
}
func handleAPIError(resp http.ResponseWriter, req *http.Request, status int, err error) {
var data struct {
Error struct {
Message string `json:"message"`
} `json:"error"`
}
data.Error.Message = http.StatusText(status)
resp.Header().Set("Content-Type", jsonMIMEType)
resp.WriteHeader(status)
json.NewEncoder(resp).Encode(&data)
}
type rootHandler []struct {
prefix string
h http.Handler
}
func (m rootHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
var h http.Handler
for _, ph := range m {
if strings.HasPrefix(req.Host, ph.prefix) {
h = ph.h
break
}
}
h.ServeHTTP(resp, req)
}
// otherDomainHandler redirects to another domain keeping the rest of the URL.
type otherDomainHandler struct {
scheme string
targetDomain string
}
func (h otherDomainHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
u := *req.URL
u.Scheme = h.scheme
u.Host = h.targetDomain
http.Redirect(w, req, u.String(), http.StatusFound)
}
func defaultBase(path string) string {
p, err := build.Default.Import(path, "", build.FindOnly)
if err != nil {
return "."
}
return p.Dir
}
var (
db *database.Database
httpClient *http.Client
statusImageHandlerPNG http.Handler
statusImageHandlerSVG http.Handler
gceLogger *GCELogger
)
func main() {
doc.SetDefaultGOOS(viper.GetString(ConfigDefaultGOOS))
httpClient = newHTTPClient()
var (
gceLogName string
projID string
)
// TODO(stephenmw): merge into viper config infrastructure.
if metadata.OnGCE() {
acct, err := metadata.ProjectAttributeValue("ga-account")
if err != nil {
log.Printf("querying metadata for ga-account: %v", err)
} else {
gaAccount = acct
}
// Get the log name on GCE and setup context for creating a GCE log client.
if name, err := metadata.ProjectAttributeValue("gce-log-name"); err != nil {
log.Printf("querying metadata for gce-log-name: %v", err)
} else {
gceLogName = name
if id, err := metadata.ProjectID(); err != nil {
log.Printf("querying metadata for project ID: %v", err)
} else {
projID = id
}
}
} else {
gaAccount = os.Getenv("GA_ACCOUNT")
}
if err := parseHTMLTemplates([][]string{
{"about.html", "common.html", "layout.html"},
{"bot.html", "common.html", "layout.html"},
{"cmd.html", "common.html", "layout.html"},
{"dir.html", "common.html", "layout.html"},
{"home.html", "common.html", "layout.html"},
{"importers.html", "common.html", "layout.html"},
{"importers_robot.html", "common.html", "layout.html"},
{"imports.html", "common.html", "layout.html"},
{"notfound.html", "common.html", "layout.html"},
{"pkg.html", "common.html", "layout.html"},
{"results.html", "common.html", "layout.html"},
{"tools.html", "common.html", "layout.html"},
{"std.html", "common.html", "layout.html"},
{"subrepo.html", "common.html", "layout.html"},
{"graph.html", "common.html"},
}); err != nil {
log.Fatal(err)
}
if err := parseTextTemplates([][]string{
{"cmd.txt", "common.txt"},
{"dir.txt", "common.txt"},
{"home.txt", "common.txt"},
{"notfound.txt", "common.txt"},
{"pkg.txt", "common.txt"},
{"results.txt", "common.txt"},
}); err != nil {
log.Fatal(err)
}
var err error
db, err = database.New()
if err != nil {
log.Fatalf("Error opening database: %v", err)
}
go runBackgroundTasks()
staticServer := httputil.StaticServer{
Dir: viper.GetString(ConfigAssetsDir),
MaxAge: time.Hour,
MIMETypes: map[string]string{
".css": "text/css; charset=utf-8",
".js": "text/javascript; charset=utf-8",
},
}
statusImageHandlerPNG = staticServer.FileHandler("status.png")
statusImageHandlerSVG = staticServer.FileHandler("status.svg")
apiMux := http.NewServeMux()
apiMux.Handle("/favicon.ico", staticServer.FileHandler("favicon.ico"))
apiMux.Handle("/google3d2f3cd4cc2bb44b.html", staticServer.FileHandler("google3d2f3cd4cc2bb44b.html"))
apiMux.Handle("/humans.txt", staticServer.FileHandler("humans.txt"))
apiMux.Handle("/robots.txt", staticServer.FileHandler("apiRobots.txt"))
apiMux.Handle("/search", apiHandler(serveAPISearch))
apiMux.Handle("/packages", apiHandler(serveAPIPackages))
apiMux.Handle("/importers/", apiHandler(serveAPIImporters))
apiMux.Handle("/imports/", apiHandler(serveAPIImports))
apiMux.Handle("/", apiHandler(serveAPIHome))
mux := http.NewServeMux()
mux.Handle("/-/site.js", staticServer.FilesHandler(
"third_party/jquery.timeago.js",
"site.js"))
mux.Handle("/-/site.css", staticServer.FilesHandler("site.css"))
mux.Handle("/-/bootstrap.min.css", staticServer.FilesHandler("bootstrap.min.css"))
mux.Handle("/-/bootstrap.min.js", staticServer.FilesHandler("bootstrap.min.js"))
mux.Handle("/-/jquery-2.0.3.min.js", staticServer.FilesHandler("jquery-2.0.3.min.js"))
if viper.GetBool(ConfigSidebar) {
mux.Handle("/-/sidebar.css", staticServer.FilesHandler("sidebar.css"))
}
mux.Handle("/-/", http.NotFoundHandler())
mux.Handle("/-/about", handler(serveAbout))
mux.Handle("/-/bot", handler(serveBot))
mux.Handle("/-/go", handler(serveGoIndex))
mux.Handle("/-/subrepo", handler(serveGoSubrepoIndex))
mux.Handle("/-/refresh", handler(serveRefresh))
mux.Handle("/-/admin/reindex", http.HandlerFunc(runReindex))
mux.Handle("/-/admin/purgeindex", http.HandlerFunc(runPurgeIndex))
mux.Handle("/about", http.RedirectHandler("/-/about", http.StatusMovedPermanently))
mux.Handle("/favicon.ico", staticServer.FileHandler("favicon.ico"))
mux.Handle("/google3d2f3cd4cc2bb44b.html", staticServer.FileHandler("google3d2f3cd4cc2bb44b.html"))
mux.Handle("/humans.txt", staticServer.FileHandler("humans.txt"))
mux.Handle("/robots.txt", staticServer.FileHandler("robots.txt"))
mux.Handle("/BingSiteAuth.xml", staticServer.FileHandler("BingSiteAuth.xml"))
mux.Handle("/C", http.RedirectHandler("http://golang.org/doc/articles/c_go_cgo.html", http.StatusMovedPermanently))
mux.Handle("/code.jquery.com/", http.NotFoundHandler())
mux.Handle("/_ah/health", http.HandlerFunc(serveHealthCheck))
mux.Handle("/_ah/", http.NotFoundHandler())
mux.Handle("/", handler(serveHome))
cacheBusters.Handler = mux
var root http.Handler = rootHandler{
{"api.", apiMux},
{"talks.godoc.org", otherDomainHandler{"https", "go-talks.appspot.com"}},
{"www.godoc.org", otherDomainHandler{"https", "godoc.org"}},
{"", mux},
}
if gceLogName != "" {
ctx := context.Background()
logc, err := logging.NewClient(ctx, projID)
if err != nil {
log.Fatalf("Failed to create cloud logging client: %v", err)
}
logger := logc.Logger(gceLogName)
if err := logc.Ping(ctx); err != nil {
log.Fatalf("Failed to ping Google Cloud Logging: %v", err)
}
gceLogger = newGCELogger(logger)
}
http.Handle("/", root)
appengine.Main()
}

33
vendor/github.com/golang/gddo/gddo-server/main_test.go generated vendored Normal file
View File

@@ -0,0 +1,33 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"net/http"
"testing"
)
var robotTests = []string{
"Mozilla/5.0 (compatible; TweetedTimes Bot/1.0; +http://tweetedtimes.com)",
"Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)",
"Mozilla/5.0 (compatible; MJ12bot/v1.4.3; http://www.majestic12.co.uk/bot.php?+)",
"Go 1.1 package http",
"Java/1.7.0_25 0.003 0.003",
"Python-urllib/2.6",
"Mozilla/5.0 (compatible; archive.org_bot +http://www.archive.org/details/archive.org_bot)",
"Mozilla/5.0 (compatible; Ezooms/1.0; ezooms.bot@gmail.com)",
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
}
func TestRobots(t *testing.T) {
for _, tt := range robotTests {
req := http.Request{Header: http.Header{"User-Agent": {tt}}}
if !isRobot(&req) {
t.Errorf("%s not a robot", tt)
}
}
}

91
vendor/github.com/golang/gddo/gddo-server/play.go generated vendored Normal file
View File

@@ -0,0 +1,91 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"fmt"
"io/ioutil"
"net/http"
"regexp"
"strings"
"github.com/golang/gddo/doc"
)
func findExamples(pdoc *doc.Package, export, method string) []*doc.Example {
if "package" == export {
return pdoc.Examples
}
for _, f := range pdoc.Funcs {
if f.Name == export {
return f.Examples
}
}
for _, t := range pdoc.Types {
for _, f := range t.Funcs {
if f.Name == export {
return f.Examples
}
}
if t.Name == export {
if method == "" {
return t.Examples
}
for _, m := range t.Methods {
if method == m.Name {
return m.Examples
}
}
return nil
}
}
return nil
}
func findExample(pdoc *doc.Package, export, method, name string) *doc.Example {
for _, e := range findExamples(pdoc, export, method) {
if name == e.Name {
return e
}
}
return nil
}
var exampleIDPat = regexp.MustCompile(`([^-]+)(?:-([^-]*)(?:-(.*))?)?`)
func playURL(pdoc *doc.Package, id, countryHeader string) (string, error) {
if m := exampleIDPat.FindStringSubmatch(id); m != nil {
if e := findExample(pdoc, m[1], m[2], m[3]); e != nil && e.Play != "" {
req, err := http.NewRequest("POST", "https://play.golang.org/share", strings.NewReader(e.Play))
if err != nil {
return "", err
}
req.Header.Set("Content-Type", "text/plain")
if countryHeader != "" {
// Forward the App Engine country header.
req.Header.Set("X-AppEngine-Country", countryHeader)
}
resp, err := httpClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
p, err := ioutil.ReadAll(resp.Body)
if err != nil {
return "", err
}
if resp.StatusCode > 399 {
return "", &httpError{
status: resp.StatusCode,
err: fmt.Errorf("Error from play.golang.org: %s", p),
}
}
return fmt.Sprintf("http://play.golang.org/p/%s", p), nil
}
}
return "", &httpError{status: http.StatusNotFound}
}

575
vendor/github.com/golang/gddo/gddo-server/template.go generated vendored Normal file
View File

@@ -0,0 +1,575 @@
// Copyright 2013 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"bytes"
"encoding/base64"
"errors"
"fmt"
godoc "go/doc"
htemp "html/template"
"io"
"net/http"
"net/url"
"path"
"path/filepath"
"reflect"
"regexp"
"sort"
"strings"
ttemp "text/template"
"time"
"github.com/spf13/viper"
"github.com/golang/gddo/doc"
"github.com/golang/gddo/gosrc"
"github.com/golang/gddo/httputil"
)
var cacheBusters httputil.CacheBusters
type flashMessage struct {
ID string
Args []string
}
// getFlashMessages retrieves flash messages from the request and clears the flash cookie if needed.
func getFlashMessages(resp http.ResponseWriter, req *http.Request) []flashMessage {
c, err := req.Cookie("flash")
if err == http.ErrNoCookie {
return nil
}
http.SetCookie(resp, &http.Cookie{Name: "flash", Path: "/", MaxAge: -1, Expires: time.Now().Add(-100 * 24 * time.Hour)})
if err != nil {
return nil
}
p, err := base64.URLEncoding.DecodeString(c.Value)
if err != nil {
return nil
}
var messages []flashMessage
for _, s := range strings.Split(string(p), "\000") {
idArgs := strings.Split(s, "\001")
messages = append(messages, flashMessage{ID: idArgs[0], Args: idArgs[1:]})
}
return messages
}
// setFlashMessages sets a cookie with the given flash messages.
func setFlashMessages(resp http.ResponseWriter, messages []flashMessage) {
var buf []byte
for i, message := range messages {
if i > 0 {
buf = append(buf, '\000')
}
buf = append(buf, message.ID...)
for _, arg := range message.Args {
buf = append(buf, '\001')
buf = append(buf, arg...)
}
}
value := base64.URLEncoding.EncodeToString(buf)
http.SetCookie(resp, &http.Cookie{Name: "flash", Value: value, Path: "/"})
}
type tdoc struct {
*doc.Package
allExamples []*texample
}
type texample struct {
ID string
Label string
Example *doc.Example
Play bool
obj interface{}
}
func newTDoc(pdoc *doc.Package) *tdoc {
return &tdoc{Package: pdoc}
}
func (pdoc *tdoc) SourceLink(pos doc.Pos, text string, textOnlyOK bool) htemp.HTML {
if pos.Line == 0 || pdoc.LineFmt == "" || pdoc.Files[pos.File].URL == "" {
if textOnlyOK {
return htemp.HTML(htemp.HTMLEscapeString(text))
}
return ""
}
return htemp.HTML(fmt.Sprintf(`<a title="View Source" href="%s">%s</a>`,
htemp.HTMLEscapeString(fmt.Sprintf(pdoc.LineFmt, pdoc.Files[pos.File].URL, pos.Line)),
htemp.HTMLEscapeString(text)))
}
// UsesLink generates a link to uses of a symbol definition.
// title is used as the tooltip. defParts are parts of the symbol definition name.
func (pdoc *tdoc) UsesLink(title string, defParts ...string) htemp.HTML {
if viper.GetString(ConfigSourcegraphURL) == "" {
return ""
}
var def string
switch len(defParts) {
case 1:
// Funcs and types have one def part.
def = defParts[0]
case 3:
// Methods have three def parts, the original receiver name, actual receiver name and method name.
orig, recv, methodName := defParts[0], defParts[1], defParts[2]
if orig == "" {
// TODO: Remove this fallback after 2016-08-05. It's only needed temporarily to backfill data.
// Actual receiver is not needed, it's only used because original receiver value
// was recently added to gddo/doc package and will be blank until next package rebuild.
//
// Use actual receiver as fallback.
orig = recv
}
// Trim "*" from "*T" if it's a pointer receiver method.
typeName := strings.TrimPrefix(orig, "*")
def = typeName + "/" + methodName
default:
panic(fmt.Errorf("%v defParts, want 1 or 3", len(defParts)))
}
q := url.Values{
"repo": {pdoc.ProjectRoot},
"pkg": {pdoc.ImportPath},
"def": {def},
}
u := viper.GetString(ConfigSourcegraphURL) + "/-/godoc/refs?" + q.Encode()
return htemp.HTML(fmt.Sprintf(`<a class="uses" title="%s" href="%s">Uses</a>`, htemp.HTMLEscapeString(title), htemp.HTMLEscapeString(u)))
}
func (pdoc *tdoc) PageName() string {
if pdoc.Name != "" && !pdoc.IsCmd {
return pdoc.Name
}
_, name := path.Split(pdoc.ImportPath)
return name
}
func (pdoc *tdoc) addExamples(obj interface{}, export, method string, examples []*doc.Example) {
label := export
id := export
if method != "" {
label += "." + method
id += "-" + method
}
for _, e := range examples {
te := &texample{
Label: label,
ID: id,
Example: e,
obj: obj,
// Only show play links for packages within the standard library.
Play: e.Play != "" && gosrc.IsGoRepoPath(pdoc.ImportPath),
}
if e.Name != "" {
te.Label += " (" + e.Name + ")"
if method == "" {
te.ID += "-"
}
te.ID += "-" + e.Name
}
pdoc.allExamples = append(pdoc.allExamples, te)
}
}
type byExampleID []*texample
func (e byExampleID) Len() int { return len(e) }
func (e byExampleID) Less(i, j int) bool { return e[i].ID < e[j].ID }
func (e byExampleID) Swap(i, j int) { e[i], e[j] = e[j], e[i] }
func (pdoc *tdoc) AllExamples() []*texample {
if pdoc.allExamples != nil {
return pdoc.allExamples
}
pdoc.allExamples = make([]*texample, 0)
pdoc.addExamples(pdoc, "package", "", pdoc.Examples)
for _, f := range pdoc.Funcs {
pdoc.addExamples(f, f.Name, "", f.Examples)
}
for _, t := range pdoc.Types {
pdoc.addExamples(t, t.Name, "", t.Examples)
for _, f := range t.Funcs {
pdoc.addExamples(f, f.Name, "", f.Examples)
}
for _, m := range t.Methods {
if len(m.Examples) > 0 {
pdoc.addExamples(m, t.Name, m.Name, m.Examples)
}
}
}
sort.Sort(byExampleID(pdoc.allExamples))
return pdoc.allExamples
}
func (pdoc *tdoc) ObjExamples(obj interface{}) []*texample {
var examples []*texample
for _, e := range pdoc.allExamples {
if e.obj == obj {
examples = append(examples, e)
}
}
return examples
}
func (pdoc *tdoc) Breadcrumbs(templateName string) htemp.HTML {
if !strings.HasPrefix(pdoc.ImportPath, pdoc.ProjectRoot) {
return ""
}
var buf bytes.Buffer
i := 0
j := len(pdoc.ProjectRoot)
if j == 0 {
j = strings.IndexRune(pdoc.ImportPath, '/')
if j < 0 {
j = len(pdoc.ImportPath)
}
}
for {
if i != 0 {
buf.WriteString(`<span class="text-muted">/</span>`)
}
link := j < len(pdoc.ImportPath) ||
(templateName != "dir.html" && templateName != "cmd.html" && templateName != "pkg.html")
if link {
buf.WriteString(`<a href="`)
buf.WriteString(formatPathFrag(pdoc.ImportPath[:j], ""))
buf.WriteString(`">`)
} else {
buf.WriteString(`<span class="text-muted">`)
}
buf.WriteString(htemp.HTMLEscapeString(pdoc.ImportPath[i:j]))
if link {
buf.WriteString("</a>")
} else {
buf.WriteString("</span>")
}
i = j + 1
if i >= len(pdoc.ImportPath) {
break
}
j = strings.IndexRune(pdoc.ImportPath[i:], '/')
if j < 0 {
j = len(pdoc.ImportPath)
} else {
j += i
}
}
return htemp.HTML(buf.String())
}
func (pdoc *tdoc) StatusDescription() htemp.HTML {
desc := ""
switch pdoc.Package.Status {
case gosrc.DeadEndFork:
desc = "This is a dead-end fork (no commits since the fork)."
case gosrc.QuickFork:
desc = "This is a quick bug-fix fork (has fewer than three commits, and only during the week it was created)."
case gosrc.Inactive:
desc = "This is an inactive package (no imports and no commits in at least two years)."
}
return htemp.HTML(desc)
}
func formatPathFrag(path, fragment string) string {
if len(path) > 0 && path[0] != '/' {
path = "/" + path
}
u := url.URL{Path: path, Fragment: fragment}
return u.String()
}
func hostFn(urlStr string) string {
u, err := url.Parse(urlStr)
if err != nil {
return ""
}
return u.Host
}
func mapFn(kvs ...interface{}) (map[string]interface{}, error) {
if len(kvs)%2 != 0 {
return nil, errors.New("map requires even number of arguments")
}
m := make(map[string]interface{})
for i := 0; i < len(kvs); i += 2 {
s, ok := kvs[i].(string)
if !ok {
return nil, errors.New("even args to map must be strings")
}
m[s] = kvs[i+1]
}
return m, nil
}
// relativePathFn formats an import path as HTML.
func relativePathFn(path string, parentPath interface{}) string {
if p, ok := parentPath.(string); ok && p != "" && strings.HasPrefix(path, p) {
path = path[len(p)+1:]
}
return path
}
// importPathFn formats an import with zero width space characters to allow for breaks.
func importPathFn(path string) htemp.HTML {
path = htemp.HTMLEscapeString(path)
if len(path) > 45 {
// Allow long import paths to break following "/"
path = strings.Replace(path, "/", "/&#8203;", -1)
}
return htemp.HTML(path)
}
var (
h3Pat = regexp.MustCompile(`<h3 id="([^"]+)">([^<]+)</h3>`)
rfcPat = regexp.MustCompile(`RFC\s+(\d{3,4})(,?\s+[Ss]ection\s+(\d+(\.\d+)*))?`)
packagePat = regexp.MustCompile(`\s+package\s+([-a-z0-9]\S+)`)
)
func replaceAll(src []byte, re *regexp.Regexp, replace func(out, src []byte, m []int) []byte) []byte {
var out []byte
for len(src) > 0 {
m := re.FindSubmatchIndex(src)
if m == nil {
break
}
out = append(out, src[:m[0]]...)
out = replace(out, src, m)
src = src[m[1]:]
}
if out == nil {
return src
}
return append(out, src...)
}
// commentFn formats a source code comment as HTML.
func commentFn(v string) htemp.HTML {
var buf bytes.Buffer
godoc.ToHTML(&buf, v, nil)
p := buf.Bytes()
p = replaceAll(p, h3Pat, func(out, src []byte, m []int) []byte {
out = append(out, `<h4 id="`...)
out = append(out, src[m[2]:m[3]]...)
out = append(out, `">`...)
out = append(out, src[m[4]:m[5]]...)
out = append(out, ` <a class="permalink" href="#`...)
out = append(out, src[m[2]:m[3]]...)
out = append(out, `">&para</a></h4>`...)
return out
})
p = replaceAll(p, rfcPat, func(out, src []byte, m []int) []byte {
out = append(out, `<a href="http://tools.ietf.org/html/rfc`...)
out = append(out, src[m[2]:m[3]]...)
// If available, add section fragment
if m[4] != -1 {
out = append(out, `#section-`...)
out = append(out, src[m[6]:m[7]]...)
}
out = append(out, `">`...)
out = append(out, src[m[0]:m[1]]...)
out = append(out, `</a>`...)
return out
})
p = replaceAll(p, packagePat, func(out, src []byte, m []int) []byte {
path := bytes.TrimRight(src[m[2]:m[3]], ".!?:")
if !gosrc.IsValidPath(string(path)) {
return append(out, src[m[0]:m[1]]...)
}
out = append(out, src[m[0]:m[2]]...)
out = append(out, `<a href="/`...)
out = append(out, path...)
out = append(out, `">`...)
out = append(out, path...)
out = append(out, `</a>`...)
out = append(out, src[m[2]+len(path):m[1]]...)
return out
})
return htemp.HTML(p)
}
// commentTextFn formats a source code comment as text.
func commentTextFn(v string) string {
const indent = " "
var buf bytes.Buffer
godoc.ToText(&buf, v, indent, "\t", 80-2*len(indent))
p := buf.Bytes()
return string(p)
}
var period = []byte{'.'}
func codeFn(c doc.Code, typ *doc.Type) htemp.HTML {
var buf bytes.Buffer
last := 0
src := []byte(c.Text)
buf.WriteString("<pre>")
for _, a := range c.Annotations {
htemp.HTMLEscape(&buf, src[last:a.Pos])
switch a.Kind {
case doc.PackageLinkAnnotation:
buf.WriteString(`<a href="`)
buf.WriteString(formatPathFrag(c.Paths[a.PathIndex], ""))
buf.WriteString(`">`)
htemp.HTMLEscape(&buf, src[a.Pos:a.End])
buf.WriteString(`</a>`)
case doc.LinkAnnotation, doc.BuiltinAnnotation:
var p string
if a.Kind == doc.BuiltinAnnotation {
p = "builtin"
} else if a.PathIndex >= 0 {
p = c.Paths[a.PathIndex]
}
n := src[a.Pos:a.End]
n = n[bytes.LastIndex(n, period)+1:]
buf.WriteString(`<a href="`)
buf.WriteString(formatPathFrag(p, string(n)))
buf.WriteString(`">`)
htemp.HTMLEscape(&buf, src[a.Pos:a.End])
buf.WriteString(`</a>`)
case doc.CommentAnnotation:
buf.WriteString(`<span class="com">`)
htemp.HTMLEscape(&buf, src[a.Pos:a.End])
buf.WriteString(`</span>`)
case doc.AnchorAnnotation:
buf.WriteString(`<span id="`)
if typ != nil {
htemp.HTMLEscape(&buf, []byte(typ.Name))
buf.WriteByte('.')
}
htemp.HTMLEscape(&buf, src[a.Pos:a.End])
buf.WriteString(`">`)
htemp.HTMLEscape(&buf, src[a.Pos:a.End])
buf.WriteString(`</span>`)
default:
htemp.HTMLEscape(&buf, src[a.Pos:a.End])
}
last = int(a.End)
}
htemp.HTMLEscape(&buf, src[last:])
buf.WriteString("</pre>")
return htemp.HTML(buf.String())
}
var isInterfacePat = regexp.MustCompile(`^type [^ ]+ interface`)
func isInterfaceFn(t *doc.Type) bool {
return isInterfacePat.MatchString(t.Decl.Text)
}
var gaAccount string
func gaAccountFn() string {
return gaAccount
}
func noteTitleFn(s string) string {
return strings.Title(strings.ToLower(s))
}
func htmlCommentFn(s string) htemp.HTML {
return htemp.HTML("<!-- " + s + " -->")
}
var mimeTypes = map[string]string{
".html": htmlMIMEType,
".txt": textMIMEType,
}
func executeTemplate(resp http.ResponseWriter, name string, status int, header http.Header, data interface{}) error {
for k, v := range header {
resp.Header()[k] = v
}
mimeType, ok := mimeTypes[path.Ext(name)]
if !ok {
mimeType = textMIMEType
}
resp.Header().Set("Content-Type", mimeType)
t := templates[name]
if t == nil {
return fmt.Errorf("template %s not found", name)
}
resp.WriteHeader(status)
if status == http.StatusNotModified {
return nil
}
return t.Execute(resp, data)
}
var templates = map[string]interface {
Execute(io.Writer, interface{}) error
}{}
func joinTemplateDir(base string, files []string) []string {
result := make([]string, len(files))
for i := range files {
result[i] = filepath.Join(base, "templates", files[i])
}
return result
}
func parseHTMLTemplates(sets [][]string) error {
for _, set := range sets {
templateName := set[0]
t := htemp.New("")
t.Funcs(htemp.FuncMap{
"code": codeFn,
"comment": commentFn,
"equal": reflect.DeepEqual,
"gaAccount": gaAccountFn,
"host": hostFn,
"htmlComment": htmlCommentFn,
"importPath": importPathFn,
"isInterface": isInterfaceFn,
"isValidImportPath": gosrc.IsValidPath,
"map": mapFn,
"noteTitle": noteTitleFn,
"relativePath": relativePathFn,
"sidebarEnabled": func() bool { return viper.GetBool(ConfigSidebar) },
"staticPath": func(p string) string { return cacheBusters.AppendQueryParam(p, "v") },
"templateName": func() string { return templateName },
})
if _, err := t.ParseFiles(joinTemplateDir(viper.GetString(ConfigAssetsDir), set)...); err != nil {
return err
}
t = t.Lookup("ROOT")
if t == nil {
return fmt.Errorf("ROOT template not found in %v", set)
}
templates[set[0]] = t
}
return nil
}
func parseTextTemplates(sets [][]string) error {
for _, set := range sets {
t := ttemp.New("")
t.Funcs(ttemp.FuncMap{
"comment": commentTextFn,
})
if _, err := t.ParseFiles(joinTemplateDir(viper.GetString(ConfigAssetsDir), set)...); err != nil {
return err
}
t = t.Lookup("ROOT")
if t == nil {
return fmt.Errorf("ROOT template not found in %v", set)
}
templates[set[0]] = t
}
return nil
}

View File

@@ -0,0 +1,33 @@
// Copyright 2014 The Go Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd.
package main
import (
"net/http"
"net/http/httptest"
"reflect"
"strings"
"testing"
)
func TestFlashMessages(t *testing.T) {
resp := httptest.NewRecorder()
expectedMessages := []flashMessage{
{ID: "a", Args: []string{"one"}},
{ID: "b", Args: []string{"two", "three"}},
{ID: "c", Args: []string{}},
}
setFlashMessages(resp, expectedMessages)
req := &http.Request{Header: http.Header{"Cookie": {strings.Split(resp.Header().Get("Set-Cookie"), ";")[0]}}}
actualMessages := getFlashMessages(resp, req)
if !reflect.DeepEqual(actualMessages, expectedMessages) {
t.Errorf("got messages %+v, want %+v", actualMessages, expectedMessages)
}
}