Major rewrite
* use dep for vendoring * lets encrypt * moved web to transfer.sh-web repo * single command install * added first tests
10
vendor/github.com/golang/gddo/gddo-server/app.yaml
generated
vendored
Normal 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
|
4
vendor/github.com/golang/gddo/gddo-server/assets/BingSiteAuth.xml
generated
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0"?>
|
||||
<users>
|
||||
<user>6F3E495D5591D0B1308072CA245E8849</user>
|
||||
</users>
|
2
vendor/github.com/golang/gddo/gddo-server/assets/apiRobots.txt
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
User-agent: *
|
||||
Disallow: *
|
BIN
vendor/github.com/golang/gddo/gddo-server/assets/apple-touch-icon-120x120.png
generated
vendored
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
vendor/github.com/golang/gddo/gddo-server/assets/apple-touch-icon-152x152.png
generated
vendored
Normal file
After Width: | Height: | Size: 6.3 KiB |
BIN
vendor/github.com/golang/gddo/gddo-server/assets/apple-touch-icon-76x76.png
generated
vendored
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
vendor/github.com/golang/gddo/gddo-server/assets/apple-touch-icon.png
generated
vendored
Normal file
After Width: | Height: | Size: 6.3 KiB |
5
vendor/github.com/golang/gddo/gddo-server/assets/bootstrap.min.css
generated
vendored
Normal file
7
vendor/github.com/golang/gddo/gddo-server/assets/bootstrap.min.js
generated
vendored
Normal file
BIN
vendor/github.com/golang/gddo/gddo-server/assets/favicon.ico
generated
vendored
Normal file
After Width: | Height: | Size: 22 KiB |
1
vendor/github.com/golang/gddo/gddo-server/assets/google3d2f3cd4cc2bb44b.html
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
google-site-verification: google3d2f3cd4cc2bb44b.html
|
0
vendor/github.com/golang/gddo/gddo-server/assets/humans.txt
generated
vendored
Normal file
6
vendor/github.com/golang/gddo/gddo-server/assets/jquery-2.0.3.min.js
generated
vendored
Normal file
8
vendor/github.com/golang/gddo/gddo-server/assets/robots.txt
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
User-agent: *
|
||||
Disallow: /*?imports
|
||||
Disallow: /*?importers
|
||||
Disallow: /*?import-graph*
|
||||
Disallow: /*?gosrc*
|
||||
Disallow: /*?file*
|
||||
Disallow: /*?play*
|
||||
Disallow: /*?tools
|
82
vendor/github.com/golang/gddo/gddo-server/assets/sidebar.css
generated
vendored
Normal 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;
|
||||
}
|
||||
}
|
139
vendor/github.com/golang/gddo/gddo-server/assets/site.css
generated
vendored
Normal 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;
|
||||
}
|
231
vendor/github.com/golang/gddo/gddo-server/assets/site.js
generated
vendored
Normal 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
|
||||
});
|
||||
});
|
BIN
vendor/github.com/golang/gddo/gddo-server/assets/status.png
generated
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
1
vendor/github.com/golang/gddo/gddo-server/assets/status.svg
generated
vendored
Normal 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 |
72
vendor/github.com/golang/gddo/gddo-server/assets/templates/about.html
generated
vendored
Normal 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}}
|
6
vendor/github.com/golang/gddo/gddo-server/assets/templates/bot.html
generated
vendored
Normal 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}}
|
9
vendor/github.com/golang/gddo/gddo-server/assets/templates/cmd.html
generated
vendored
Normal 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}}
|
5
vendor/github.com/golang/gddo/gddo-server/assets/templates/cmd.txt
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
{{define "ROOT"}}{{with .pdoc}}
|
||||
COMMAND DOCUMENTATION
|
||||
|
||||
{{.Doc|comment}}
|
||||
{{template "Subdirs" $}}{{end}}{{end}}
|
126
vendor/github.com/golang/gddo/gddo-server/assets/templates/common.html
generated
vendored
Normal 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">¶</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">¶</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">¶</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}}
|
||||
|
3
vendor/github.com/golang/gddo/gddo-server/assets/templates/common.txt
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{{define "Subdirs"}}{{with $.pkgs}}SUBDIRECTORIES
|
||||
{{range .}}
|
||||
{{.Path}}{{end}}{{end}}{{end}}
|
10
vendor/github.com/golang/gddo/gddo-server/assets/templates/dir.html
generated
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
{{define "Head"}}
|
||||
{{template "PkgCmdHeader" $}}
|
||||
<meta name="robots" content="NOINDEX">
|
||||
{{end}}
|
||||
|
||||
{{define "Body"}}
|
||||
{{template "ProjectNav" $}}
|
||||
{{template "PkgCmdFooter" $}}
|
||||
|
||||
{{end}}
|
1
vendor/github.com/golang/gddo/gddo-server/assets/templates/dir.txt
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{{define "ROOT"}}{{with .pdoc}}{{template "Subdirs" $}}{{end}}{{end}}
|
23
vendor/github.com/golang/gddo/gddo-server/assets/templates/graph.html
generated
vendored
Normal 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}}
|
37
vendor/github.com/golang/gddo/gddo-server/assets/templates/home.html
generated
vendored
Normal 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&q=language%3Ago&s=stars&type=Repositories">Most stars</a>,
|
||||
<a href="https://github.com/search?o=desc&q=language%3Ago&s=forks&type=Repositories">most forks</a>,
|
||||
<a href="https://github.com/search?o=desc&q=language%3Ago&s=updated&type=Repositories">recently updated</a> on GitHub
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{end}}
|
2
vendor/github.com/golang/gddo/gddo-server/assets/templates/home.txt
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
{{define "ROOT"}}
|
||||
{{end}}
|
7
vendor/github.com/golang/gddo/gddo-server/assets/templates/importers.html
generated
vendored
Normal 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}}
|
10
vendor/github.com/golang/gddo/gddo-server/assets/templates/importers_robot.html
generated
vendored
Normal 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}}
|
7
vendor/github.com/golang/gddo/gddo-server/assets/templates/imports.html
generated
vendored
Normal 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}}
|
73
vendor/github.com/golang/gddo/gddo-server/assets/templates/layout.html
generated
vendored
Normal 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">×</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}}
|
||||
|
10
vendor/github.com/golang/gddo/gddo-server/assets/templates/notfound.html
generated
vendored
Normal 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}}
|
2
vendor/github.com/golang/gddo/gddo-server/assets/templates/notfound.txt
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
{{define "ROOT"}}NOT FOUND
|
||||
{{end}}
|
9
vendor/github.com/golang/gddo/gddo-server/assets/templates/opensearch.xml
generated
vendored
Normal 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}}
|
183
vendor/github.com/golang/gddo/gddo-server/assets/templates/pkg.html
generated
vendored
Normal 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">¶</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">¶</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">¶</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">¶</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">¶</a></h3>
|
||||
{{end}}{{end}}
|
||||
{{range .Funcs}}
|
||||
<h3 id="{{.Name}}" data-kind="f">func {{$.pdoc.SourceLink .Pos .Name true}} <a class="permalink" href="#{{.Name}}">¶</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">¶</a></h3>
|
||||
{{end}}{{end}}
|
||||
|
||||
{{range $t := .Types}}
|
||||
<h3 id="{{.Name}}" data-kind="t">type {{$.pdoc.SourceLink .Pos .Name true}} <a class="permalink" href="#{{.Name}}">¶</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}}">¶</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}}">¶</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> </span>{{end}}
|
||||
{{code .Example.Code nil}}
|
||||
{{with .Example.Output}}<p>Output:<pre>{{.}}</pre>{{end}}
|
||||
</div></div>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
{{end}}
|
||||
{{end}}
|
38
vendor/github.com/golang/gddo/gddo-server/assets/templates/pkg.txt
generated
vendored
Normal 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}}
|
14
vendor/github.com/golang/gddo/gddo-server/assets/templates/results.html
generated
vendored
Normal 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}}
|
2
vendor/github.com/golang/gddo/gddo-server/assets/templates/results.txt
generated
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
{{define "ROOT"}}{{range .pkgs}}{{.Path}} {{.Synopsis}}
|
||||
{{end}}{{end}}
|
8
vendor/github.com/golang/gddo/gddo-server/assets/templates/std.html
generated
vendored
Normal 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}}
|
||||
|
23
vendor/github.com/golang/gddo/gddo-server/assets/templates/subrepo.html
generated
vendored
Normal 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}}
|
36
vendor/github.com/golang/gddo/gddo-server/assets/templates/tools.html
generated
vendored
Normal 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="[]({{.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>
|
||||
{{end}}
|
184
vendor/github.com/golang/gddo/gddo-server/assets/third_party/jquery.timeago.js
generated
vendored
Normal 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
@@ -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
@@ -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
|
||||
}
|
40
vendor/github.com/golang/gddo/gddo-server/browse_test.go
generated
vendored
Normal 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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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, "/", "/​", -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, `">¶</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
|
||||
}
|
33
vendor/github.com/golang/gddo/gddo-server/template_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|