Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Initial commit.

  • Loading branch information...
commit 19cce328ec4b2c13f42c0930e0d80c75a0b5eee2 0 parents
Jeremy Ashkenas authored
2  .gitignore
@@ -0,0 +1,2 @@
+lib/*.js
+config/keys.json
7 Cakefile
@@ -0,0 +1,7 @@
+{spawn, exec}: require 'child_process'
+
+task 'deploy', 'Deploy to Linode', ->
+ exec "rsync -av --progress --inplace --rsh='ssh -p9977' . jashkenas@ashkenas.com:/home/jashkenas/sites/api_playground", ->
+ puts "Deployed..."
+ exec "ssh -t -p 9977 jashkenas@ashkenas.com 'pkill -n node; nohup node sites/api_playground/lib/app.js &; exit'", ->
+ puts "Restarted..."
123 public/css/api.css
@@ -0,0 +1,123 @@
+body {
+ font-size: 62.5%;
+ background: white;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ font-size: 1.4em;
+ line-height: 1.5em;
+ margin: 1.5em 0 0.5em 0;
+}
+ h1 {
+ font-size: 1.25em;
+ font-weight: bold;
+ margin-bottom: 1em;
+ }
+
+a {
+ cursor: pointer;
+ color: #536eac;
+ text-decoration: none;
+}
+ a:hover {
+ color: #000055;
+ }
+
+p {
+ margin: 0 0 1em 0;
+ width: 650px;
+}
+
+label {
+ font-weight: bold;
+ font-size: 0.85em;
+}
+
+.clear {
+ clear: both;
+}
+
+#container {
+ position: relative;
+ font: 1.4em/1.5em "Lucida Grande", "Lucida Sans Unicode", Arial, sans-serif;
+ margin: 50px 10px 50px 50px;
+ color: black;
+}
+ #controls {
+ margin-top: 2em;
+ margin-bottom: 1em;
+ }
+ #picker {
+ margin: 0 1em 0 1em;
+ }
+ #text, #line {
+ line-height: 1.5em;
+ padding: 0.5em;
+ width: 650px;
+ display: none;
+ margin: 0.5em 0 1em;
+ }
+ body.text_mode #text, body.line_mode #line {
+ display: inline;
+ }
+ #text {
+ height: 200px;
+ display: none;
+ }
+ #results {
+
+ }
+ #results table {
+ width: 100%;
+ margin: 2em 0 0 0;
+ font-size: 0.8em;
+ line-height: 1.4em;
+ border-left: 1px solid #C1DAD7;
+ }
+ #results th {
+ font-weight: bold;
+ color: #333;
+ border-right: 1px solid #C1DAD7;
+ border-bottom: 1px solid #C1DAD7;
+ border-top: 1px solid #C1DAD7;
+ background: #daf0f0;
+ padding: 6px 6px 6px 12px;
+ }
+ #results td {
+ vertical-align: middle;
+ max-width: 425px;
+ border-right: 1px solid #C1DAD7;
+ border-bottom: 1px solid #C1DAD7;
+ background: #fff;
+ padding: 6px 6px 6px 12px;
+ color: #333;
+ overflow: hidden;
+ }
+ #results td p {
+ width: auto;
+ }
+
+.minibutton {
+ float: left;
+ cursor: pointer;
+ color: #fff;
+ text-shadow: #aaa 0 -1px 0;
+ font-weight: bold;
+ font-size: 11px;
+ line-height: 11px;
+ padding: 5px 10px 6px;
+ height: 11px;
+ text-align: center;
+ -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px;
+ box-shadow: 0 1px 2px #c4c4c4; -webkit-box-shadow: 0 1px 2px #c4c4c4; -moz-box-shadow: 0 1px 2px #c4c4c4;
+ border: 1px solid #b2b2b2; border-top-color: #c9c9c9; border-bottom-color: #9a9a9a;
+ background: url(/public/images/button_bg.gif) repeat-x left top;
+}
+ .minibutton:active {
+ position: relative;
+ box-shadow: 0 0 1px #c4c4c4; -webkit-box-shadow: 0 0 1px #c4c4c4; -moz-box-shadow: 0 0 1px #c4c4c4;
+ top: 1px;
+ }
+ .minibutton::selection, .minibutton > span::selection {
+ background: transparent;
+ }
56 public/css/reset.css
@@ -0,0 +1,56 @@
+/*------------------------------ RESET + DEFAULT STYLES ---------------------------------*/
+
+/*
+Eric Meyer's final reset.css
+Source: http://meyerweb.com/eric/thoughts/2007/05/01/reset-reloaded/
+*/
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, font, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ outline: 0;
+ font-weight: inherit;
+ font-style: inherit;
+ font-size: 100%;
+ font-family: inherit;
+ vertical-align: baseline;
+}
+
+strong {
+ font-weight: bold;
+}
+/* remember to define focus styles! */
+:focus {
+ outline: 0;
+}
+body {
+ line-height: 1;
+ color: black;
+ background: white;
+}
+ol, ul {
+ list-style: none;
+}
+/* tables still need 'cellspacing="0"' in the markup */
+table {
+ border-collapse: separate;
+ border-spacing: 0;
+}
+caption, th, td {
+ text-align: left;
+ font-weight: normal;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: "";
+}
+blockquote, q {
+ quotes: "" "";
+}
BIN  public/images/button_bg.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  public/images/logo.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  public/images/spinner.gif
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
208 public/js/api.js
@@ -0,0 +1,208 @@
+(function(){
+ window.API = {
+ services: {
+ zemanta: {
+ box: 'Enter a block of text:',
+ description: 'Zemanta is a service that finds names, locations, photos, links and other material based on a raw chunk of text.',
+ mode: 'text'
+ },
+ truveo: {
+ box: 'Enter video keywords:',
+ description: 'Truveo is a huge database of online videos.',
+ mode: 'line'
+ },
+ opencongress: {
+ box: 'Enter the last name of a Member of Congress:',
+ description: 'A general resource on Congress, produced by the Participatory Politics Foundation and the Sunlight Foundation.',
+ mode: 'line'
+ },
+ guardian: {
+ box: 'Enter anything that might appear in a story:',
+ description: 'This API lets you mine The Guardian\'s article database.',
+ mode: 'line'
+ },
+ oilreporter: {
+ box: '',
+ description: 'Oil Reporter is a new effort to crowdsource oil sightings along the Gulf coast.',
+ mode: 'none'
+ },
+ twitter: {
+ box: 'Enter something to search for on Twitter:',
+ description: 'How might you mine the Twitterverse? With Twitter\'s API.',
+ mode: 'line'
+ }
+ },
+ initialize: function() {
+ $('#go').click(API.go);
+ $('#picker').bind('change', API.change);
+ $('#line').keypress(function(e) {
+ if (e.keyCode === 13) {
+ return API.go();
+ }
+ });
+ return API.change('guardian');
+ },
+ change: function(service) {
+ var api;
+ service = _.isString(service) ? service : $('#picker').val();
+ api = API.services[service];
+ API.current = api;
+ document.body.className = ("" + (api.mode) + "_mode");
+ $('#box_label').html(api.box);
+ $('#description').html(api.description);
+ $('#results').html('');
+ return API.getInput().focus();
+ },
+ go: function() {
+ var api;
+ api = $('#picker').val();
+ return API.fetch(api, API.getInput().val());
+ },
+ getInput: function() {
+ if (API.current.mode === 'text') {
+ return $('#text');
+ } else {
+ return $('#line');
+ }
+ },
+ render: function(data) {
+ return $('#results').html(API.table(data));
+ },
+ fetch: function(api, value) {
+ $.getJSON(("/api/" + (api) + ".json"), {
+ text: value
+ }, API[("" + (api) + "Complete")]);
+ return $('#spinner').show();
+ },
+ zemantaComplete: function(response) {
+ var articles, images, keywords;
+ $('#spinner').hide();
+ images = {
+ title: "Images",
+ headers: ["Description", "Image"],
+ rows: _.map(response.images, function(image) {
+ return [image.description, ("<img src='" + image.url_m + "' width='" + image.url_m_w + "' height='" + image.url_m_h + "' />")];
+ })
+ };
+ articles = {
+ title: "Articles",
+ headers: ["Title", "Link", "Publication Date"],
+ rows: _.map(response.articles, function(article) {
+ return [
+ article.title, {
+ url: article.url,
+ text: article.url
+ }, article.published_datetime
+ ];
+ })
+ };
+ keywords = {
+ title: "Keywords",
+ headers: ["Keyword", "Confidence"],
+ rows: _.map(response.keywords, function(keyword) {
+ return [keyword.name, keyword.confidence];
+ })
+ };
+ return API.render({
+ tables: [images, articles, keywords]
+ });
+ },
+ truveoComplete: function(response) {
+ var videos;
+ $('#spinner').hide();
+ videos = {
+ title: "Videos",
+ headers: ["Title", "Channel", "Description", "Video"],
+ rows: _.map(response.response.data.results.videoSet.videos, function(video) {
+ return [video.title, video.channel, video.description, video.videoPlayerEmbedTag];
+ })
+ };
+ return API.render({
+ tables: [videos]
+ });
+ },
+ opencongressComplete: function(response) {
+ var articles, news, people;
+ $('#spinner').hide();
+ people = {
+ title: "Members of Congress",
+ headers: ["Name", "Website", "Phone", "Office", "Religion"],
+ rows: _.map(response.people, function(person) {
+ return [
+ person.name, {
+ url: person.website,
+ text: person.website
+ }, person.phone, person.congress_office, person.religion
+ ];
+ })
+ };
+ news = _.flatten(_.map(response.people, function(person) {
+ return person.recent_news;
+ }));
+ articles = {
+ title: "Articles",
+ headers: ["Title", "Source", "Excerpt", "Link"],
+ rows: _.map(news, function(article) {
+ return [
+ article.title, article.source, article.excerpt, {
+ url: article.url,
+ text: article.url
+ }
+ ];
+ })
+ };
+ return API.render({
+ tables: [people, articles]
+ });
+ },
+ guardianComplete: function(response) {
+ var articles;
+ $('#spinner').hide();
+ articles = {
+ title: "Articles",
+ headers: ["Title", "Section", "Excerpt", "Link"],
+ rows: _.map(response.response.results, function(article) {
+ return [
+ article.webTitle, article.sectionName, article.fields.trailText + '...', {
+ url: article.webUrl,
+ text: article.webUrl
+ }
+ ];
+ })
+ };
+ return API.render({
+ tables: [articles]
+ });
+ },
+ oilreporterComplete: function(response) {
+ var reports;
+ $('#spinner').hide();
+ reports = {
+ title: "Reports",
+ headers: ["Description", "Wildlife", "Oil", "Lat/Lng", "Date"],
+ rows: _.map(response, function(report) {
+ return [report.description, report.wildlife, report.oil, report.latitude + ' / ' + report.longitude, report.created_at];
+ })
+ };
+ return API.render({
+ tables: [reports]
+ });
+ },
+ twitterComplete: function(response) {
+ var tweets;
+ $('#spinner').hide();
+ tweets = {
+ title: "Tweets",
+ headers: ["User", "Picture", "Tweet"],
+ rows: _.map(response.results, function(tweet) {
+ return [tweet.from_user, "<img width='48' height='48' src='" + tweet.profile_image_url + "' />", tweet.text];
+ })
+ };
+ return API.render({
+ tables: [tweets]
+ });
+ },
+ table: _.template("<% _.each(tables, function(table) { %>\n <h3><%= table.title %></h3>\n <table>\n <thead>\n <tr>\n <% _.each(table.headers, function(header) { %>\n <th><%= header %></th>\n <% }); %>\n </tr>\n </thead>\n <tbody>\n <% _.each(table.rows, function(row) { %>\n <tr>\n <% _.each(row, function(col) { %>\n <td class=\"col\">\n <% if (col && col.url) { %>\n <a href=\"<%= col.url %>\" target=\"_blank\"><%= col.text %></a>\n <% } else { %>\n <%= col %>\n <% } %>\n </td>\n <% }); %>\n </tr>\n <% }); %>\n </tbody>\n </table>\n<% }); %>")
+ };
+ $(API.initialize);
+})();
4,376 public/js/jquery-1.3.2.js
4,376 additions, 0 deletions not shown
694 public/js/underscore-1.0.2.js
@@ -0,0 +1,694 @@
+// Underscore.js
+// (c) 2010 Jeremy Ashkenas, DocumentCloud Inc.
+// Underscore is freely distributable under the terms of the MIT license.
+// Portions of Underscore are inspired by or borrowed from Prototype.js,
+// Oliver Steele's Functional, and John Resig's Micro-Templating.
+// For all details and documentation:
+// http://documentcloud.github.com/underscore
+
+(function() {
+ // ------------------------- Baseline setup ---------------------------------
+
+ // Establish the root object, "window" in the browser, or "global" on the server.
+ var root = this;
+
+ // Save the previous value of the "_" variable.
+ var previousUnderscore = root._;
+
+ // Establish the object that gets thrown to break out of a loop iteration.
+ var breaker = typeof StopIteration !== 'undefined' ? StopIteration : '__break__';
+
+ // Quick regexp-escaping function, because JS doesn't have RegExp.escape().
+ var escapeRegExp = function(s) { return s.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); };
+
+ // Save bytes in the minified (but not gzipped) version:
+ var ArrayProto = Array.prototype, ObjProto = Object.prototype;
+
+ // Create quick reference variables for speed access to core prototypes.
+ var slice = ArrayProto.slice,
+ unshift = ArrayProto.unshift,
+ toString = ObjProto.toString,
+ hasOwnProperty = ObjProto.hasOwnProperty,
+ propertyIsEnumerable = ObjProto.propertyIsEnumerable;
+
+ // All ECMA5 native implementations we hope to use are declared here.
+ var
+ nativeForEach = ArrayProto.forEach,
+ nativeMap = ArrayProto.map,
+ nativeReduce = ArrayProto.reduce,
+ nativeReduceRight = ArrayProto.reduceRight,
+ nativeFilter = ArrayProto.filter,
+ nativeEvery = ArrayProto.every,
+ nativeSome = ArrayProto.some,
+ nativeIndexOf = ArrayProto.indexOf,
+ nativeLastIndexOf = ArrayProto.lastIndexOf,
+ nativeIsArray = Array.isArray,
+ nativeKeys = Object.keys;
+
+ // Create a safe reference to the Underscore object for use below.
+ var _ = function(obj) { return new wrapper(obj); };
+
+ // Export the Underscore object for CommonJS.
+ if (typeof exports !== 'undefined') exports._ = _;
+
+ // Export underscore to global scope.
+ root._ = _;
+
+ // Current version.
+ _.VERSION = '1.0.2';
+
+ // ------------------------ Collection Functions: ---------------------------
+
+ // The cornerstone, an each implementation.
+ // Handles objects implementing forEach, arrays, and raw objects.
+ // Delegates to JavaScript 1.6's native forEach if available.
+ var each = _.forEach = function(obj, iterator, context) {
+ try {
+ if (nativeForEach && obj.forEach === nativeForEach) {
+ obj.forEach(iterator, context);
+ } else if (_.isNumber(obj.length)) {
+ for (var i = 0, l = obj.length; i < l; i++) iterator.call(context, obj[i], i, obj);
+ } else {
+ for (var key in obj) {
+ if (hasOwnProperty.call(obj, key)) iterator.call(context, obj[key], key, obj);
+ }
+ }
+ } catch(e) {
+ if (e != breaker) throw e;
+ }
+ return obj;
+ };
+
+ // Return the results of applying the iterator to each element.
+ // Delegates to JavaScript 1.6's native map if available.
+ _.map = function(obj, iterator, context) {
+ if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
+ var results = [];
+ each(obj, function(value, index, list) {
+ results.push(iterator.call(context, value, index, list));
+ });
+ return results;
+ };
+
+ // Reduce builds up a single result from a list of values, aka inject, or foldl.
+ // Delegates to JavaScript 1.8's native reduce if available.
+ _.reduce = function(obj, memo, iterator, context) {
+ if (nativeReduce && obj.reduce === nativeReduce) return obj.reduce(_.bind(iterator, context), memo);
+ each(obj, function(value, index, list) {
+ memo = iterator.call(context, memo, value, index, list);
+ });
+ return memo;
+ };
+
+ // The right-associative version of reduce, also known as foldr. Uses
+ // Delegates to JavaScript 1.8's native reduceRight if available.
+ _.reduceRight = function(obj, memo, iterator, context) {
+ if (nativeReduceRight && obj.reduceRight === nativeReduceRight) return obj.reduceRight(_.bind(iterator, context), memo);
+ var reversed = _.clone(_.toArray(obj)).reverse();
+ return _.reduce(reversed, memo, iterator, context);
+ };
+
+ // Return the first value which passes a truth test.
+ _.detect = function(obj, iterator, context) {
+ var result;
+ each(obj, function(value, index, list) {
+ if (iterator.call(context, value, index, list)) {
+ result = value;
+ _.breakLoop();
+ }
+ });
+ return result;
+ };
+
+ // Return all the elements that pass a truth test.
+ // Delegates to JavaScript 1.6's native filter if available.
+ _.filter = function(obj, iterator, context) {
+ if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
+ var results = [];
+ each(obj, function(value, index, list) {
+ iterator.call(context, value, index, list) && results.push(value);
+ });
+ return results;
+ };
+
+ // Return all the elements for which a truth test fails.
+ _.reject = function(obj, iterator, context) {
+ var results = [];
+ each(obj, function(value, index, list) {
+ !iterator.call(context, value, index, list) && results.push(value);
+ });
+ return results;
+ };
+
+ // Determine whether all of the elements match a truth test.
+ // Delegates to JavaScript 1.6's native every if available.
+ _.every = function(obj, iterator, context) {
+ iterator = iterator || _.identity;
+ if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
+ var result = true;
+ each(obj, function(value, index, list) {
+ if (!(result = result && iterator.call(context, value, index, list))) _.breakLoop();
+ });
+ return result;
+ };
+
+ // Determine if at least one element in the object matches a truth test.
+ // Delegates to JavaScript 1.6's native some if available.
+ _.some = function(obj, iterator, context) {
+ iterator = iterator || _.identity;
+ if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
+ var result = false;
+ each(obj, function(value, index, list) {
+ if (result = iterator.call(context, value, index, list)) _.breakLoop();
+ });
+ return result;
+ };
+
+ // Determine if a given value is included in the array or object using '==='.
+ _.include = function(obj, target) {
+ if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
+ var found = false;
+ each(obj, function(value) {
+ if (found = value === target) _.breakLoop();
+ });
+ return found;
+ };
+
+ // Invoke a method with arguments on every item in a collection.
+ _.invoke = function(obj, method) {
+ var args = _.rest(arguments, 2);
+ return _.map(obj, function(value) {
+ return (method ? value[method] : value).apply(value, args);
+ });
+ };
+
+ // Convenience version of a common use case of map: fetching a property.
+ _.pluck = function(obj, key) {
+ return _.map(obj, function(value){ return value[key]; });
+ };
+
+ // Return the maximum item or (item-based computation).
+ _.max = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
+ var result = {computed : -Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed >= result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Return the minimum element (or element-based computation).
+ _.min = function(obj, iterator, context) {
+ if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
+ var result = {computed : Infinity};
+ each(obj, function(value, index, list) {
+ var computed = iterator ? iterator.call(context, value, index, list) : value;
+ computed < result.computed && (result = {value : value, computed : computed});
+ });
+ return result.value;
+ };
+
+ // Sort the object's values by a criterion produced by an iterator.
+ _.sortBy = function(obj, iterator, context) {
+ return _.pluck(_.map(obj, function(value, index, list) {
+ return {
+ value : value,
+ criteria : iterator.call(context, value, index, list)
+ };
+ }).sort(function(left, right) {
+ var a = left.criteria, b = right.criteria;
+ return a < b ? -1 : a > b ? 1 : 0;
+ }), 'value');
+ };
+
+ // Use a comparator function to figure out at what index an object should
+ // be inserted so as to maintain order. Uses binary search.
+ _.sortedIndex = function(array, obj, iterator) {
+ iterator = iterator || _.identity;
+ var low = 0, high = array.length;
+ while (low < high) {
+ var mid = (low + high) >> 1;
+ iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
+ }
+ return low;
+ };
+
+ // Convert anything iterable into a real, live array.
+ _.toArray = function(iterable) {
+ if (!iterable) return [];
+ if (iterable.toArray) return iterable.toArray();
+ if (_.isArray(iterable)) return iterable;
+ if (_.isArguments(iterable)) return slice.call(iterable);
+ return _.values(iterable);
+ };
+
+ // Return the number of elements in an object.
+ _.size = function(obj) {
+ return _.toArray(obj).length;
+ };
+
+ // -------------------------- Array Functions: ------------------------------
+
+ // Get the first element of an array. Passing "n" will return the first N
+ // values in the array. Aliased as "head". The "guard" check allows it to work
+ // with _.map.
+ _.first = function(array, n, guard) {
+ return n && !guard ? slice.call(array, 0, n) : array[0];
+ };
+
+ // Returns everything but the first entry of the array. Aliased as "tail".
+ // Especially useful on the arguments object. Passing an "index" will return
+ // the rest of the values in the array from that index onward. The "guard"
+ //check allows it to work with _.map.
+ _.rest = function(array, index, guard) {
+ return slice.call(array, _.isUndefined(index) || guard ? 1 : index);
+ };
+
+ // Get the last element of an array.
+ _.last = function(array) {
+ return array[array.length - 1];
+ };
+
+ // Trim out all falsy values from an array.
+ _.compact = function(array) {
+ return _.filter(array, function(value){ return !!value; });
+ };
+
+ // Return a completely flattened version of an array.
+ _.flatten = function(array) {
+ return _.reduce(array, [], function(memo, value) {
+ if (_.isArray(value)) return memo.concat(_.flatten(value));
+ memo.push(value);
+ return memo;
+ });
+ };
+
+ // Return a version of the array that does not contain the specified value(s).
+ _.without = function(array) {
+ var values = _.rest(arguments);
+ return _.filter(array, function(value){ return !_.include(values, value); });
+ };
+
+ // Produce a duplicate-free version of the array. If the array has already
+ // been sorted, you have the option of using a faster algorithm.
+ _.uniq = function(array, isSorted) {
+ return _.reduce(array, [], function(memo, el, i) {
+ if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) memo.push(el);
+ return memo;
+ });
+ };
+
+ // Produce an array that contains every item shared between all the
+ // passed-in arrays.
+ _.intersect = function(array) {
+ var rest = _.rest(arguments);
+ return _.filter(_.uniq(array), function(item) {
+ return _.every(rest, function(other) {
+ return _.indexOf(other, item) >= 0;
+ });
+ });
+ };
+
+ // Zip together multiple lists into a single array -- elements that share
+ // an index go together.
+ _.zip = function() {
+ var args = _.toArray(arguments);
+ var length = _.max(_.pluck(args, 'length'));
+ var results = new Array(length);
+ for (var i = 0; i < length; i++) results[i] = _.pluck(args, String(i));
+ return results;
+ };
+
+ // If the browser doesn't supply us with indexOf (I'm looking at you, MSIE),
+ // we need this function. Return the position of the first occurence of an
+ // item in an array, or -1 if the item is not included in the array.
+ // Delegates to JavaScript 1.8's native indexOf if available.
+ _.indexOf = function(array, item) {
+ if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
+ for (var i = 0, l = array.length; i < l; i++) if (array[i] === item) return i;
+ return -1;
+ };
+
+
+ // Delegates to JavaScript 1.6's native lastIndexOf if available.
+ _.lastIndexOf = function(array, item) {
+ if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
+ var i = array.length;
+ while (i--) if (array[i] === item) return i;
+ return -1;
+ };
+
+ // Generate an integer Array containing an arithmetic progression. A port of
+ // the native Python range() function. See:
+ // http://docs.python.org/library/functions.html#range
+ _.range = function(start, stop, step) {
+ var a = _.toArray(arguments);
+ var solo = a.length <= 1;
+ var start = solo ? 0 : a[0], stop = solo ? a[0] : a[1], step = a[2] || 1;
+ var len = Math.ceil((stop - start) / step);
+ if (len <= 0) return [];
+ var range = new Array(len);
+ for (var i = start, idx = 0; true; i += step) {
+ if ((step > 0 ? i - stop : stop - i) >= 0) return range;
+ range[idx++] = i;
+ }
+ };
+
+ // ----------------------- Function Functions: ------------------------------
+
+ // Create a function bound to a given object (assigning 'this', and arguments,
+ // optionally). Binding with arguments is also known as 'curry'.
+ _.bind = function(func, obj) {
+ var args = _.rest(arguments, 2);
+ return function() {
+ return func.apply(obj || {}, args.concat(_.toArray(arguments)));
+ };
+ };
+
+ // Bind all of an object's methods to that object. Useful for ensuring that
+ // all callbacks defined on an object belong to it.
+ _.bindAll = function(obj) {
+ var funcs = _.rest(arguments);
+ if (funcs.length == 0) funcs = _.functions(obj);
+ each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
+ return obj;
+ };
+
+ // Delays a function for the given number of milliseconds, and then calls
+ // it with the arguments supplied.
+ _.delay = function(func, wait) {
+ var args = _.rest(arguments, 2);
+ return setTimeout(function(){ return func.apply(func, args); }, wait);
+ };
+
+ // Defers a function, scheduling it to run after the current call stack has
+ // cleared.
+ _.defer = function(func) {
+ return _.delay.apply(_, [func, 1].concat(_.rest(arguments)));
+ };
+
+ // Returns the first function passed as an argument to the second,
+ // allowing you to adjust arguments, run code before and after, and
+ // conditionally execute the original function.
+ _.wrap = function(func, wrapper) {
+ return function() {
+ var args = [func].concat(_.toArray(arguments));
+ return wrapper.apply(wrapper, args);
+ };
+ };
+
+ // Returns a function that is the composition of a list of functions, each
+ // consuming the return value of the function that follows.
+ _.compose = function() {
+ var funcs = _.toArray(arguments);
+ return function() {
+ var args = _.toArray(arguments);
+ for (var i=funcs.length-1; i >= 0; i--) {
+ args = [funcs[i].apply(this, args)];
+ }
+ return args[0];
+ };
+ };
+
+ // ------------------------- Object Functions: ------------------------------
+
+ // Retrieve the names of an object's properties.
+ // Delegates to ECMA5's native Object.keys
+ _.keys = nativeKeys || function(obj) {
+ if (_.isArray(obj)) return _.range(0, obj.length);
+ var keys = [];
+ for (var key in obj) if (hasOwnProperty.call(obj, key)) keys.push(key);
+ return keys;
+ };
+
+ // Retrieve the values of an object's properties.
+ _.values = function(obj) {
+ return _.map(obj, _.identity);
+ };
+
+ // Return a sorted list of the function names available on the object.
+ _.functions = function(obj) {
+ return _.filter(_.keys(obj), function(key){ return _.isFunction(obj[key]); }).sort();
+ };
+
+ // Extend a given object with all the properties in passed-in object(s).
+ _.extend = function(obj) {
+ each(_.rest(arguments), function(source) {
+ for (var prop in source) obj[prop] = source[prop];
+ });
+ return obj;
+ };
+
+ // Create a (shallow-cloned) duplicate of an object.
+ _.clone = function(obj) {
+ if (_.isArray(obj)) return obj.slice(0);
+ return _.extend({}, obj);
+ };
+
+ // Invokes interceptor with the obj, and then returns obj.
+ // The primary purpose of this method is to "tap into" a method chain, in order to perform operations on intermediate results within the chain.
+ _.tap = function(obj, interceptor) {
+ interceptor(obj);
+ return obj;
+ };
+
+ // Perform a deep comparison to check if two objects are equal.
+ _.isEqual = function(a, b) {
+ // Check object identity.
+ if (a === b) return true;
+ // Different types?
+ var atype = typeof(a), btype = typeof(b);
+ if (atype != btype) return false;
+ // Basic equality test (watch out for coercions).
+ if (a == b) return true;
+ // One is falsy and the other truthy.
+ if ((!a && b) || (a && !b)) return false;
+ // One of them implements an isEqual()?
+ if (a.isEqual) return a.isEqual(b);
+ // Check dates' integer values.
+ if (_.isDate(a) && _.isDate(b)) return a.getTime() === b.getTime();
+ // Both are NaN?
+ if (_.isNaN(a) && _.isNaN(b)) return true;
+ // Compare regular expressions.
+ if (_.isRegExp(a) && _.isRegExp(b))
+ return a.source === b.source &&
+ a.global === b.global &&
+ a.ignoreCase === b.ignoreCase &&
+ a.multiline === b.multiline;
+ // If a is not an object by this point, we can't handle it.
+ if (atype !== 'object') return false;
+ // Check for different array lengths before comparing contents.
+ if (a.length && (a.length !== b.length)) return false;
+ // Nothing else worked, deep compare the contents.
+ var aKeys = _.keys(a), bKeys = _.keys(b);
+ // Different object sizes?
+ if (aKeys.length != bKeys.length) return false;
+ // Recursive comparison of contents.
+ for (var key in a) if (!(key in b) || !_.isEqual(a[key], b[key])) return false;
+ return true;
+ };
+
+ // Is a given array or object empty?
+ _.isEmpty = function(obj) {
+ if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
+ for (var key in obj) if (hasOwnProperty.call(obj, key)) return false;
+ return true;
+ };
+
+ // Is a given value a DOM element?
+ _.isElement = function(obj) {
+ return !!(obj && obj.nodeType == 1);
+ };
+
+ // Is a given value an array?
+ // Delegates to ECMA5's native Array.isArray
+ _.isArray = nativeIsArray || function(obj) {
+ return !!(obj && obj.concat && obj.unshift && !obj.callee);
+ };
+
+ // Is a given variable an arguments object?
+ _.isArguments = function(obj) {
+ return obj && obj.callee;
+ };
+
+ // Is a given value a function?
+ _.isFunction = function(obj) {
+ return !!(obj && obj.constructor && obj.call && obj.apply);
+ };
+
+ // Is a given value a string?
+ _.isString = function(obj) {
+ return !!(obj === '' || (obj && obj.charCodeAt && obj.substr));
+ };
+
+ // Is a given value a number?
+ _.isNumber = function(obj) {
+ return (obj === +obj) || (toString.call(obj) === '[object Number]');
+ };
+
+ // Is a given value a boolean?
+ _.isBoolean = function(obj) {
+ return obj === true || obj === false;
+ };
+
+ // Is a given value a date?
+ _.isDate = function(obj) {
+ return !!(obj && obj.getTimezoneOffset && obj.setUTCFullYear);
+ };
+
+ // Is the given value a regular expression?
+ _.isRegExp = function(obj) {
+ return !!(obj && obj.test && obj.exec && (obj.ignoreCase || obj.ignoreCase === false));
+ };
+
+ // Is the given value NaN -- this one is interesting. NaN != NaN, and
+ // isNaN(undefined) == true, so we make sure it's a number first.
+ _.isNaN = function(obj) {
+ return _.isNumber(obj) && isNaN(obj);
+ };
+
+ // Is a given value equal to null?
+ _.isNull = function(obj) {
+ return obj === null;
+ };
+
+ // Is a given variable undefined?
+ _.isUndefined = function(obj) {
+ return typeof obj == 'undefined';
+ };
+
+ // -------------------------- Utility Functions: ----------------------------
+
+ // Run Underscore.js in noConflict mode, returning the '_' variable to its
+ // previous owner. Returns a reference to the Underscore object.
+ _.noConflict = function() {
+ root._ = previousUnderscore;
+ return this;
+ };
+
+ // Keep the identity function around for default iterators.
+ _.identity = function(value) {
+ return value;
+ };
+
+ // Run a function n times.
+ _.times = function (n, iterator, context) {
+ for (var i = 0; i < n; i++) iterator.call(context, i);
+ };
+
+ // Break out of the middle of an iteration.
+ _.breakLoop = function() {
+ throw breaker;
+ };
+
+ // Add your own custom functions to the Underscore object, ensuring that
+ // they're correctly added to the OOP wrapper as well.
+ _.mixin = function(obj) {
+ each(_.functions(obj), function(name){
+ addToWrapper(name, _[name] = obj[name]);
+ });
+ };
+
+ // Generate a unique integer id (unique within the entire client session).
+ // Useful for temporary DOM ids.
+ var idCounter = 0;
+ _.uniqueId = function(prefix) {
+ var id = idCounter++;
+ return prefix ? prefix + id : id;
+ };
+
+ // By default, Underscore uses ERB-style template delimiters, change the
+ // following template settings to use alternative delimiters.
+ _.templateSettings = {
+ start : '<%',
+ end : '%>',
+ interpolate : /<%=(.+?)%>/g
+ };
+
+ // JavaScript templating a-la ERB, pilfered from John Resig's
+ // "Secrets of the JavaScript Ninja", page 83.
+ // Single-quote fix from Rick Strahl's version.
+ // With alterations for arbitrary delimiters.
+ _.template = function(str, data) {
+ var c = _.templateSettings;
+ var endMatch = new RegExp("'(?=[^"+c.end.substr(0, 1)+"]*"+escapeRegExp(c.end)+")","g");
+ var fn = new Function('obj',
+ 'var p=[],print=function(){p.push.apply(p,arguments);};' +
+ 'with(obj){p.push(\'' +
+ str.replace(/[\r\t\n]/g, " ")
+ .replace(endMatch,"\t")
+ .split("'").join("\\'")
+ .split("\t").join("'")
+ .replace(c.interpolate, "',$1,'")
+ .split(c.start).join("');")
+ .split(c.end).join("p.push('")
+ + "');}return p.join('');");
+ return data ? fn(data) : fn;
+ };
+
+ // ------------------------------- Aliases ----------------------------------
+
+ _.each = _.forEach;
+ _.foldl = _.inject = _.reduce;
+ _.foldr = _.reduceRight;
+ _.select = _.filter;
+ _.all = _.every;
+ _.any = _.some;
+ _.head = _.first;
+ _.tail = _.rest;
+ _.methods = _.functions;
+
+ // ------------------------ Setup the OOP Wrapper: --------------------------
+
+ // If Underscore is called as a function, it returns a wrapped object that
+ // can be used OO-style. This wrapper holds altered versions of all the
+ // underscore functions. Wrapped objects may be chained.
+ var wrapper = function(obj) { this._wrapped = obj; };
+
+ // Helper function to continue chaining intermediate results.
+ var result = function(obj, chain) {
+ return chain ? _(obj).chain() : obj;
+ };
+
+ // A method to easily add functions to the OOP wrapper.
+ var addToWrapper = function(name, func) {
+ wrapper.prototype[name] = function() {
+ var args = _.toArray(arguments);
+ unshift.call(args, this._wrapped);
+ return result(func.apply(_, args), this._chain);
+ };
+ };
+
+ // Add all of the Underscore functions to the wrapper object.
+ _.mixin(_);
+
+ // Add all mutator Array functions to the wrapper.
+ each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+ var method = ArrayProto[name];
+ wrapper.prototype[name] = function() {
+ method.apply(this._wrapped, arguments);
+ return result(this._wrapped, this._chain);
+ };
+ });
+
+ // Add all accessor Array functions to the wrapper.
+ each(['concat', 'join', 'slice'], function(name) {
+ var method = ArrayProto[name];
+ wrapper.prototype[name] = function() {
+ return result(method.apply(this._wrapped, arguments), this._chain);
+ };
+ });
+
+ // Start chaining a wrapped Underscore object.
+ wrapper.prototype.chain = function() {
+ this._chain = true;
+ return this;
+ };
+
+ // Extracts the result from a wrapped and chained object.
+ wrapper.prototype.value = function() {
+ return this._wrapped;
+ };
+
+})();
153 src/api.coffee
@@ -0,0 +1,153 @@
+window.API: {
+
+ services : {
+ zemanta : {box: 'Enter a block of text:', description: 'Zemanta is a service that finds names, locations, photos, links and other material based on a raw chunk of text.', mode : 'text'},
+ truveo : {box: 'Enter video keywords:', description: 'Truveo is a huge database of online videos.', mode : 'line'},
+ opencongress : {box: 'Enter the last name of a Member of Congress:', description: 'A general resource on Congress, produced by the Participatory Politics Foundation and the Sunlight Foundation.', mode : 'line'},
+ guardian : {box: 'Enter anything that might appear in a story:', description: 'This API lets you mine The Guardian\'s article database.', mode : 'line'},
+ oilreporter : {box: '', description :'Oil Reporter is a new effort to crowdsource oil sightings along the Gulf coast.', mode : 'none'},
+ twitter : {box: 'Enter something to search for on Twitter:', description: 'How might you mine the Twitterverse? With Twitter\'s API.', mode : 'line'}
+ }
+
+ initialize: ->
+ $('#go').click API.go
+ $('#picker').bind 'change', API.change
+ $('#line').keypress (e) -> (API.go() if e.keyCode is 13)
+ API.change 'guardian'
+
+ change: (service) ->
+ service: if _.isString(service) then service else $('#picker').val()
+ api: API.services[service]
+ API.current: api
+ document.body.className: "${api.mode}_mode"
+ $('#box_label').html api.box
+ $('#description').html api.description
+ $('#results').html ''
+ API.getInput().focus()
+
+ go: ->
+ api: $('#picker').val()
+ API.fetch api, API.getInput().val()
+
+ getInput: ->
+ if API.current.mode is 'text' then $('#text') else $('#line')
+
+ render: (data) ->
+ $('#results').html API.table data
+
+ fetch: (api, value) ->
+ $.getJSON "/api/${api}.json", {text: value}, API["${api}Complete"]
+ $('#spinner').show()
+
+ zemantaComplete: (response) ->
+ $('#spinner').hide()
+ images: {
+ title : "Images"
+ headers : ["Description", "Image"]
+ rows : _.map response.images, (image) ->
+ [image.description, "<img src='$image.url_m' width='$image.url_m_w' height='$image.url_m_h' />"]
+ }
+ articles: {
+ title : "Articles"
+ headers : ["Title", "Link", "Publication Date"]
+ rows : _.map response.articles, (article) ->
+ [article.title, {url: article.url, text: article.url}, article.published_datetime]
+ }
+ keywords: {
+ title : "Keywords"
+ headers : ["Keyword", "Confidence"]
+ rows : _.map response.keywords, (keyword) ->
+ [keyword.name, keyword.confidence]
+ }
+ API.render {tables: [images, articles, keywords]}
+
+ truveoComplete: (response) ->
+ $('#spinner').hide()
+ videos: {
+ title : "Videos"
+ headers : ["Title", "Channel", "Description", "Video"]
+ rows : _.map response.response.data.results.videoSet.videos, (video) ->
+ [video.title, video.channel, video.description, video.videoPlayerEmbedTag]
+ }
+ API.render {tables: [videos]}
+
+ opencongressComplete: (response) ->
+ $('#spinner').hide()
+ people: {
+ title : "Members of Congress"
+ headers : ["Name", "Website", "Phone", "Office", "Religion"]
+ rows : _.map response.people, (person) ->
+ [person.name, {url: person.website, text: person.website}, person.phone, person.congress_office, person.religion]
+ }
+ news: _.flatten _.map response.people, (person) -> person.recent_news
+ articles: {
+ title : "Articles"
+ headers : ["Title", "Source", "Excerpt", "Link"]
+ rows : _.map news, (article) ->
+ [article.title, article.source, article.excerpt, {url: article.url, text: article.url}]
+ }
+ API.render {tables: [people, articles]}
+
+ guardianComplete: (response) ->
+ $('#spinner').hide()
+ articles: {
+ title : "Articles"
+ headers : ["Title", "Section", "Excerpt", "Link"]
+ rows : _.map response.response.results, (article) ->
+ [article.webTitle, article.sectionName, article.fields.trailText + '...', {url: article.webUrl, text: article.webUrl}]
+ }
+ API.render {tables: [articles]}
+
+ oilreporterComplete: (response) ->
+ $('#spinner').hide()
+ reports: {
+ title : "Reports"
+ headers : ["Description", "Wildlife", "Oil", "Lat/Lng", "Date"]
+ rows : _.map response, (report) ->
+ [report.description, report.wildlife, report.oil, report.latitude + ' / ' + report.longitude, report.created_at]
+ }
+ API.render {tables : [reports]}
+
+ twitterComplete: (response) ->
+ $('#spinner').hide()
+ tweets: {
+ title : "Tweets"
+ headers : ["User", "Picture", "Tweet"]
+ rows : _.map response.results, (tweet) ->
+ [tweet.from_user, "<img width='48' height='48' src='" + tweet.profile_image_url + "' />", tweet.text]
+ }
+ API.render {tables : [tweets]}
+
+ table: _.template """
+ <% _.each(tables, function(table) { %>
+ <h3><%= table.title %></h3>
+ <table>
+ <thead>
+ <tr>
+ <% _.each(table.headers, function(header) { %>
+ <th><%= header %></th>
+ <% }); %>
+ </tr>
+ </thead>
+ <tbody>
+ <% _.each(table.rows, function(row) { %>
+ <tr>
+ <% _.each(row, function(col) { %>
+ <td class="col">
+ <% if (col && col.url) { %>
+ <a href="<%= col.url %>" target="_blank"><%= col.text %></a>
+ <% } else { %>
+ <%= col %>
+ <% } %>
+ </td>
+ <% }); %>
+ </tr>
+ <% }); %>
+ </tbody>
+ </table>
+ <% }); %>
+ """
+
+}
+
+$ API.initialize
80 src/app.coffee
@@ -0,0 +1,80 @@
+require 'express'
+http: require 'express/express/http'
+path: require 'path'
+sys: require 'sys'
+fs: require 'fs'
+
+configure ->
+ use Static
+ set 'root', path.normalize __dirname + '/..'
+
+urls: {
+ zemanta : "http://api.zemanta.com/services/rest/0.0"
+ truveo : "http://xml.truveo.com/apiv3"
+ opencongress : "http://www.opencongress.org/api/people"
+ guardian : "http://content.guardianapis.com/search"
+ oilreporter : "http://oilreporter.org/reports.json"
+ twitter : "http://search.twitter.com/search.json"
+}
+
+keys: JSON.parse fs.readFileSync('./config/keys.json').toString()
+
+get '/', ->
+ @render 'index.html.ejs', {layout: no}
+
+get '/api/twitter.json', ->
+ http.get urls.twitter, {
+ q: @param('text')
+ rpp: 50
+ }, respond this
+
+get '/api/zemanta.json', ->
+ http.post urls.zemanta, {
+ api_key: keys.zemanta
+ method: 'zemanta.suggest'
+ format: 'json'
+ text: @param 'text'
+ }, respond this
+
+get '/api/truveo.json', ->
+ http.get urls.truveo, {
+ appid: keys.truveo
+ method: 'truveo.videos.getVideos'
+ query: @param 'text'
+ results: 10
+ showRelatedItems: 1
+ showAdult: 0
+ format: 'json'
+ }, respond this
+
+get '/api/opencongress.json', ->
+ http.get urls.opencongress, {
+ key: keys.opencongress
+ last_name: capitalize @param 'text'
+ format: 'json'
+ }, respond this
+
+get '/api/guardian.json', ->
+ http.get urls.guardian, {
+ q: @param 'text'
+ 'api-key': keys.guardian
+ 'show-fields': 'all'
+ format: 'json'
+ }, respond this
+
+get '/api/oilreporter.json', ->
+ http.get urls.oilreporter, {
+ api_key: keys.oilreporter
+ }, respond this
+
+respond: (request) ->
+ (err, body, response) =>
+ # sys.puts err
+ # sys.puts body
+ # sys.puts response.toString()
+ request.respond 200, body
+
+capitalize: (s) ->
+ s.charAt(0).toUpperCase() + s.substring(1).toLowerCase()
+
+run(2560)
58 views/index.html.ejs
@@ -0,0 +1,58 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>API Playground</title>
+ <link rel="stylesheet" href="/public/css/reset.css" />
+ <link rel="stylesheet" href="/public/css/api.css" />
+ <script src="/public/js/jquery-1.3.2.js"></script>
+ <script src="/public/js/underscore-1.0.2.js"></script>
+ <script src="/public/js/api.js"></script>
+</head>
+<body>
+ <div id="container">
+
+ <img id="logo" alt="The API Playground" src="/public/images/logo.png" width="550" height="199" />
+
+ <h2>
+ Helping journalists understand, browse and toy with API data.
+ </h2>
+
+ <p class="intro">
+ Huge amounts of data are available online via APIs, but often in forms that
+ aren't viewable by non-technical folks (like many journalists). This tool
+ helps browse these data sets to make it easier to imagine ways to turn
+ data into stories.
+ </p>
+
+ <div id="controls">
+ <label for="picker">
+ API:
+ </label>
+ <select id="picker">
+ <option value="guardian">The Guardian</option>
+ <option value="zemanta">Zemanta</option>
+ <option value="opencongress">OpenCongress</option>
+ <option value="truveo">Truveo</option>
+ <option value="oilreporter">Oil Reporter</option>
+ <option value="twitter">Twitter</option>
+ </select>
+ <img src="/public/images/spinner.gif" id="spinner" style="display:none;" />
+ </div>
+
+ <p id="description"></p>
+
+ <div id="box_label"></div>
+
+ <div id="text_container">
+ <textarea id="text"></textarea>
+ <input type="text" id="line" />
+ </div>
+
+ <div id="go" class="minibutton">Search the API</div>
+ <br class="clear" />
+
+ <div id="results"></div>
+
+ </div>
+</body>
+</html>
Please sign in to comment.
Something went wrong with that request. Please try again.