{"action":"created","comment":{"url":"https://api.github.com/repos/DockYard/reefpoints/pulls/comments/75344976","id":75344976,"diff_hunk":"@@ -0,0 +1,406 @@\n+---\n+layout: post\n+title: \"The minimum knowledge you need to start Metaprogramming in Elixir\"\n+summary: \"Metaprogramming Elixir\"\n+author: \"Daniel Xu\"\n+twitter: \"Daniel_Xu_For\"\n+github: \"Daniel-Xu\"\n+published: true\n+tags: Elixir\n+---\n+\n+Basically, metaprogramming is `writing code that writes code`. In Elixir, we use `Macro` to transform our internal program structure (AST) in compile time to something else. For example, the `if` macro is transformed to `case` during compilation, we call this `macro expansion`.\n+\n+```elixir\n+if true do\n+  IO.puts \"yea\"\n+end\n+\n+# becomes\n+\n+case(true) do\n+  x when x in [false, nil] ->\n+    nil\n+  _ ->\n+    IO.puts(\"yea\")\n+end\n+```\n+\n+## The Abstract Syntax Tree (AST) and AST literal\n+\n+The internal representation of Elixir code which is called [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) is the protagonist in program transformation. Elixir also calls AST `quoted expression`.\n+\n+The quoted expression is composed of three-element tuples:\n+\n+```elixir\n+# simple one: AST for 1 + 2\n+{:+, [context: Elixir, import: Kernel], [1, 2]}\n+\n+# nested one:\n+# AST for\n+# def hello do\n+#\tIO.puts \"hello\"\n+# end\n+\n+{:def, [context: Elixir, import: Kernel],\n+ [{:hello, [context: Elixir],\n+   [[do: {{:., [], [{:__aliases__, [alias: false], [:IO]}, :puts]}, [],\n+      [\"hello\"]}]]}]}\n+\n+```\n+Essentially, All tuples in the AST follow the same pattern:\n+\n+`{function_call, meta_data_for_context, argument_list}`\n+\n+During compilation, all of our source code will be **transformed into AST** before producing final bytecode. However, there are **five** Elixir literals that will remain the same format as their high-level source.\n+\n+![](http://d.pr/i/1ddgz+)\n+\n+The following example shows the differences between normal type and Elixir literal:\n+\n+```elixir\n+# AST for {1, 2, 3}\n+{:{}, [], [1, 2, 3]}\n+\n+# AST for {1, 2} (AST literal)\n+{1, 2}\n+```\n+\n+## Macro\n+\n+**Macros receive AST as arguments and provide AST as return values**. The returned AST is injected back into the global program’s compile tree, in this way, macros enable syntactic extensions and code generation.\n+\n+* Syntactic extensions: e.g. we can implement `while` which is not available in Elixir or create DSL\n+* Code generation: e.g. generate function from external data\n+\n+### Return AST\n+\n+There are three ways to create quoted expressions in Elixir:\n+\n+1. Manually construct it\n+2. `Macro.escape`\n+3. `quote`/`unquote` to compose AST\n+\n+```elixir\n+defmodule MyMacro do\n+  @opts: %{time: :am}\n+\n+  # case 1\n+  defmacro one_plus_two do\n+ \t{:+, [], [1, 2]}\n+  end\n+\n+  # case 2\n+  defmacro say_hi do\n+    quote do\n+      IO.puts \"hello world\"\n+    end\n+  end\n+\n+  # case 3\n+  defmacro ops do\n+  \tMacro.escape(@opts)\n+  end\n+end\n+\n+defmodule MyModule do\n+  import Mymacro\n+\n+  def case1 do\n+  \tIO.puts one_plus_two()\n+  end\n+\n+  def case2 do\n+  \tsay_hi()\n+  end\n+\n+  def case3 do\n+  \tIO.inspect ops()\n+  end\n+end\n+\n+#=> c \"example.exs\"\n+#=> MyModule.case1()\n+#=> \"3\"\n+#=> MyModule.case2()\n+#=> \"hello world\"\n+#=> MyModule.case3()\n+#=> %{time: :am}\n+```\n+\n+In this example, we define three macros using `defmacro`, both of them return quoted expressions, then we import `MyMacro` module into `MyModule`. During compilation, these macros will be expanded and the returned AST will be injected into `MyModule's` compile tree.\n+\n+When it comes to a complex situation, it will be very hard to construct AST manually, we should use `quote` and `Macro.escape`. The main differences between these two are:\n+\n+* `quote` returns AST of passed in code block.\n+\n+* `Macro.escape` returns AST of passed in value.\n+\n+Here are some examples:\n+\n+```elixir\n+data = {1, 2, 3}\n+\n+quote do: {1, 2, 3}\n+#=> {:{}, [], [1, 2, 3]} (AST of {1, 2, 3})\n+\n+quote do: data\n+#=> {:data, [], Elixir} (data is not inject into returned AST)\n+\n+quote do: IO.inspect(1)\n+#=> {{:., [], [{:__aliases__, [alias: false], [:IO]}, :inspect]}, [], [1]}\n+quote do: IO.inspect(data)\n+#=>{{:., [], [{:__aliases__, [alias: false], [:IO]}, :inspect]}, [],\n+ [{:data, [], Elixir}]}\n+\n+Macro.escape(data)\n+#=> {:{}, [], [1, 2, 3]}\n+\n+IO.inspect(1)\n+|> Macro.escape()\n+#=> :error\n+```\n+Notice that `data` variable is not injected into AST returned by `quote` block, in order to do that, we need to use `unquote` which we will discuss later.\n+\n+\n+### Receive AST\n+\n+Let's take an example to see how macros receive AST:\n+\n+```elixir\n+defmodule M do\n+  defmacro macro_args(a, b) do\n+    IO.inspect a\n+    IO.inspect b\n+  end\n+end\n+\n+#=> c \"example.exs\"\n+{:+, [line: 22], [1, 1]}\n+2\n+```\n+After compiling the module, we can see the results: `{{:+, [line: 22], [1, 1]}}` and `2`, they are both quoted expressions. Remember that number is AST literal so its quoted expressions remains the same as itself.\n+\n+Combining this fact with the pattern of AST, we can easily do pattern matching to get what we want from the argument for further AST composition.\n+\n+Keep in mind that code passed into macro is not evaluated/excuted.\n+\n+### unquote\n+\n+`unquote` injects quoted expressions into AST returned by `quote`. **You can only use `unquote` inside `quote blocks`**.\n+\n+To make it easier to understand, you can think **quote/unquote** as **string interpolation**. When you do `quote`, it's like creating string using `\"\"`. When you do `unquote`, it's like injecting value into string by `\"#{}\"`. However, instead of manipulating string, we are composing AST.\n+\n+There are two types of unquote:\n+\n+* Normal unquote\n+\n+```elixir\n+data = {1, 2, 3}\n+quote do\n+  IO.inspect(unquote(data))\n+end\n+```\n+\n+It looks correct, but when we evaluate the AST, we will get error:\n+\n+![](http://d.pr/i/7adz+)\n+\n+How come? It's because we forget an important concept:\n+> unquote injects AST into AST returned by quote.\n+\n+`{1, 2, 3}` is not AST literal, so we need to get the quoted expressions. first by using `Macro.escape`.\n+\n+```elixir\n+data = {1, 2, 3}\n+quote do\n+  IO.inspect(unquote(Macro.escape(data)))\n+end\n+```\n+\n+* Unquote fragment\n+\n+Unquote fragment is added to support **dynamic generation of functions** and **nested macros**.\n+\n+```\n+defmodule MyModule do\n+  Enum.each [foo: 1, bar: 2, baz: 3], fn { k, v } ->\n+    def unquote(k)(arg) do\n+      unquote(v) + arg\n+    end\n+  end\n+end\n+\n+#=> MyModule.foo(1) #2\n+#=> MyModule.bar(1) #3\n+#=> MyModule.baz(2) #4\n+```\n+In this example, we use `unquote(k)` as function name to generate functions from keys of a Keyworld list.\n+\n+You might wonder why we can use `unquote` without `quote`. It's because `def` is macro, its arguments will be quoted automatically as we discussed above.\n+\n+Besides, we need `quote(v)` inside function body because of scope rule in Elixir:\n+\n+> for named function, any variable coming from the surrounding scope has to be unquoted inside a function clause body.\n+\n+### Bind_quoted\n+\n+`bind_quoted` does two things:\n+\n+* prevent accidental reevaluation of bindings.\n+\n+If we have two same `unquote` inside `quote` block, the `unquote` will be evaluated twice, this can cause problem.\n+We can use `bind_quoted` to fix it:\n+\n+```elixir\n+# bad\n+defmacro my_macro(x) do\n+  quote do\n+  \t IO.inspect unquote(x) * unquote(x)\n+  end\n+end\n+\n+# good\n+defmacro my_macro(x) do\n+  quote bind_quoted: [x: x] do\n+  \t IO.inspect x * x\n+  end\n+end\n+```\n+\n+* Defer the execution of `unquote` via `unquote: false`\n+\n+`unquote: false` is the default behavior of `bind_quoted`.\n+\n+The **order of execution** is:\n+\n+when a macro module is compiled, code in the macro context will run first (`IO.puts 1`). Normal code in `quote` block will not be executed until the returned AST is injected into caller module. However, `unquote` code will \"break the wall\" and run in macro's context.\n+\n+Macro module\n+\n+```elixir\n+defmodule M do\n+  defmacro my_macro(name) do\n+    # macro context\n+    IO.puts 1\n+\n+    quote do\n+      # caller context\n+      IO.puts 4\n+      unquote(IO.puts 2)\n+    end\n+  end\n+end\n+\n+```\n+Caller Module\n+\n+```elixir\n+defmodule Create do\n+  import M\n+  IO.puts 3\n+  my_macro(\"hello\")\n+end\n+```\n+According to the explanation above, we can know the result of the example is: `1 2 3 4`.\n+\n+If we use `bind_quoted` in the example, the order will change. The `unquote` code will be treated as normal code and run in caller's context. Therefore, the result for the following example is: `1 3 4 2`.\n+\n+```elixir\n+defmodule M do\n+  defmacro my_macro(name) do\n+    IO.puts 1\n+\n+    quote bind_quoted: [name: name] do\n+      IO.puts 4\n+\n+      def unquote(name)() do\n+        unquote(IO.puts 2)\n+        IO.puts \"hello #{unquote(name)}\"\n+      end\n+    end\n+  end\n+end\n+```\n+```elixir\n+defmodule Create do\n+  import M\n+  IO.puts 3\n+  my_macro(:hello)\n+end\n+```\n+\n+This is helpful because when we change `my_macro(:hello)` in caller module  to\n+\n+```elixir\n+  [:foo, :bar]\n+  |> Enum.each(&my_macro(&1))\n+```\n+\n+Our code will still work because the `each` function is executed before the injected AST.\n+\n+## How to do experiments","path":"source/posts/2016-08-16-The-minumum-knowledge-you-need-to-start-metaprogramming-in-Elixir.md","position":342,"original_position":342,"commit_id":"9bbed042d126c5f6fe796b5d411ca3825e3d56a4","original_commit_id":"9bbed042d126c5f6fe796b5d411ca3825e3d56a4","user":{"login":"rsocci","id":4259637,"avatar_url":"https://avatars.githubusercontent.com/u/4259637?v=3","gravatar_id":"","url":"https://api.github.com/users/rsocci","html_url":"https://github.com/rsocci","followers_url":"https://api.github.com/users/rsocci/followers","following_url":"https://api.github.com/users/rsocci/following{/other_user}","gists_url":"https://api.github.com/users/rsocci/gists{/gist_id}","starred_url":"https://api.github.com/users/rsocci/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/rsocci/subscriptions","organizations_url":"https://api.github.com/users/rsocci/orgs","repos_url":"https://api.github.com/users/rsocci/repos","events_url":"https://api.github.com/users/rsocci/events{/privacy}","received_events_url":"https://api.github.com/users/rsocci/received_events","type":"User","site_admin":false},"body":"I'd even consider making this \"How to do experiments\" section into a separate blog post","created_at":"2016-08-18T16:49:22Z","updated_at":"2016-08-18T16:49:22Z","html_url":"https://github.com/DockYard/reefpoints/pull/545#discussion_r75344976","pull_request_url":"https://api.github.com/repos/DockYard/reefpoints/pulls/545","_links":{"self":{"href":"https://api.github.com/repos/DockYard/reefpoints/pulls/comments/75344976"},"html":{"href":"https://github.com/DockYard/reefpoints/pull/545#discussion_r75344976"},"pull_request":{"href":"https://api.github.com/repos/DockYard/reefpoints/pulls/545"}}},"pull_request":{"url":"https://api.github.com/repos/DockYard/reefpoints/pulls/545","id":81544436,"html_url":"https://github.com/DockYard/reefpoints/pull/545","diff_url":"https://github.com/DockYard/reefpoints/pull/545.diff","patch_url":"https://github.com/DockYard/reefpoints/pull/545.patch","issue_url":"https://api.github.com/repos/DockYard/reefpoints/issues/545","number":545,"state":"open","locked":false,"title":"metaprogramming in elixir","user":{"login":"Daniel-Xu","id":548144,"avatar_url":"https://avatars.githubusercontent.com/u/548144?v=3","gravatar_id":"","url":"https://api.github.com/users/Daniel-Xu","html_url":"https://github.com/Daniel-Xu","followers_url":"https://api.github.com/users/Daniel-Xu/followers","following_url":"https://api.github.com/users/Daniel-Xu/following{/other_user}","gists_url":"https://api.github.com/users/Daniel-Xu/gists{/gist_id}","starred_url":"https://api.github.com/users/Daniel-Xu/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/Daniel-Xu/subscriptions","organizations_url":"https://api.github.com/users/Daniel-Xu/orgs","repos_url":"https://api.github.com/users/Daniel-Xu/repos","events_url":"https://api.github.com/users/Daniel-Xu/events{/privacy}","received_events_url":"https://api.github.com/users/Daniel-Xu/received_events","type":"User","site_admin":false},"body":"[rendered](https://github.com/DockYard/reefpoints/blob/de7bc3c119000ac1a5a3b87c7c168775d60352ea/source/posts/2016-08-16-The-minumum-knowledge-you-need-to-start-metaprogramming-in-Elixir.md)","created_at":"2016-08-16T21:04:06Z","updated_at":"2016-08-18T16:49:22Z","closed_at":null,"merged_at":null,"merge_commit_sha":"7fde12f8dfa8ef9090bf37e8ac267ea44d4973c6","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/DockYard/reefpoints/pulls/545/commits","review_comments_url":"https://api.github.com/repos/DockYard/reefpoints/pulls/545/comments","review_comment_url":"https://api.github.com/repos/DockYard/reefpoints/pulls/comments{/number}","comments_url":"https://api.github.com/repos/DockYard/reefpoints/issues/545/comments","statuses_url":"https://api.github.com/repos/DockYard/reefpoints/statuses/9bbed042d126c5f6fe796b5d411ca3825e3d56a4","head":{"label":"DockYard:2016-08-16-the-metaprogramming-in-elixir","ref":"2016-08-16-the-metaprogramming-in-elixir","sha":"9bbed042d126c5f6fe796b5d411ca3825e3d56a4","user":{"login":"DockYard","id":873981,"avatar_url":"https://avatars.githubusercontent.com/u/873981?v=3","gravatar_id":"","url":"https://api.github.com/users/DockYard","html_url":"https://github.com/DockYard","followers_url":"https://api.github.com/users/DockYard/followers","following_url":"https://api.github.com/users/DockYard/following{/other_user}","gists_url":"https://api.github.com/users/DockYard/gists{/gist_id}","starred_url":"https://api.github.com/users/DockYard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DockYard/subscriptions","organizations_url":"https://api.github.com/users/DockYard/orgs","repos_url":"https://api.github.com/users/DockYard/repos","events_url":"https://api.github.com/users/DockYard/events{/privacy}","received_events_url":"https://api.github.com/users/DockYard/received_events","type":"Organization","site_admin":false},"repo":{"id":2262874,"name":"reefpoints","full_name":"DockYard/reefpoints","owner":{"login":"DockYard","id":873981,"avatar_url":"https://avatars.githubusercontent.com/u/873981?v=3","gravatar_id":"","url":"https://api.github.com/users/DockYard","html_url":"https://github.com/DockYard","followers_url":"https://api.github.com/users/DockYard/followers","following_url":"https://api.github.com/users/DockYard/following{/other_user}","gists_url":"https://api.github.com/users/DockYard/gists{/gist_id}","starred_url":"https://api.github.com/users/DockYard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DockYard/subscriptions","organizations_url":"https://api.github.com/users/DockYard/orgs","repos_url":"https://api.github.com/users/DockYard/repos","events_url":"https://api.github.com/users/DockYard/events{/privacy}","received_events_url":"https://api.github.com/users/DockYard/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/DockYard/reefpoints","description":"DockYard Blog","fork":false,"url":"https://api.github.com/repos/DockYard/reefpoints","forks_url":"https://api.github.com/repos/DockYard/reefpoints/forks","keys_url":"https://api.github.com/repos/DockYard/reefpoints/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DockYard/reefpoints/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DockYard/reefpoints/teams","hooks_url":"https://api.github.com/repos/DockYard/reefpoints/hooks","issue_events_url":"https://api.github.com/repos/DockYard/reefpoints/issues/events{/number}","events_url":"https://api.github.com/repos/DockYard/reefpoints/events","assignees_url":"https://api.github.com/repos/DockYard/reefpoints/assignees{/user}","branches_url":"https://api.github.com/repos/DockYard/reefpoints/branches{/branch}","tags_url":"https://api.github.com/repos/DockYard/reefpoints/tags","blobs_url":"https://api.github.com/repos/DockYard/reefpoints/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DockYard/reefpoints/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DockYard/reefpoints/git/refs{/sha}","trees_url":"https://api.github.com/repos/DockYard/reefpoints/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DockYard/reefpoints/statuses/{sha}","languages_url":"https://api.github.com/repos/DockYard/reefpoints/languages","stargazers_url":"https://api.github.com/repos/DockYard/reefpoints/stargazers","contributors_url":"https://api.github.com/repos/DockYard/reefpoints/contributors","subscribers_url":"https://api.github.com/repos/DockYard/reefpoints/subscribers","subscription_url":"https://api.github.com/repos/DockYard/reefpoints/subscription","commits_url":"https://api.github.com/repos/DockYard/reefpoints/commits{/sha}","git_commits_url":"https://api.github.com/repos/DockYard/reefpoints/git/commits{/sha}","comments_url":"https://api.github.com/repos/DockYard/reefpoints/comments{/number}","issue_comment_url":"https://api.github.com/repos/DockYard/reefpoints/issues/comments{/number}","contents_url":"https://api.github.com/repos/DockYard/reefpoints/contents/{+path}","compare_url":"https://api.github.com/repos/DockYard/reefpoints/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DockYard/reefpoints/merges","archive_url":"https://api.github.com/repos/DockYard/reefpoints/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DockYard/reefpoints/downloads","issues_url":"https://api.github.com/repos/DockYard/reefpoints/issues{/number}","pulls_url":"https://api.github.com/repos/DockYard/reefpoints/pulls{/number}","milestones_url":"https://api.github.com/repos/DockYard/reefpoints/milestones{/number}","notifications_url":"https://api.github.com/repos/DockYard/reefpoints/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DockYard/reefpoints/labels{/name}","releases_url":"https://api.github.com/repos/DockYard/reefpoints/releases{/id}","deployments_url":"https://api.github.com/repos/DockYard/reefpoints/deployments","created_at":"2011-08-24T16:55:40Z","updated_at":"2016-08-03T01:00:29Z","pushed_at":"2016-08-17T17:28:44Z","git_url":"git://github.com/DockYard/reefpoints.git","ssh_url":"git@github.com:DockYard/reefpoints.git","clone_url":"https://github.com/DockYard/reefpoints.git","svn_url":"https://github.com/DockYard/reefpoints","homepage":"https://dockyard.com/blog","size":23613,"stargazers_count":39,"watchers_count":39,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":64,"mirror_url":null,"open_issues_count":11,"forks":64,"open_issues":11,"watchers":39,"default_branch":"master"}},"base":{"label":"DockYard:master","ref":"master","sha":"817ee07dedc9a19412947db962464b97f80d17cb","user":{"login":"DockYard","id":873981,"avatar_url":"https://avatars.githubusercontent.com/u/873981?v=3","gravatar_id":"","url":"https://api.github.com/users/DockYard","html_url":"https://github.com/DockYard","followers_url":"https://api.github.com/users/DockYard/followers","following_url":"https://api.github.com/users/DockYard/following{/other_user}","gists_url":"https://api.github.com/users/DockYard/gists{/gist_id}","starred_url":"https://api.github.com/users/DockYard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DockYard/subscriptions","organizations_url":"https://api.github.com/users/DockYard/orgs","repos_url":"https://api.github.com/users/DockYard/repos","events_url":"https://api.github.com/users/DockYard/events{/privacy}","received_events_url":"https://api.github.com/users/DockYard/received_events","type":"Organization","site_admin":false},"repo":{"id":2262874,"name":"reefpoints","full_name":"DockYard/reefpoints","owner":{"login":"DockYard","id":873981,"avatar_url":"https://avatars.githubusercontent.com/u/873981?v=3","gravatar_id":"","url":"https://api.github.com/users/DockYard","html_url":"https://github.com/DockYard","followers_url":"https://api.github.com/users/DockYard/followers","following_url":"https://api.github.com/users/DockYard/following{/other_user}","gists_url":"https://api.github.com/users/DockYard/gists{/gist_id}","starred_url":"https://api.github.com/users/DockYard/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/DockYard/subscriptions","organizations_url":"https://api.github.com/users/DockYard/orgs","repos_url":"https://api.github.com/users/DockYard/repos","events_url":"https://api.github.com/users/DockYard/events{/privacy}","received_events_url":"https://api.github.com/users/DockYard/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/DockYard/reefpoints","description":"DockYard Blog","fork":false,"url":"https://api.github.com/repos/DockYard/reefpoints","forks_url":"https://api.github.com/repos/DockYard/reefpoints/forks","keys_url":"https://api.github.com/repos/DockYard/reefpoints/keys{/key_id}","collaborators_url":"https://api.github.com/repos/DockYard/reefpoints/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/DockYard/reefpoints/teams","hooks_url":"https://api.github.com/repos/DockYard/reefpoints/hooks","issue_events_url":"https://api.github.com/repos/DockYard/reefpoints/issues/events{/number}","events_url":"https://api.github.com/repos/DockYard/reefpoints/events","assignees_url":"https://api.github.com/repos/DockYard/reefpoints/assignees{/user}","branches_url":"https://api.github.com/repos/DockYard/reefpoints/branches{/branch}","tags_url":"https://api.github.com/repos/DockYard/reefpoints/tags","blobs_url":"https://api.github.com/repos/DockYard/reefpoints/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/DockYard/reefpoints/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/DockYard/reefpoints/git/refs{/sha}","trees_url":"https://api.github.com/repos/DockYard/reefpoints/git/trees{/sha}","statuses_url":"https://api.github.com/repos/DockYard/reefpoints/statuses/{sha}","languages_url":"https://api.github.com/repos/DockYard/reefpoints/languages","stargazers_url":"https://api.github.com/repos/DockYard/reefpoints/stargazers","contributors_url":"https://api.github.com/repos/DockYard/reefpoints/contributors","subscribers_url":"https://api.github.com/repos/DockYard/reefpoints/subscribers","subscription_url":"https://api.github.com/repos/DockYard/reefpoints/subscription","commits_url":"https://api.github.com/repos/DockYard/reefpoints/commits{/sha}","git_commits_url":"https://api.github.com/repos/DockYard/reefpoints/git/commits{/sha}","comments_url":"https://api.github.com/repos/DockYard/reefpoints/comments{/number}","issue_comment_url":"https://api.github.com/repos/DockYard/reefpoints/issues/comments{/number}","contents_url":"https://api.github.com/repos/DockYard/reefpoints/contents/{+path}","compare_url":"https://api.github.com/repos/DockYard/reefpoints/compare/{base}...{head}","merges_url":"https://api.github.com/repos/DockYard/reefpoints/merges","archive_url":"https://api.github.com/repos/DockYard/reefpoints/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/DockYard/reefpoints/downloads","issues_url":"https://api.github.com/repos/DockYard/reefpoints/issues{/number}","pulls_url":"https://api.github.com/repos/DockYard/reefpoints/pulls{/number}","milestones_url":"https://api.github.com/repos/DockYard/reefpoints/milestones{/number}","notifications_url":"https://api.github.com/repos/DockYard/reefpoints/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/DockYard/reefpoints/labels{/name}","releases_url":"https://api.github.com/repos/DockYard/reefpoints/releases{/id}","deployments_url":"https://api.github.com/repos/DockYard/reefpoints/deployments","created_at":"2011-08-24T16:55:40Z","updated_at":"2016-08-03T01:00:29Z","pushed_at":"2016-08-17T17:28:44Z","git_url":"git://github.com/DockYard/reefpoints.git","ssh_url":"git@github.com:DockYard/reefpoints.git","clone_url":"https://github.com/DockYard/reefpoints.git","svn_url":"https://github.com/DockYard/reefpoints","homepage":"https://dockyard.com/blog","size":23613,"stargazers_count":39,"watchers_count":39,"language":"CSS","has_issues":true,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":64,"mirror_url":null,"open_issues_count":11,"forks":64,"open_issues":11,"watchers":39,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/DockYard/reefpoints/pulls/545"},"html":{"href":"https://github.com/DockYard/reefpoints/pull/545"},"issue":{"href":"https://api.github.com/repos/DockYard/reefpoints/issues/545"},"comments":{"href":"https://api.github.com/repos/DockYard/reefpoints/issues/545/comments"},"review_comments":{"href":"https://api.github.com/repos/DockYard/reefpoints/pulls/545/comments"},"review_comment":{"href":"https://api.github.com/repos/DockYard/reefpoints/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/DockYard/reefpoints/pulls/545/commits"},"statuses":{"href":"https://api.github.com/repos/DockYard/reefpoints/statuses/9bbed042d126c5f6fe796b5d411ca3825e3d56a4"}}}}
{"actor":{"display_login":"rsocci"}}