commit 47aba3b380459c07967b4f38c9e77fbe42be07a6 Author: David Coallier Date: Sun Feb 6 14:36:30 2011 +0000 The initial github resumes repository diff --git a/css/resume.css b/css/resume.css new file mode 100644 index 0000000..5587469 --- /dev/null +++ b/css/resume.css @@ -0,0 +1,119 @@ +/* +--------------------------------------------------------------------------------- + STRIPPED DOWN RESUME TEMPLATE + html resume + + v0.9: 5/28/09 + + design and code by: thingsthatarebrown.com + (matt brown) +--------------------------------------------------------------------------------- +*/ + + +.msg { padding: 10px; background: #222; position: relative; } +.msg h1 { color: #fff; } +.msg a { margin-left: 20px; background: #408814; color: white; padding: 4px 8px; text-decoration: none; } +.msg a:hover { background: #266400; } + +/* //-- yui-grids style overrides -- */ +body { font-family: Georgia; color: #444; } +#inner { padding: 10px 80px; margin: 80px auto; background: #f5f5f5; border: solid #666; border-width: 8px 0 2px 0; } +.yui-gf { margin-bottom: 2em; padding-bottom: 2em; border-bottom: 1px solid #ccc; } + +/* //-- header, body, footer -- */ +#hd { margin: 2.5em 0 3em 0; padding-bottom: 1.5em; border-bottom: 1px solid #ccc } +#hd h2 { text-transform: uppercase; letter-spacing: 2px; } +#bd, #ft { margin-bottom: 2em; } + +/* //-- footer -- */ +#ft { padding: 1em 0 5em 0; font-size: 92%; border-top: 1px solid #ccc; text-align: center; } +#ft p { margin-bottom: 0; text-align: center; } + +/* //-- core typography and style -- */ +#hd h1 { font-size: 48px; text-transform: uppercase; letter-spacing: 3px; } +h2 { font-size: 152% } +h3, h4 { font-size: 122%; } +h1, h2, h3, h4 { color: #333; } +p { font-size: 100%; line-height: 18px; padding-right: 3em; } +a { color: #990003 } +a:hover { text-decoration: none; } +strong { font-weight: bold; } +li { line-height: 24px; border-bottom: 1px solid #ccc; } +p.enlarge { font-size: 144%; padding-right: 6.5em; line-height: 24px; } +p.enlarge span { color: #000 } +.contact-info { margin-top: 7px; } +.first h2 { font-style: italic; } +.last { border-bottom: 0 } + + +/* //-- section styles -- */ + +a#pdf { display: block; float: left; background: #666; color: white; padding: 6px 50px 6px 12px; margin-bottom: 6px; text-decoration: none; } +a#pdf:hover { background: #222; } + +.job { position: relative; margin-bottom: 1em; padding-bottom: 1em; border-bottom: 1px solid #ccc; } +.job h4 { position: absolute; top: 0.35em; right: 0 } +.job p { margin: 0.75em 0 3em 0; } + +.last { border: none; } +.skills-list { } +.skills-list ul { margin: 0; } +.skills-list li { margin: 3px 0; padding: 3px 0; } +.skills-list li span { font-size: 152%; display: block; margin-bottom: -2px; padding: 0 } +.talent { width: 32%; float: left } +.talent h2 { margin-bottom: 6px; } + +#srt-ttab { margin-bottom: 100px; text-align: center; } +#srt-ttab img.last { margin-top: 20px } + +/* --// override to force 1/8th width grids -- */ +.yui-gf .yui-u{width:80.2%;} +.yui-gf div.first{width:12.3%;} + +input#username { + position: relative; + float: left; + height: 30px; + width: 75%; + border: 2px solid #444; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + font-family: Georgia; color: #444; + font-size: 18px; + padding: 5px; +} + +button#gen { + position: relative; + float: left; + margin-left: 10px; + width: 20%; + height: 44px; + font-family: Georgia; color: #444; + font-size: 18px; + color: white; + background-image: -webkit-gradient( + linear, + left bottom, + left top, + color-stop(0.21, rgb(31,31,31)), + color-stop(0.61, rgb(51,51,51)) + ); + background-image: -moz-linear-gradient( + center bottom, + rgb(31,31,31) 21%, + rgb(51,51,51) 61% + ); + border: 1px solid #444; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +button#gen:hover { + cursor: pointer; +} + + diff --git a/images/srt.png b/images/srt.png new file mode 100644 index 0000000..dc33184 Binary files /dev/null and b/images/srt.png differ diff --git a/images/ttab.png b/images/ttab.png new file mode 100644 index 0000000..2a2ca1e Binary files /dev/null and b/images/ttab.png differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..f04d351 --- /dev/null +++ b/index.html @@ -0,0 +1,23 @@ + + + + + Github Resume + + + + + + + + + + + + + + + + + + diff --git a/js/github.js b/js/github.js new file mode 100644 index 0000000..6f077d5 --- /dev/null +++ b/js/github.js @@ -0,0 +1,667 @@ +// ## Client-side Javascript API wrapper for GitHub +// +// Tries to map one-to-one with the GitHub API V2, but in a Javascripty manner. + +(function (globals) { + + // Before we implement the API methods, we will define all of our private + // variables and helper functions with one `var` statement. + var + + // The username and authentication token of the library's user. + authUsername, + authToken, + + // To save keystrokes when we make JSONP calls to the HTTP API, we will keep + // track of the root from which all V2 urls extend. + apiRoot = "https://github.com/api/v2/json/", + + // Send a JSONP request to the Github API that calls `callback` with + // the `context` argument as `this`. + // + // The `url` parameter is concatenated with the apiRoot, for the reasons + // mentioned above. The way that we are supporting non-global, anonymous + // functions is by sticking them in the globally exposed + // `gh.__jsonp_callbacks` object with a "unique" `id` that is the current + // time in milliseconds. Once the callback is called, it is deleted from the + // object to prevent memory leaks. + jsonp = function (url, callback, context) { + var id = +new Date, + script = document.createElement("script"); + + while (gh.__jsonp_callbacks[id] !== undefined) + id += Math.random(); // Avoid slight possibility of id clashes. + + gh.__jsonp_callbacks[id] = function () { + delete gh.__jsonp_callbacks[id]; + callback.apply(context, arguments); + }; + + var prefix = "?"; + if (url.indexOf("?") >= 0) + prefix = "&"; + + url += prefix + "callback=" + encodeURIComponent("gh.__jsonp_callbacks[" + id + "]"); + if (authUsername && authToken) { + url += "&login=" + authUsername + "&authToken=" + authToken; + } + script.setAttribute("src", apiRoot + url); + + document.getElementsByTagName('head')[0].appendChild(script); + }, + + // Send an HTTP POST. Unfortunately, it isn't possible to support a callback + // with the resulting data. (Please prove me wrong if you can!) + // + // This is implemented with a hack to get around the cross-domain + // restrictions on ajax calls. Basically, a form is created that will POST + // to the GitHub API URL, stuck inside an iframe so that it won't redirect + // this page, and then submitted. + post = function (url, vals) { + var + form = document.createElement("form"), + iframe = document.createElement("iframe"), + doc = iframe.contentDocument !== undefined ? + iframe.contentDocument : + iframe.contentWindow.document, + key, field; + vals = vals || {}; + + form.setAttribute("method", "post"); + form.setAttribute("action", apiRoot + url); + for (key in vals) { + if (vals.hasOwnProperty(key)) { + field = document.createElement("input"); + field.type = "hidden"; + field.value = encodeURIComponent(vals[key]); + form.appendChild(field); + } + } + + iframe.setAttribute("style", "display: none;"); + doc.body.appendChild(form); + document.body.appendChild(iframe); + form.submit(); + }, + + // This helper function will throw a TypeError if the library user is not + // properly authenticated. Otherwise, it silently returns. + authRequired = function (username) { + if (!authUsername || !authToken || authUsername !== username) { + throw new TypeError("gh: Must be authenticated to do that."); + } + }, + + // Convert an object to a url parameter string. + // + // paramify({foo:1, bar:3}) -> "foo=1&bar=3". + paramify = function (params) { + var str = "", key; + for (key in params) if (params.hasOwnProperty(key)) + str += key + "=" + params[key] + "&"; + return str.replace(/&$/, ""); + }, + + // Get around how the GH team haven't migrated all the API to version 2, and + // how gists use a different api root. + withTempApiRoot = function (tempApiRoot, fn) { + return function () { + var oldRoot = apiRoot; + apiRoot = tempApiRoot; + fn.apply(this, arguments); + apiRoot = oldRoot; + }; + }, + + // Expose the global `gh` variable, through which every API method is + // accessed, but keep a local variable around so we can reference it easily. + gh = globals.gh = {}; + + // Psuedo private home for JSONP callbacks (which are required to be global + // by the nature of JSONP, as discussed earlier). + gh.__jsonp_callbacks = {}; + + // Authenticate as a user. Does not try to validate at any point; that job + // is up to each individual method, which calls `authRequired` as needed. + gh.authenticate = function (username, token) { + authUsername = username; + authToken = token; + return this; + }; + + // ### Users + + // The constructor for user objects. Just creating an instance of a user + // doesn't fetch any data from GitHub, you need to get explicit about what + // you want to do that. + // + // var huddlej = gh.user("huddlej"); + gh.user = function (username) { + if ( !(this instanceof gh.user)) { + return new gh.user(username); + } + this.username = username; + }; + + // Show basic user info; you can get more info if you are authenticated as + // this user. + // + // gh.user("fitzgen").show(function (data) { + // console.log(data.user); + // }); + gh.user.prototype.show = function (callback, context) { + jsonp("user/show/" + this.username, callback, context); + return this; + }; + + // Update a user's info. You must be authenticated as this user for this to + // succeed. + // + // TODO: example + gh.user.prototype.update = function (params) { + authRequired(this.username); + var key, postData = { + login: authUsername, + token: authToken + }; + for (key in params) { + if (params.hasOwnProperty(key)) { + postData["values["+key+"]"] = encodeURIComponent(params[key]); + } + } + post("user/show/" + this.username, postData); + return this; + }; + + // Get a list of who this user is following. + // + // TODO: example + gh.user.prototype.following = function (callback, context) { + jsonp("user/show/" + this.username + "/following", callback, context); + }; + + // Find out what other users are following this user. + // + // TODO: example + gh.user.prototype.followers = function (callback, context) { + jsonp("user/show/" + this.username + "/followers", callback, context); + }; + + // Make this user follow some other user. You must be authenticated as this + // user for this to succeed. + // + // TODO: example + gh.user.prototype.follow = function (user) { + authRequired.call(this); + post("user/follow/" + user); + return this; + }; + + // Make this user quit following the given `user`. You must be authenticated + // as this user to succeed. + // + // TODO: example + gh.user.prototype.unfollow = function (user) { + authRequired.call(this); + post("user/unfollow/" + user); + return this; + }; + + // Get a list of repositories that this user is watching. + // + // TODO: example + gh.user.prototype.watching = function (callback, context) { + jsonp("repos/watched/" + this.username, callback, context); + return this; + }; + + // Get a list of this user's repositories. + // + // gh.user("fitzgen").repos(function (data) { + // alert(data.repositories.length); + // }); + gh.user.prototype.repos = function (callback, context) { + gh.repo.forUser(this.username, callback, context); + return this; + }; + + // Make this user fork the repo that lives at + // http://github.com/user/repo. You must be authenticated as this user for + // this to succeed. + // + // gh.user("fitzgen").forkRepo("brianleroux", "wtfjs"); + gh.user.prototype.forkRepo = function (user, repo) { + authRequired(this.username); + post("repos/fork/" + user + "/" + repo); + return this; + }; + + // Get a list of all repos that this user can push to (including ones that + // they are just a collaborator on, and do not own). Must be authenticated + // as this user. + gh.user.prototype.pushable = function (callback, context) { + authRequired(authUsername); + jsonp("repos/pushable", callback, context); + }; + + gh.user.prototype.publicGists = withTempApiRoot( + "http://gist.github.com/api/v1/json/gists/", + function (callback, context) { + jsonp(this.username, callback, context); + return this; + } + ); + + // Search users for `query`. + gh.user.search = function (query, callback, context) { + jsonp("user/search/" + query, callback, context); + return this; + }; + + // ### Repositories + + // This is the base constructor for creating repo objects. Note that this + // won't actually hit the GitHub API until you specify what data you want, + // or what action you wish to take via a prototype method. + gh.repo = function (user, repo) { + if ( !(this instanceof gh.repo)) { + return new gh.repo(user, repo); + } + this.repo = repo; + this.user = user; + }; + + // Get basic information on this repo. + // + // gh.repo("schacon", "grit").show(function (data) { + // console.log(data.repository.description); + // }); + gh.repo.prototype.show = function (callback, context) { + jsonp("repos/show/" + this.user + "/" + this.repo, callback, context); + return this; + }; + + // Update the information for this repo. Must be authenticated as the + // repository owner. Params can include: + // + // * description + // * homepage + // * has_wiki + // * has_issues + // * has_downloads + gh.repo.prototype.update = function (params) { + authRequired(this.user); + var key, postData = { + login: authUsername, + token: authToken + }; + for (key in params) { + if (params.hasOwnProperty(key)) { + postData["values["+key+"]"] = encodeURIComponent(params[key]); + } + } + post("repos/show/" + this.user + "/" + this.repo, postData); + return this; + }; + + // Get all tags for this repo. + gh.repo.prototype.tags = function (callback, context) { + jsonp("repos/show/" + this.user + "/" + this.repo + "/tags", + callback, + context); + return this; + }; + + // Get all branches in this repo. + gh.repo.prototype.branches = function (callback, context) { + jsonp("repos/show/" + this.user + "/" + this.repo + "/branches", + callback, + context); + return this; + }; + + // Gather line count information on the language(s) used in this repo. + gh.repo.prototype.languages = function (callback, context) { + jsonp("/repos/show/" + this.user + "/" + this.repo + "/languages", + callback, + context); + return this; + }; + + // Gather data on all the forks of this repo. + gh.repo.prototype.network = function (callback, context) { + jsonp("repos/show/" + this.user + "/" + this.repo + "/network", + callback, + context); + return this; + }; + + // All users who have contributed to this repo. Pass `true` to showAnon if you + // want to see the non-github contributors. + gh.repo.prototype.contributors = function (callback, context, showAnon) { + var url = "repos/show/" + this.user + "/" + this.repo + "/contributors"; + if (showAnon) + url += "/anon"; + jsonp(url, + callback, + context); + return this; + }; + + // Get all of the collaborators for this repo. + gh.repo.prototype.collaborators = function (callback, context) { + jsonp("repos/show/" + this.user + "/" + this.repo + "/collaborators", + callback, + context); + return this; + }; + + // Add a collaborator to this project. Must be authenticated. + gh.repo.prototype.addCollaborator = function (collaborator) { + authRequired(this.user); + post("repos/collaborators/" + this.repo + "/add/" + collaborator); + return this; + }; + + // Remove a collaborator from this project. Must be authenticated. + gh.repo.prototype.removeCollaborator = function (collaborator) { + authRequired(this.user); + post("repos/collaborators/" + this.repo + "/remove/" + collaborator); + return this; + }; + + // Make this repository private. Authentication required. + gh.repo.prototype.setPrivate = function () { + authRequired(this.user); + post("repo/set/private/" + this.repo); + return this; + }; + + // Make this repository public. Authentication required. + gh.repo.prototype.setPublic = function () { + authRequired(this.user); + post("repo/set/public/" + this.repo); + return this; + }; + + // Search for repositories. `opts` may include `start_page` or `language`, + // which must be capitalized. + gh.repo.search = function (query, opts, callback, context) { + var url = "repos/search/" + query.replace(" ", "+"); + if (typeof opts === "function") { + opts = {}; + callback = arguments[1]; + context = arguments[2]; + } + url += "?" + paramify(opts); + return this; + }; + + // Get all the repos that are owned by `user`. + gh.repo.forUser = function (user, callback, context) { + jsonp("repos/show/" + user, callback, context); + return this; + }; + + // Create a repository. Must be authenticated. + gh.repo.create = function (name, opts) { + authRequired(authUsername); + opts.name = name; + post("repos/create", opts); + return this; + }; + + // Delete a repository. Must be authenticated. + gh.repo.del = function (name) { + authRequired(authUsername); + post("repos/delete/" + name); + return this; + }; + + // ### Commits + + gh.commit = function (user, repo, sha) { + if ( !(this instanceof gh.commit) ) + return new gh.commit(user, repo, sha); + this.user = user; + this.repo = repo; + this.sha = sha; + }; + + gh.commit.prototype.show = function (callback, context) { + jsonp("commits/show/" + this.user + "/" + this.repo + "/" + this.sha, + callback, + context); + return this; + }; + + // Get a list of all commits on a repos branch. + gh.commit.forBranch = function (user, repo, branch, callback, context) { + jsonp("commits/list/" + user + "/" + repo + "/" + branch, + callback, + context); + return this; + }; + + // Get a list of all commits on this path (file or dir). + gh.commit.forPath = function (user, repo, branch, path, callback, context) { + jsonp("commits/list/" + user + "/" + repo + "/" + branch + "/" + path, + callback, + context); + return this; + }; + + // ### Issues + + gh.issue = function (user, repo, number) { + if ( !(this instanceof gh.issue) ) + return new gh.commit(user, repo, number); + this.user = user; + this.repo = repo; + this.number = number; + }; + + // View this issue's info. + gh.issue.prototype.show = function (callback, context) { + jsonp("issues/show/" + this.user + "/" + this.repo + "/" + this.number, + callback, + context); + return this; + }; + + // Get a list of all comments on this issue. + gh.issue.prototype.comments = function (callback, context) { + jsonp("issues/comments/" + this.user + "/" + this.repo + "/" + this.number, + callback, + context); + return this; + }; + + // Close this issue. + gh.issue.prototype.close = function () { + authRequired(this.user); + post("issues/close/" + this.user + "/" + this.repo + "/" + this.number); + return this; + }; + + // Reopen this issue. + gh.issue.prototype.reopen = function () { + authRequired(this.user); + post("issues/reopen/" + this.user + "/" + this.repo + "/" + this.number); + return this; + }; + + // Reopen this issue. + gh.issue.prototype.update = function (title, body) { + authRequired(this.user); + post("issues/edit/" + this.user + "/" + this.repo + "/" + this.number, { + title: title, + body: body + }); + return this; + }; + + // Add `label` to this issue. If the label is not yet in the system, it will + // be created. + gh.issue.prototype.addLabel = function (label) { + post("issues/label/add/" + this.user + "/" + this.repo + "/" + label + "/" + this.number); + return this; + }; + + // Remove a label from this issue. + gh.issue.prototype.removeLabel = function (label) { + post("issues/label/remove/" + this.user + "/" + this.repo + "/" + label + "/" + this.number); + return this; + }; + + // Comment on this issue as the user that is authenticated. + gh.issue.prototype.comment = function (comment) { + authRequired(authUsername); + post("/issues/comment/" + user + "/" + repo + "/" + this.number, { + comment: comment + }); + return this; + }; + + // Get all issues' labels for the repo. + gh.issue.labels = function (user, repo) { + jsonp("issues/labels/" + user + "/" + repo, + callback, + context); + return this; + }; + + // Open an issue. Must be authenticated. + gh.issue.open = function (repo, title, body) { + authRequired(authUsername); + post("issues/open/" + authUsername + "/" + repo, { + title: title, + body: body + }); + return this; + }; + + // Search a repository's issue tracker. `state` can be "open" or "closed". + gh.issue.search = function (user, repo, state, query, callback, context) { + jsonp("/issues/search/" + user + "/" + repo + "/" + state + "/" + query, + callback, + context); + return this; + }; + + // Get a list of issues for the given repo. `state` can be "open" or + // "closed". + gh.issue.list = function (user, repo, state, callback, context) { + jsonp("issues/list/" + user + "/" + repo + "/" + state, + callback, + context); + return this; + }; + + // ### Gists + + gh.gist = function (id) { + if ( !(this instanceof gh.gist) ) { + return new gh.gist(id); + } + this.id = id; + }; + + gh.gist.prototype.show = withTempApiRoot( + "http://gist.github.com/api/v1/json/", + function (callback, context) { + jsonp(this.id, callback, cont); + return this; + } + ); + + gh.gist.prototype.file = withTempApiRoot( + "http://gist.github.com/raw/v1/json/", + function (filename, callback, context) { + jsonp(this.id + "/" + filename, callback, cont); + return this; + } + ); + + // ### Objects + + gh.object = function (user, repo) { + if (!(this instanceof gh.object)) { + return new gh.object(user, repo); + } + this.user = user; + this.repo = repo; + }; + + // Get the contents of a tree by tree SHA + gh.object.prototype.tree = function (sha, callback, context) { + jsonp("tree/show/" + this.user + "/" + this.repo + "/" + sha, + callback, + context); + return this; + }; + + // Get the data about a blob by tree SHA and path + gh.object.prototype.blob = function (path, sha, callback, context) { + jsonp("blob/show/" + this.user + "/" + this.repo + "/" + sha + "/" + path, + callback, + context); + return this; + }; + + // Get only blob meta + gh.object.prototype.blobMeta = function (path, sha, callback, context) { + jsonp("blob/show/" + this.user + "/" + this.repo + "/" + sha + "/" + path + "?meta=1", + callback, + context); + return this; + }; + + // Get list of blobs + gh.object.prototype.blobAll = function (branch, callback, context) { + jsonp("blob/all/" + this.user + "/" + this.repo + "/" + branch, + callback, + context); + return this; + }; + + // Get meta of each blob in tree + gh.object.prototype.blobFull = function (sha, callback, context) { + jsonp("blob/full/" + this.user + "/" + this.repo + "/" + sha, + callback, + context); + return this; + }; + + // ### Network + + gh.network = function(user, repo) { + if (!(this instanceof gh.network)) { + return new gh.network(user, repo); + } + this.user = user; + this.repo = repo; + }; + + gh.network.prototype.data = withTempApiRoot( + "http://github.com/", + function (nethash, start, end, callback, context) { + jsonp(this.user + "/" + this.repo + "/network_data_chunk?" + + nethash + "&" + start + "&" + end, + callback, + context); + return this; + } + ); + + gh.network.prototype.meta = withTempApiRoot( + "http://github.com/", + function (callback, context) { + jsonp(this.user + "/" + this.repo + "/network_meta", + callback, + context); + return this; + } + ); + +}(window)); diff --git a/js/githubresume.js b/js/githubresume.js new file mode 100644 index 0000000..bad1630 --- /dev/null +++ b/js/githubresume.js @@ -0,0 +1,140 @@ +var urlParams = {}; +(function () { + var e, + a = /\+/g, // Regex for replacing addition symbol with a space + r = /([^&=]+)=?([^&]*)/g, + d = function (s) { return decodeURIComponent(s.replace(a, " ")); }, + q = window.location.search.substring(1); + + while (e = r.exec(q)) + urlParams[0] = d(e[1]); +})(); + +var username; + +$(document).ready(function() { + try { + if (urlParams[0] !== undefined) { + username = urlParams[0]; + run(); + } else { + home(); + } + } catch (err) { + console.log(err); + } +}); + +var error = function() { + $.ajax({ + url: 'views/error.html', + dataType: 'html', + success: function(data) { + var template = data; + $('#resume').html(data); + } + }); +}; + +var home = function() { + $.ajax({ + url: 'views/index.html', + dataType: 'html', + success: function(data) { + var template = data; + $('#resume').html(data); + } + }); +}; + +var run = function() { + + var gh_user = gh.user(username); + var itemCount = 0, maxItems = 5; + + var res = gh_user.show(function(data) { + gh_user.repos(function(data) { + repos = data; + }); + + var since = new Date(data.user.created_at); + since = since.getFullYear(); + + var view = { + name: data.user.name, + email: data.user.email, + created_at: data.user.created_at, + blog: data.user.blog, + location: data.user.location, + repos: data.user.public_repo_count, + plural: data.user.public_repo_count > 1 ? 'repositories' : 'repository', + username: username, + since: since + }; + + $.ajax({ + url: 'views/resume.html', + dataType: 'html', + success: function(data) { + var template = data; + var html = Mustache.to_html(template, view); + $('#resume').html(html); + } + }); + }); + + gh_user.repos(function(data) { + var repos = data.repositories; + + var sorted = []; + var languages = []; + + repos.forEach(function(elm, i, arr) { + if (arr[i].fork !== false) { + return; + } + var popularity = arr[i].watchers + arr[i].forks; + sorted.push({position: i, popularity: popularity, info: arr[i]}); + }); + + function sortByPopularity(a, b) { + return b.popularity - a.popularity; + }; + + sorted.sort(sortByPopularity); + + $.ajax({ + url: 'views/job.html', + dataType: 'html', + success: function(response) { + var now = new Date().getFullYear(); + + sorted.forEach(function(elm, index, arr) { + if (itemCount >= maxItems) { + return; + } + + var since = new Date(arr[index].info.created_at); + since = since.getFullYear(); + var view = { + name: arr[index].info.name, + since: since, + now: now, + description: arr[index].info.description, + username: username, + watchers: arr[index].info.watchers, + forks: arr[index].info.forks + }; + + var template = response; + var html = Mustache.to_html(template, view); + + $('#jobs').append($(html)); + ++itemCount; + }); + } + }); + }); +}; + +$(window).bind('error', error); diff --git a/js/mustache.js b/js/mustache.js new file mode 100644 index 0000000..95a68d2 --- /dev/null +++ b/js/mustache.js @@ -0,0 +1,298 @@ +/* + Shameless port of http://github.com/defunkt/mustache + by Jan Lehnardt , + Alexander Lang , + Sebastian Cohnen + + Thanks @defunkt for the awesome code. + + See http://github.com/defunkt/mustache for more info. +*/ + +var Mustache = function() { + var Renderer = function() {}; + + Renderer.prototype = { + otag: "{{", + ctag: "}}", + pragmas: {}, + buffer: [], + pragmas_parsed: false, + + render: function(template, context, partials, in_recursion) { + // fail fast + if(template.indexOf(this.otag) == -1) { + if(in_recursion) { + return template; + } else { + this.send(template); + return; + } + } + + if(!in_recursion) { + this.buffer = []; + } + + if(!this.pragmas_parsed) { + template = this.render_pragmas(template); + } + var html = this.render_section(template, context, partials); + if(in_recursion) { + return this.render_tags(html, context, partials, in_recursion); + } + + this.render_tags(html, context, partials, in_recursion); + }, + + /* + Sends parsed lines + */ + send: function(line) { + if(line != "") { + this.buffer.push(line); + } + }, + + /* + Looks for %PRAGMAS + */ + render_pragmas: function(template) { + this.pragmas_parsed = true; + // no pragmas + if(template.indexOf(this.otag + "%") == -1) { + return template; + } + + var that = this; + var regex = new RegExp(this.otag + "%([\\w_-]+) ?([\\w]+=[\\w]+)?" + + this.ctag); + return template.replace(regex, function(match, pragma, options) { + that.pragmas[pragma] = {}; + if(options) { + var opts = options.split("="); + that.pragmas[pragma][opts[0]] = opts[1]; + } + return ""; + // ignore unknown pragmas silently + }); + }, + + /* + Tries to find a partial in the global scope and render it + */ + render_partial: function(name, context, partials) { + if(typeof(context[name]) != "object") { + throw({message: "subcontext for '" + name + "' is not an object"}); + } + if(!partials || !partials[name]) { + throw({message: "unknown_partial '" + name + "'"}); + } + return this.render(partials[name], context[name], partials, true); + }, + + /* + Renders boolean and enumerable sections + */ + render_section: function(template, context, partials) { + if(template.indexOf(this.otag + "#") == -1) { + return template; + } + var that = this; + // CSW - Added "+?" so it finds the tighest bound, not the widest + var regex = new RegExp(this.otag + "\\#(.+)" + this.ctag + + "\\s*([\\s\\S]+?)" + this.otag + "\\/\\1" + this.ctag + "\\s*", "mg"); + + // for each {{#foo}}{{/foo}} section do... + return template.replace(regex, function(match, name, content) { + var value = that.find(name, context); + if(that.is_array(value)) { // Enumerable, Let's loop! + return that.map(value, function(row) { + return that.render(content, that.merge(context, + that.create_context(row)), partials, true); + }).join(""); + } else if(value) { // boolean section + return that.render(content, context, partials, true); + } else { + return ""; + } + }); + }, + + /* + Replace {{foo}} and friends with values from our view + */ + render_tags: function(template, context, partials, in_recursion) { + // tit for tat + var that = this; + + var new_regex = function() { + return new RegExp(that.otag + "(=|!|>|\\{|%)?([^\/#]+?)\\1?" + + that.ctag + "+", "g"); + }; + + var regex = new_regex(); + var lines = template.split("\n"); + for (var i=0; i < lines.length; i++) { + lines[i] = lines[i].replace(regex, function(match, operator, name) { + switch(operator) { + case "!": // ignore comments + return match; + case "=": // set new delimiters, rebuild the replace regexp + that.set_delimiters(name); + regex = new_regex(); + return ""; + case ">": // render partial + return that.render_partial(name, context, partials); + case "{": // the triple mustache is unescaped + return that.find(name, context); + default: // escape the value + return that.escape(that.find(name, context)); + } + }, this); + if(!in_recursion) { + this.send(lines[i]); + } + } + + if(in_recursion) { + return lines.join("\n"); + } + }, + + set_delimiters: function(delimiters) { + var dels = delimiters.split(" "); + this.otag = this.escape_regex(dels[0]); + this.ctag = this.escape_regex(dels[1]); + }, + + escape_regex: function(text) { + // thank you Simon Willison + if(!arguments.callee.sRE) { + var specials = [ + '/', '.', '*', '+', '?', '|', + '(', ')', '[', ']', '{', '}', '\\' + ]; + arguments.callee.sRE = new RegExp( + '(\\' + specials.join('|\\') + ')', 'g' + ); + } + return text.replace(arguments.callee.sRE, '\\$1'); + }, + + /* + find `name` in current `context`. That is find me a value + from the view object + */ + find: function(name, context) { + name = this.trim(name); + if(typeof context[name] === "function") { + return context[name].apply(context); + } + if(context[name] !== undefined) { + return context[name]; + } + // silently ignore unkown variables + return ""; + }, + + // Utility methods + + /* + Does away with nasty characters + */ + escape: function(s) { + return ((s == null) ? "" : s).toString().replace(/[&"<>\\]/g, function(s) { + switch(s) { + case "&": return "&"; + case "\\": return "\\\\";; + case '"': return '\"';; + case "<": return "<"; + case ">": return ">"; + default: return s; + } + }); + }, + + /* + Merges all properties of object `b` into object `a`. + `b.property` overwrites a.property` + */ + merge: function(a, b) { + var _new = {}; + for(var name in a) { + if(a.hasOwnProperty(name)) { + _new[name] = a[name]; + } + }; + for(var name in b) { + if(b.hasOwnProperty(name)) { + _new[name] = b[name]; + } + }; + return _new; + }, + + // by @langalex, support for arrays of strings + create_context: function(_context) { + if(this.is_object(_context)) { + return _context; + } else if(this.pragmas["IMPLICIT-ITERATOR"]) { + var iterator = this.pragmas["IMPLICIT-ITERATOR"].iterator || "."; + var ctx = {}; + ctx[iterator] = _context + return ctx; + } + }, + + is_object: function(a) { + return a && typeof a == "object"; + }, + + is_array: function(a) { + return Object.prototype.toString.call(a) === '[object Array]'; + }, + + /* + Gets rid of leading and trailing whitespace + */ + trim: function(s) { + return s.replace(/^\s*|\s*$/g, ""); + }, + + /* + Why, why, why? Because IE. Cry, cry cry. + */ + map: function(array, fn) { + if (typeof array.map == "function") { + return array.map(fn) + } else { + var r = []; + var l = array.length; + for(i=0;i + +
+
+

+ We couldn't find enough to build a resume from. Make sure this is the good username :-). Try again with another username? +

+
+ +
+ + diff --git a/views/index.html b/views/index.html new file mode 100644 index 0000000..c6a9a04 --- /dev/null +++ b/views/index.html @@ -0,0 +1,42 @@ +
+ +
+
+

+ As a software startup owner I really enjoy when people send us their + resumes and they include their github account so we can see tangible work they have done. +

+


+

+ After a tweet by John Resig + I imagined that it may be nice for people to be able to generate their Github resumes. +

+


+

+ + +

+
+ +
+ +
+ + diff --git a/views/job.html b/views/job.html new file mode 100644 index 0000000..e86ce52 --- /dev/null +++ b/views/job.html @@ -0,0 +1,9 @@ +
+

{{name}}

+

Creator & Owner

+

{{since}} - {{now}}

+

{{description}}.

+

This repository has {{watchers}} watcher(s) and {{forks}} fork(s). If you would like more information about this repository and my contributed code, please visit the repo on Github.

+
+ + diff --git a/views/repo.html b/views/repo.html new file mode 100644 index 0000000..a9b6e74 --- /dev/null +++ b/views/repo.html @@ -0,0 +1,4 @@ +{{#repo}} +

{{repo}}

+

{{description}}

+{{/repo}} diff --git a/views/resume.html b/views/resume.html new file mode 100644 index 0000000..d0fbc89 --- /dev/null +++ b/views/resume.html @@ -0,0 +1,76 @@ +
+
+ +
+
+
+

{{name}}

+

Passionate github user

+
+ +
+
+

{{email}}

+
+
+
+
+ +
+
+
+ +
+
+

Github Profile

+
+
+

+ I'm a developer + {{#location}} + based in {{location}} + {{/location}} + with {{repos}} public {{plural}}. I've been using github.com since {{since}} + {{#blog}} + and sometimes I blog at {{blog}} + {{/blog}}. +

+
+
+ + +
+
+

My Popular Repositories

+
+ +
+ +
+
+ +
+
+

About This Resume

+
+
+

+ This resume is generated automatically using information from my github account. The repositories are + ordered by popularity based on a very simple popularity heuristic that defines the popularity of a repository + by it's sum of watchers and forks. Do not hesitate to visit my github page + for more information about my repositories and work. +

+
+
+
+
+
+ + + +
+ + +
diff --git a/views/user.html b/views/user.html new file mode 100644 index 0000000..42279f4 --- /dev/null +++ b/views/user.html @@ -0,0 +1,2 @@ +

{{full_name}}

+

Information about this user