Initial pass at S3 version

This commit is contained in:
Jay McCarthy 2013-10-07 14:10:32 -06:00
parent 0371ade45e
commit 4cb98ac8df
8 changed files with 298 additions and 72 deletions

View File

@ -1 +1,5 @@
/root
/static/pkgs
/static/pkg
/static/pkgs-all
*json

View File

@ -0,0 +1,31 @@
#lang racket/base
(require racket/file
racket/runtime-path
pkg/util
racket/string
web-server/http/id-cookie)
(define-runtime-path src ".")
(define-runtime-path root "root")
(make-directory* root)
(define secret-key
(make-secret-salt/file
(build-path root "secret.key")))
(define users-path (build-path root "users"))
(make-directory* users-path)
(define users.new-path (build-path root "users.new"))
(make-directory* users.new-path)
(github-client_id (file->string (build-path root "client_id")))
(github-client_secret (file->string (build-path root "client_secret")))
(define pkgs-path (build-path root "pkgs"))
(make-directory* pkgs-path)
(define static-path (build-path src "static"))
(define (author->list as)
(string-split as))
(provide (all-defined-out))

View File

@ -2,6 +2,7 @@
(module+ test
(require rackunit))
(require web-server/http
"common.rkt"
web-server/servlet-env
racket/file
xml
@ -38,24 +39,6 @@
(define (salty str)
(sha1 (open-input-string str)))
(define-runtime-path src ".")
(define-runtime-path root "root")
(make-directory* root)
(define secret-key
(make-secret-salt/file
(build-path root "secret.key")))
(define users-path (build-path root "users"))
(make-directory* users-path)
(define users.new-path (build-path root "users.new"))
(make-directory* users.new-path)
(github-client_id (file->string (build-path root "client_id")))
(github-client_secret (file->string (build-path root "client_secret")))
(define pkgs-path (build-path root "pkgs"))
(make-directory* pkgs-path)
(define id-cookie-name "pnrid")
;; XXX Add a caching system
@ -159,9 +142,6 @@
new-v]))
#f)))
(define (author->list as)
(string-split as))
(define (page/rss req)
(define ps
(sort (map package-info (package-list))
@ -1132,7 +1112,7 @@
#:ssl-cert (build-path root "server-cert.pem")
#:ssl-key (build-path root "private-key.pem")
#:extra-files-paths
(list (build-path src "static"))
(list static-path)
#:servlet-regexp #rx""
#:port port))

View File

@ -0,0 +1,75 @@
#lang racket/base
(require web-server/http
racket/file
racket/match
json
net/url
racket/list
racket/path
racket/promise
meta/pkg-index/basic/main)
(define convert-to-json
(match-lambda
[(? list? l)
(map convert-to-json l)]
[(? string? s)
s]
[(? hash? ht)
(for/hash ([(k v) (in-hash ht)])
(values (convert-to-json-key k)
(convert-to-json v)))]
[(? number? n)
n]
[#f
#f]
[(? symbol? s)
(symbol->string s)]
[(? keyword? s)
(hasheq 'kw (keyword->string s))]
[x
(error 'convert-to-json "~e" x)]))
(define convert-to-json-key
(match-lambda
[(? string? s)
(string->symbol s)]
[(? symbol? s)
s]
[x
(error 'convert-to-json-key "~e" x)]))
(module+ main
(require "common.rkt")
(define pkg-list
(map path->string (directory-list pkgs-path)))
(define dispatch
(pkg-index/basic
(λ () pkg-list)
(λ (pkg-name)
(define ht (file->value (build-path pkgs-path pkg-name)))
(hash-set* ht
'name pkg-name
'tags (hash-ref ht 'tags empty)
'authors (author->list (hash-ref ht 'author ""))))))
(define (url->request u)
(make-request #"GET" (string->url u) empty
(delay empty) #f "1.2.3.4" 80 "4.3.2.1"))
(define (cache url file)
(define p (build-path static-path file))
(make-directory* (path-only p))
(with-output-to-file p
#:exists 'replace
(λ () ((response-output (dispatch (url->request url))) (current-output-port))))
(with-output-to-file (path-add-suffix p #".json")
#:exists 'replace
(λ () (write-json (convert-to-json (file->value p)))))
(void))
(cache "/pkgs" "pkgs")
(cache "/pkgs-all" "pkgs-all")
(for ([p (in-list pkg-list)])
(cache (format "/pkg/~a" p) (format "pkg/~a" p))))

View File

@ -0,0 +1,39 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN" "http://www.w3.org/TR/REC-html40/loose.dtd">
<html>
<head>
<title>Packages &gt; Search &gt; !main-distribution &gt; !main-tests</title>
<script src="/sorttable.js"></script>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<script src="/index.js"></script>
<link rel="stylesheet" type="text/css" href="/style.css">
</head>
<body>
<div class="breadcrumb">
<span><a href="/">Packages</a></span> &gt;
<span>Search</span>
<span id="logout">jay.mccarthy@gmail.com | <a href="/curate">curate</a> | <a href="/rss">rss</a> | <a href="/account/login">re-login</a> | <a href="/account/logout">logout</a></span>
</div>
<div id="menu"><form action="/query/search/!main-distribution/!main-tests">
<span class="menu_option"><input name="input_0" type="text"><input type="submit" value="Search"></span><span class="menu_option"><a href="/manage">Manage
Your Packages</a></span>
</form></div>
<p id="search_menu"></p>
<table class="packages sortable">
<thead><tr>
<th>Package</th>
<th>Authors</th>
<th>Description</th>
<th>Tags</th>
</tr></thead>
<tbody id="packages_table">
</tbody>
<tfoot></tfoot>
</table>
<div id="footer">
Powered by <a href="http://racket-lang.org">Racket</a> and a
mess of ugly JS. Written
by <a href="http://faculty.cs.byu.edu/~jay">Jay McCarthy</a>.
</div>
</body>
</html>

View File

@ -0,0 +1,116 @@
// xxx curate
// xxx rss
// xxx re-login
// xxx logout
// xxx what user am i
// xxx manage packages
$( document ).ready(function() {
var search_terms = [ "!main-tests", "!main-distribution" ];
function addfilterlink ( text, term ) {
return [$('<a>', { text: text,
href: "javascript:void(0)",
click: function () {
search_terms.push(term);
evaluate_search();
} } ),
" "
];
};
function removefilterlink ( text, term ) {
return [$('<a>', { text: text,
href: "javascript:void(0)",
click: function () {
search_terms = $.grep( search_terms, function (v) { return v != term } );
evaluate_search();
} } ),
" "
];
};
$.getJSON( "/pkgs-all.json", function( resp ) {
var names = [];
$.each(resp, function(key, value) { names.push(key) });
var snames = names.sort(function(a,b) {
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
})
var now = new Date().getTime() / 1000;
$.each( snames,
function (name_i) {
var name = snames[name_i];
var value = resp[name];
$('<tr>',
{ class: ((now - (60*60*24*2)) < value['last-updated'] ? "recent" : "old") }).
data( "obj", value).append(
$('<td>').html( $('<a>', { text: value['name'],
href: "javascript:void(0)",
click: function () {
// XXX open up a subwindow
console.log(value);
} } ) ),
$('<td>').append( $.map( value['authors'], function ( author, i ) {
return addfilterlink ( author, "author:" + author );
} ) ),
$('<td>').text( value['description'] ),
$('<td>').append( $.map( value['tags'], function ( tag, i ) {
return addfilterlink ( tag, tag );
} ) )).appendTo('#packages_table');
});
evaluate_search();
});
function evaluate_search_term( value, term ) {
if ( term == ":error:") {
return value['checksum-error'];
} else if ( term == ":no-tag:") {
return value['tags'].length == 0;
} else if ( term.substring(0, 5) == "ring:") {
return value['ring'] == term.substring(5);
} else if ( term.substring(0, 7) == "author:") {
return ($.inArray( term.substring(7), value['authors'] ) != -1);
} else if ( term.charAt(0) == "!" ) {
return ! evaluate_search_term( value, term.substring(1) );
} else if ( $.inArray( term, value['tags']) != -1 ) {
return true;
} else {
return false;
}
}
function evaluate_search () {
$.each( $('#packages_table tr'), function (key, dom) {
var value = $(dom).data("obj");
var show = true;
for (termi in search_terms) {
var term = search_terms[termi];
if ( ! evaluate_search_term( value, term ) ) {
show = false;
}
}
if ( show ) {
$(dom).show();
} else {
$(dom).hide();
}
});
// xxx handle search button
// xxx update menu available
$("#search_menu").html("").append( $.map( search_terms, function ( term, i ) {
return removefilterlink ( term, term );
} ) );
$("#packages_table tr:visible:even").removeClass("even");
$("#packages_table tr:visible:odd").addClass("even");
};
});

View File

@ -1,42 +1,21 @@
function TocviewToggle(glyphid, id) {
var glyph = document.getElementById(glyphid);
var s = document.getElementById(id).style;
var expand = s.display == "none";
s.display = expand ? "block" : "none";
glyph.innerHTML = expand ? "&#9660;" : "&#9658;";
}
function ToggleOn(id) {
var s = document.getElementById(id).style;
var li = document.getElementById("li" + id);
s.display = "block";
li.setAttribute("class", "tab-selected");
}
function ToggleOff(id) {
var s = document.getElementById(id).style;
var li = document.getElementById("li" + id);
s.display = "none";
li.setAttribute("class", "");
}
/*
SortTable
version 2
7th April 2007
Stuart Langridge, http://www.kryogenix.org/code/browser/sorttable/
Instructions:
Download this file
Add <script src="sorttable.js"></script> to your HTML
Add class="sortable" to any table you'd like to make sortable
Click on the headers to sort
Thanks to many, many people for contributions and suggestions.
Licenced as X11: http://www.kryogenix.org/code/browser/licence.html
This basically means: do what you want with it.
*/
var stIsIE = /*@cc_on!@*/false;
sorttable = {
@ -47,19 +26,19 @@ sorttable = {
arguments.callee.done = true;
// kill the timer
if (_timer) clearInterval(_timer);
if (!document.createElement || !document.getElementsByTagName) return;
sorttable.DATE_RE = /^(\d\d?)[\/\.-](\d\d?)[\/\.-]((\d\d)?\d\d)$/;
forEach(document.getElementsByTagName('table'), function(table) {
if (table.className.search(/\bsortable\b/) != -1) {
sorttable.makeSortable(table);
}
});
},
makeSortable: function(table) {
if (table.getElementsByTagName('thead').length == 0) {
// table doesn't have a tHead. Since it should have, create one and
@ -70,9 +49,9 @@ sorttable = {
}
// Safari doesn't support table.tHead, sigh
if (table.tHead == null) table.tHead = table.getElementsByTagName('thead')[0];
if (table.tHead.rows.length != 1) return; // can't cope with two header rows
// Sorttable v1 put rows with a class of "sortbottom" at the bottom (as
// "total" rows, for example). This is B&R, since what you're supposed
// to do is put them in a tfoot. So, if there are sortbottom rows,
@ -94,7 +73,7 @@ sorttable = {
}
delete sortbottomrows;
}
// work through each column and calculate its type
headrow = table.tHead.rows[0].cells;
for (var i=0; i<headrow.length; i++) {
@ -110,10 +89,10 @@ sorttable = {
// make it clickable to sort
headrow[i].sorttable_columnindex = i;
headrow[i].sorttable_tbody = table.tBodies[0];
dean_addEvent(headrow[i],"click", function(e) {
dean_addEvent(headrow[i],"click", sorttable.innerSortFunction = function(e) {
if (this.className.search(/\bsorttable_sorted\b/) != -1) {
// if we're already sorted by this column, just
// if we're already sorted by this column, just
// reverse the table, which is quicker
sorttable.reverse(this.sorttable_tbody);
this.className = this.className.replace('sorttable_sorted',
@ -126,7 +105,7 @@ sorttable = {
return;
}
if (this.className.search(/\bsorttable_sorted_reverse\b/) != -1) {
// if we're already sorted by this column in reverse, just
// if we're already sorted by this column in reverse, just
// re-reverse the table, which is quicker
sorttable.reverse(this.sorttable_tbody);
this.className = this.className.replace('sorttable_sorted_reverse',
@ -138,7 +117,7 @@ sorttable = {
this.appendChild(sortfwdind);
return;
}
// remove sorttable_sorted classes
theadrow = this.parentNode;
forEach(theadrow.childNodes, function(cell) {
@ -151,7 +130,7 @@ sorttable = {
if (sortfwdind) { sortfwdind.parentNode.removeChild(sortfwdind); }
sortrevind = document.getElementById('sorttable_sortrevind');
if (sortrevind) { sortrevind.parentNode.removeChild(sortrevind); }
this.className += ' sorttable_sorted';
sortfwdind = document.createElement('span');
sortfwdind.id = "sorttable_sortfwdind";
@ -173,18 +152,18 @@ sorttable = {
/* and comment out this one */
row_array.sort(this.sorttable_sortfunction);
row_array.reverse();
tb = this.sorttable_tbody;
for (var j=0; j<row_array.length; j++) {
tb.appendChild(row_array[j][1]);
}
delete row_array;
});
}
}
},
guessType: function(table, column) {
// guess the type of a column based on its first non-blank row
sortfn = sorttable.sort_alpha;
@ -194,7 +173,7 @@ sorttable = {
if (text.match(/^-?[£$¤]?[\d,.]+%?$/)) {
return sorttable.sort_numeric;
}
// check for a date: dd/mm/yyyy or dd/mm/yy
// check for a date: dd/mm/yyyy or dd/mm/yy
// can have / or . or - as separator
// can be mm/dd as well
possdate = text.match(sorttable.DATE_RE)
@ -217,17 +196,19 @@ sorttable = {
}
return sortfn;
},
getInnerText: function(node) {
// gets the text we want to use for sorting for a cell.
// strips leading and trailing whitespace.
// this is *not* a generic getInnerText function; it's special to sorttable.
// for example, you can override the cell text with a customkey attribute.
// it also gets .value for <input> fields.
if (!node) return "";
hasInputs = (typeof node.getElementsByTagName == 'function') &&
node.getElementsByTagName('input').length;
if (node.getAttribute("sorttable_customkey") != null) {
return node.getAttribute("sorttable_customkey");
}
@ -262,7 +243,7 @@ sorttable = {
}
}
},
reverse: function(tbody) {
// reverse the rows in a tbody
newrows = [];
@ -274,14 +255,14 @@ sorttable = {
}
delete newrows;
},
/* sort functions
each sort function takes two parameters, a and b
you are comparing a[0] and b[0] */
sort_numeric: function(a,b) {
aa = parseFloat(a[0].replace(/[^0-9.-]/g,''));
if (isNaN(aa)) aa = 0;
bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
bb = parseFloat(b[0].replace(/[^0-9.-]/g,''));
if (isNaN(bb)) bb = 0;
return aa-bb;
},
@ -320,7 +301,7 @@ sorttable = {
if (dt1<dt2) return -1;
return 1;
},
shaker_sort: function(list, comp_func) {
// A stable sort function to allow multi-level sorting of data
// see: http://en.wikipedia.org/wiki/Cocktail_sort
@ -350,7 +331,7 @@ sorttable = {
b++;
} // while(swap)
}
}
}
/* ******************************************************************

View File

@ -51,7 +51,7 @@ table.packages thead tr {
background: #FFCC66;
}
table.packages tbody tr:nth-child(2n) {
table.packages tbody tr.even {
background: #F5F5DC;
}