GitHub PullRequestReviewCommentEvent at 2014-12-30 18:28:41
Repository: jarofghosts.github.io
Actor: jarofghosts
Payload: {"action":"created","comment":{"url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/comments/22358230","id":22358230,"diff_hunk":"@@ -0,0 +1,251 @@\n+---\n+layout: post\n+title:  \"Front-end Streams\"\n+date:   2014-12-28 17:56:45\n+description: Streams all up in your browser window\n+categories:\n+- blog\n+permalink: front-end-streams\n+---\n+[Streams](http://nodejs.org/api/stream.html) are infectious. They are, to date,\n+my favorite abstraction for dealing with large amounts of data over time.\n+\n+They force you to think about big problems in small pieces, and present a handy\n+composable interface to allow communication with one another via `.pipe()`.\n+\n+A big problem that I deal with every day is that of working with a large amount\n+of dynamic user input over time in the form of DOM events. That, to me,\n+indicates that streams are a great fit for modeling web applications!\n+\n+Thinking about your application in terms of streams and pipelines of data\n+provides you with a lot of benefits.\n+\n+1. The pieces that your system consists of (the streaming modules) tend to be\n+much smaller and simpler. These properties tend to make them more testable,\n+more reusable, easier to reason about, and less prone to bugs.\n+2. The code that glues the pieces together as pipelines tends to be very\n+expressive. `a.pipe(b).pipe(c)` is a very powerful statement! Not only is it\n+very easy to write, it is also very easy to read.\n+3. Your system is talking [POJOs](http://odetocode.com/blogs/scott/archive/2012/02/27/plain-old-javascript.aspx)\n+at its deepest levels. This means that stepping through code and introspecting\n+data is much easier and more intuitive. It also means that you are free to\n+introduce *any* libraries or modules into your \"stack\".\n+\n+For a more in-depth analysis, see\n+[The Stream Handbook](https://github.com/substack/stream-handbook)\n+\n+## An Ideal World\n+\n+Imagine you want to take what a user types into an input element and display it\n+in an output element. All we have to do is think of these things in terms of\n+readable and writable streams and suddenly it's very trivial!\n+\n+Let's imagine the simplest way to write that:\n+\n+```javascript\n+// pseudo-code incoming!\n+\n+inputElement.pipe(outputElement)\n+```\n+\n+The good news is that this ideal world is not much different from the world\n+that you can very easily be living within!\n+\n+## How\n+\n+Browserify enables us to have [node streams](http://nodejs.org/api/stream.html)\n+in the browser. This means that not only do we get stream primitives for cheap,\n+but we also get the *wealth* of streaming modules on\n+[npm](https://www.npmjs.org) that Just Work(TM), because they deal simply with\n+isolated forms of data.\n+\n+Generally, we use [through](http://npm.im/through) for stream construction. It\n+presents a nice, functional interface that is very intuitive.\n+\n+For more specific DOM tasks, we use speciality modules, such as:\n+\n+* [dom-delegation-stream](http://npm.im/dom-delegation-stream)\n+* [dom-value-stream](http://npm.im/dom-value-stream)\n+* [dom-replace-html-stream](http://npm.im/dom-replace-html-stream)\n+\n+Another module we lean heavily upon for aggregating and contextualizing streams\n+(and event emitters in general) is [ObjectState](http://npm.im/objectstate).\n+\n+If we want to use something in a pipeline that doesn't provide a streaming\n+interface, we simply wrap it with one!\n+\n+## Examples\n+\n+```javascript\n+var write = require('dom-replace-html-stream')\n+  , events = require('dom-delegation-stream')\n+  , values = require('dom-value-stream')\n+\n+var input = events(document.getElementById('input'), 'input').pipe(values())\n+var output = write(document.getElementById('output'))\n+\n+input.pipe(output)\n+```\n+\n+Let's take a look at what is happening here. \n+\n+1. We require all the modules that we are going to use.\n+2. We set up a first pipeline consisting of:\n+    - 'input' events triggered by the element with ID 'input'\n+    - the value of the element that triggered the original event\n+3. We create a writable stream to the element with ID 'output'\n+4. We create a new pipeline from the \"input\" to the \"output\"\n+5. Magic\n+\n+### Introducing \"ObjectState\"\n+\n+Now, let's imagine that you want to actually collect this data somewhere to be\n+used later, perhaps to send off to an API. This is where\n+[ObjectState](http://npm.im/objectstate) comes in! Check this out:\n+\n+```javascript\n+var objectState = require('objectstate')\n+\n+var state = objectState()\n+\n+// ...the previous example happens...\n+\n+state.listen(input, 'userInput')\n+```\n+\n+Now, as you enter text into the input, ObjectState is collecting the emissions\n+into a state object like:\n+\n+```javascript\n+{\n+    userInput: 'whatever you typed!'\n+}\n+```\n+\n+The best part is that ObjectState is itself a stream! Which means that you can\n+pipe it anywhere and it will happily emit its \"state objects\" anywhere you\n+desire. This gives you a very simple and powerful way to compose streams of\n+data into meaningful chunks of data and act upon them accordingly.\n+\n+### Putting it all together\n+\n+Let's make a slightly more advanced (if still somewhat contrived) system. Say\n+you want to create a module that lets the user input an ID number into an input\n+and have it rendered as it might appear on an ID card.\n+\n+For this example, we will want to use a template engine. At Urban Airship, we\n+use [Ractive](http://www.ractivejs.org/). As mentioned earlier, because it does\n+not expose a streaming interface, we wrap it in one. That wrapper looks very\n+much like:\n+\n+```javascript\n+var Ractive = require('ractive')\n+  , through = require('through')\n+\n+module.exports = ractiveStream\n+\n+function ractiveStream(el, template, _options) {\n+  var options = _options || {}\n+  var stream = through(write)\n+\n+  options.el = el\n+  options.template = template\n+\n+  var ractive = new Ractive(options)\n+\n+  stream.view = ractive\n+\n+  return stream\n+\n+  function write(data) {\n+    ractive.reset(data)\n+  }\n+}\n+```\n+\n+Pretty straight-forward, but let's take a quick look.\n+\n+* We expose a simple API that meets our needs (give an element and a template,\n+  optionally direct Ractive options, return a stream).\n+* We provide access to the Ractive instance via the `.view` property of the\n+  returned stream.\n+* Whenever our stream is written to, it resets the context of the Ractive\n+  instance.\n+\n+Now, let's build our theoretical widget! We can assume that it will be handed a\n+DOM element to render itself into.\n+\n+Let's start with the template\n+\n+```html\n+<div class=\"card-input\">\n+  <input type=\"text\" name=\"full-name\"/>\n+  <input type=\"text\" name=\"card-number\"/>\n+</div>\n+<div class=\"card-output\">\n+  <div class=\"card-template\">\n+    <span class=\"card-number\">{{ cardNumber }}</span>\n+    <span class=\"card-name\">{{ fullName }}</span>\n+  </div>\n+</div>\n+```\n+\n+Simple enough, now for the actual code:\n+\n+```javascript\n+var events = require('dom-delegation-stream')\n+  , values = require('dom-value-stream')\n+  , objectState = require('objectstate')\n+\n+var ractiveStream = require('./ractive-stream')\n+  , cardTemplate = require('./template.ract') // check out the ractify transform\n+  , stripNonDigits = require('./strip-non-digits')\n+  , addDashes = require('./add-dashes')\n+\n+module.exports = cardWidget\n+\n+function cardWidget(el) {\n+  var ractive = ractiveStream(el, cardTemplate)\n+    , state = objectState()\n+\n+  var name = events(el, 'input', '[name=full-name]')\n+    .pipe(values())\n+\n+  var cardNumber = events(el, 'input', '[name=card-number]')\n+    .pipe(values())\n+    .pipe(stripNonDigits())\n+    .pipe(addDashes())\n+\n+  state.listen(name, 'fullName')\n+    .listen(cardNumber, 'cardNumber')\n+\n+  state.pipe(ractive)\n+}\n+```\n+\n+Let's break it down:\n+\n+* We require all of the modules we will be using, including a couple of\n+  theoretical but trivially-implemented modules.\n+  - \"strip-non-digits\" just removes all non-digit characters from a string.\n+  - \"add-dashes\" inserts dashes into a string in a pattern like you might\n+    expect on an ID card.\n+* We declare our export, which is the function that constructs this \"widget\"\n+* Our function takes an element, and immediately creates a ractiveStream with\n+  that element and our template.\n+* We initialize an ObjectState to serve as our source of context.\n+* We create two pipelines.\n+  - They both start as events -> values, effectively turning input keystrokes\n+    into the value of the element they were triggered from.\n+  - The `cardNumber` stream is further piped into our \"add-spaces\" module for\n+    formatting.\n+* Our objectState is told to listen to these streams, and consider them as keys\n+  \"fullName\" and \"cardNumber\" respectively.\n+* We pipe our objectState directly into our ractiveStream to be used as context.\n+\n+## In Conclusion\n+\n+Streams are an undeniably powerful concept. They enforce a separation of\n+concerns and provide handy mechanisms for composition. Hopefully, I have made\n+clear how you can leverage them for making your front-end code simpler and\n+more portable!","path":"_posts/2014-12-28-front-end-streams.md","position":251,"original_position":251,"commit_id":"4732cab192c52b7353b004c53fba850bf959fec6","original_commit_id":"4732cab192c52b7353b004c53fba850bf959fec6","user":{"login":"jarofghosts","id":1013892,"avatar_url":"https://avatars.githubusercontent.com/u/1013892?v=3","gravatar_id":"","url":"https://api.github.com/users/jarofghosts","html_url":"https://github.com/jarofghosts","followers_url":"https://api.github.com/users/jarofghosts/followers","following_url":"https://api.github.com/users/jarofghosts/following{/other_user}","gists_url":"https://api.github.com/users/jarofghosts/gists{/gist_id}","starred_url":"https://api.github.com/users/jarofghosts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jarofghosts/subscriptions","organizations_url":"https://api.github.com/users/jarofghosts/orgs","repos_url":"https://api.github.com/users/jarofghosts/repos","events_url":"https://api.github.com/users/jarofghosts/events{/privacy}","received_events_url":"https://api.github.com/users/jarofghosts/received_events","type":"User","site_admin":false},"body":"i was planning on writing a follow up that discusses architecting larger systems using these concepts, perhaps i could just provide a spoiler that i am planning to do that?","created_at":"2014-12-30T18:28:41Z","updated_at":"2014-12-30T18:28:41Z","html_url":"https://github.com/jarofghosts/jarofghosts.github.io/pull/1#discussion_r22358230","pull_request_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/1","_links":{"self":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/comments/22358230"},"html":{"href":"https://github.com/jarofghosts/jarofghosts.github.io/pull/1#discussion_r22358230"},"pull_request":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/1"}}},"pull_request":{"url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/1","id":26628901,"html_url":"https://github.com/jarofghosts/jarofghosts.github.io/pull/1","diff_url":"https://github.com/jarofghosts/jarofghosts.github.io/pull/1.diff","patch_url":"https://github.com/jarofghosts/jarofghosts.github.io/pull/1.patch","issue_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues/1","number":1,"state":"open","locked":false,"title":"add front-end streams blog entry","user":{"login":"jarofghosts","id":1013892,"avatar_url":"https://avatars.githubusercontent.com/u/1013892?v=3","gravatar_id":"","url":"https://api.github.com/users/jarofghosts","html_url":"https://github.com/jarofghosts","followers_url":"https://api.github.com/users/jarofghosts/followers","following_url":"https://api.github.com/users/jarofghosts/following{/other_user}","gists_url":"https://api.github.com/users/jarofghosts/gists{/gist_id}","starred_url":"https://api.github.com/users/jarofghosts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jarofghosts/subscriptions","organizations_url":"https://api.github.com/users/jarofghosts/orgs","repos_url":"https://api.github.com/users/jarofghosts/repos","events_url":"https://api.github.com/users/jarofghosts/events{/privacy}","received_events_url":"https://api.github.com/users/jarofghosts/received_events","type":"User","site_admin":false},"body":"![weeeee](http://24.media.tumblr.com/1877bcca2cf5df5c129152d2dbbdfbb4/tumblr_mh7gxafAjc1qdlh1io1_400.gif)","created_at":"2014-12-28T23:28:36Z","updated_at":"2014-12-30T18:28:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"8603e6a0f77ddae82ae24fae3a19e7686970a4a2","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/1/commits","review_comments_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/1/comments","review_comment_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/comments/{number}","comments_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues/1/comments","statuses_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/statuses/4732cab192c52b7353b004c53fba850bf959fec6","head":{"label":"jarofghosts:front-end-streams","ref":"front-end-streams","sha":"4732cab192c52b7353b004c53fba850bf959fec6","user":{"login":"jarofghosts","id":1013892,"avatar_url":"https://avatars.githubusercontent.com/u/1013892?v=3","gravatar_id":"","url":"https://api.github.com/users/jarofghosts","html_url":"https://github.com/jarofghosts","followers_url":"https://api.github.com/users/jarofghosts/followers","following_url":"https://api.github.com/users/jarofghosts/following{/other_user}","gists_url":"https://api.github.com/users/jarofghosts/gists{/gist_id}","starred_url":"https://api.github.com/users/jarofghosts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jarofghosts/subscriptions","organizations_url":"https://api.github.com/users/jarofghosts/orgs","repos_url":"https://api.github.com/users/jarofghosts/repos","events_url":"https://api.github.com/users/jarofghosts/events{/privacy}","received_events_url":"https://api.github.com/users/jarofghosts/received_events","type":"User","site_admin":false},"repo":{"id":27293744,"name":"jarofghosts.github.io","full_name":"jarofghosts/jarofghosts.github.io","owner":{"login":"jarofghosts","id":1013892,"avatar_url":"https://avatars.githubusercontent.com/u/1013892?v=3","gravatar_id":"","url":"https://api.github.com/users/jarofghosts","html_url":"https://github.com/jarofghosts","followers_url":"https://api.github.com/users/jarofghosts/followers","following_url":"https://api.github.com/users/jarofghosts/following{/other_user}","gists_url":"https://api.github.com/users/jarofghosts/gists{/gist_id}","starred_url":"https://api.github.com/users/jarofghosts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jarofghosts/subscriptions","organizations_url":"https://api.github.com/users/jarofghosts/orgs","repos_url":"https://api.github.com/users/jarofghosts/repos","events_url":"https://api.github.com/users/jarofghosts/events{/privacy}","received_events_url":"https://api.github.com/users/jarofghosts/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jarofghosts/jarofghosts.github.io","description":"A responsive Jekyll theme.","fork":true,"url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io","forks_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/forks","keys_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/teams","hooks_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/hooks","issue_events_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/events","assignees_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/tags","blobs_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/languages","stargazers_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/stargazers","contributors_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/contributors","subscribers_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/subscribers","subscription_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/subscription","commits_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues/comments/{number}","contents_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/merges","archive_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/downloads","issues_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/labels{/name}","releases_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/releases{/id}","created_at":"2014-11-29T06:34:43Z","updated_at":"2014-12-30T04:32:00Z","pushed_at":"2014-12-29T23:54:04Z","git_url":"git://github.com/jarofghosts/jarofghosts.github.io.git","ssh_url":"git@github.com:jarofghosts/jarofghosts.github.io.git","clone_url":"https://github.com/jarofghosts/jarofghosts.github.io.git","svn_url":"https://github.com/jarofghosts/jarofghosts.github.io","homepage":"http://gayan.me/harmony","size":864,"stargazers_count":2,"watchers_count":2,"language":"CSS","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":2,"default_branch":"master"}},"base":{"label":"jarofghosts:master","ref":"master","sha":"8778e7c971ebdaa9c73440182c74ad23c618e092","user":{"login":"jarofghosts","id":1013892,"avatar_url":"https://avatars.githubusercontent.com/u/1013892?v=3","gravatar_id":"","url":"https://api.github.com/users/jarofghosts","html_url":"https://github.com/jarofghosts","followers_url":"https://api.github.com/users/jarofghosts/followers","following_url":"https://api.github.com/users/jarofghosts/following{/other_user}","gists_url":"https://api.github.com/users/jarofghosts/gists{/gist_id}","starred_url":"https://api.github.com/users/jarofghosts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jarofghosts/subscriptions","organizations_url":"https://api.github.com/users/jarofghosts/orgs","repos_url":"https://api.github.com/users/jarofghosts/repos","events_url":"https://api.github.com/users/jarofghosts/events{/privacy}","received_events_url":"https://api.github.com/users/jarofghosts/received_events","type":"User","site_admin":false},"repo":{"id":27293744,"name":"jarofghosts.github.io","full_name":"jarofghosts/jarofghosts.github.io","owner":{"login":"jarofghosts","id":1013892,"avatar_url":"https://avatars.githubusercontent.com/u/1013892?v=3","gravatar_id":"","url":"https://api.github.com/users/jarofghosts","html_url":"https://github.com/jarofghosts","followers_url":"https://api.github.com/users/jarofghosts/followers","following_url":"https://api.github.com/users/jarofghosts/following{/other_user}","gists_url":"https://api.github.com/users/jarofghosts/gists{/gist_id}","starred_url":"https://api.github.com/users/jarofghosts/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/jarofghosts/subscriptions","organizations_url":"https://api.github.com/users/jarofghosts/orgs","repos_url":"https://api.github.com/users/jarofghosts/repos","events_url":"https://api.github.com/users/jarofghosts/events{/privacy}","received_events_url":"https://api.github.com/users/jarofghosts/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/jarofghosts/jarofghosts.github.io","description":"A responsive Jekyll theme.","fork":true,"url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io","forks_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/forks","keys_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/keys{/key_id}","collaborators_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/teams","hooks_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/hooks","issue_events_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues/events{/number}","events_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/events","assignees_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/assignees{/user}","branches_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/branches{/branch}","tags_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/tags","blobs_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/refs{/sha}","trees_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/trees{/sha}","statuses_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/statuses/{sha}","languages_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/languages","stargazers_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/stargazers","contributors_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/contributors","subscribers_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/subscribers","subscription_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/subscription","commits_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/commits{/sha}","git_commits_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/git/commits{/sha}","comments_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/comments{/number}","issue_comment_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues/comments/{number}","contents_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/contents/{+path}","compare_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/compare/{base}...{head}","merges_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/merges","archive_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/downloads","issues_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues{/number}","pulls_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls{/number}","milestones_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/milestones{/number}","notifications_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/labels{/name}","releases_url":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/releases{/id}","created_at":"2014-11-29T06:34:43Z","updated_at":"2014-12-30T04:32:00Z","pushed_at":"2014-12-29T23:54:04Z","git_url":"git://github.com/jarofghosts/jarofghosts.github.io.git","ssh_url":"git@github.com:jarofghosts/jarofghosts.github.io.git","clone_url":"https://github.com/jarofghosts/jarofghosts.github.io.git","svn_url":"https://github.com/jarofghosts/jarofghosts.github.io","homepage":"http://gayan.me/harmony","size":864,"stargazers_count":2,"watchers_count":2,"language":"CSS","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":true,"forks_count":0,"mirror_url":null,"open_issues_count":1,"forks":0,"open_issues":1,"watchers":2,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/1"},"html":{"href":"https://github.com/jarofghosts/jarofghosts.github.io/pull/1"},"issue":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues/1"},"comments":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/issues/1/comments"},"review_comments":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/1/comments"},"review_comment":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/comments/{number}"},"commits":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/pulls/1/commits"},"statuses":{"href":"https://api.github.com/repos/jarofghosts/jarofghosts.github.io/statuses/4732cab192c52b7353b004c53fba850bf959fec6"}}}}