{"action":"created","comment":{"url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments/28164769","id":28164769,"diff_hunk":"@@ -0,0 +1,94 @@\n+defmodule Phoenix.Digester do\n+  @digested_file_regex ~r/(-[a-fA-F\\d]{32})/\n+\n+  @moduledoc \"\"\"\n+    Digests and compress assets files.\n+\n+    For each file under the given input path, Phoenix will generate a digest\n+    and also compress in `.gz` format. The filename and its digest will be\n+    used to generate the manifest file. It also avoid duplications checking\n+    for already digested files.\n+  \"\"\"\n+\n+  @doc \"\"\"\n+  Compile the assets and save them in the given output path.\n+\n+    * `input_path` - The path where the assets are located\n+    * `output_path` - The path where the compiled/compressed files will be saved\n+  \"\"\"\n+  @spec compile(String.t, String.t) :: :ok | {:error, :invalid_path}\n+  def compile(input_path, output_path) do\n+    if File.exists?(input_path) && File.exists?(output_path) do\n+      input_path\n+      |> filter_files\n+      |> do_compile(output_path)\n+      |> generate_manifest(output_path)\n+      :ok\n+    else\n+      {:error, :invalid_path}\n+    end\n+  end\n+\n+  defp filter_files(input_path) do\n+    Path.wildcard(Path.join(input_path, \"**\"))\n+    |> Enum.filter(&(!File.dir?(&1) && !compiled_file?(&1)))\n+    |> Enum.map(&(map_file(&1, input_path)))\n+  end\n+\n+  defp do_compile(files, output_path) do\n+    Enum.map(files, fn (file) ->\n+      file\n+      |> digest\n+      |> compress\n+      |> write_to_disk(output_path)\n+    end)\n+  end\n+\n+  defp generate_manifest(files, output_path) do\n+    entries = Enum.reduce(files, %{}, fn (file, acc) ->\n+      Map.put(acc, Path.join(file.relative_path, file.filename),\n+        Path.join(file.relative_path, file.digested_filename))\n+    end)\n+\n+    manifest_content = Poison.Encoder.encode(entries, [])\n+    File.write!(Path.join(output_path, \"manifest.json\"), manifest_content)\n+  end\n+\n+  defp compiled_file?(file_path) do\n+    Regex.match?(@digested_file_regex, Path.basename(file_path)) ||\n+      Path.extname(file_path) == \".gz\"\n+  end\n+\n+  defp map_file(file_path, input_path) do\n+    %{}","path":"lib/phoenix/digester.ex","position":63,"original_position":63,"commit_id":"5b42d43584e3bba4e8a6ef82a47dce8a8213acaa","original_commit_id":"5b42d43584e3bba4e8a6ef82a47dce8a8213acaa","user":{"login":"josevalim","id":9582,"avatar_url":"https://avatars.githubusercontent.com/u/9582?v=3","gravatar_id":"","url":"https://api.github.com/users/josevalim","html_url":"https://github.com/josevalim","followers_url":"https://api.github.com/users/josevalim/followers","following_url":"https://api.github.com/users/josevalim/following{/other_user}","gists_url":"https://api.github.com/users/josevalim/gists{/gist_id}","starred_url":"https://api.github.com/users/josevalim/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/josevalim/subscriptions","organizations_url":"https://api.github.com/users/josevalim/orgs","repos_url":"https://api.github.com/users/josevalim/repos","events_url":"https://api.github.com/users/josevalim/events{/privacy}","received_events_url":"https://api.github.com/users/josevalim/received_events","type":"User","site_admin":false},"body":"What about:\r\n\r\n```elixir\r\n%{absolute_path: file_path,\r\n   relative_path: ...,\r\n   ...}\r\n```\r\n\r\nbe cleaner?","created_at":"2015-04-10T17:41:02Z","updated_at":"2015-04-10T17:41:02Z","html_url":"https://github.com/phoenixframework/phoenix/pull/758#discussion_r28164769","pull_request_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/758","_links":{"self":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments/28164769"},"html":{"href":"https://github.com/phoenixframework/phoenix/pull/758#discussion_r28164769"},"pull_request":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/758"}}},"pull_request":{"url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/758","id":33000640,"html_url":"https://github.com/phoenixframework/phoenix/pull/758","diff_url":"https://github.com/phoenixframework/phoenix/pull/758.diff","patch_url":"https://github.com/phoenixframework/phoenix/pull/758.patch","issue_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/758","number":758,"state":"open","locked":false,"title":"Introduce the digest task","user":{"login":"laurocaetano","id":611891,"avatar_url":"https://avatars.githubusercontent.com/u/611891?v=3","gravatar_id":"","url":"https://api.github.com/users/laurocaetano","html_url":"https://github.com/laurocaetano","followers_url":"https://api.github.com/users/laurocaetano/followers","following_url":"https://api.github.com/users/laurocaetano/following{/other_user}","gists_url":"https://api.github.com/users/laurocaetano/gists{/gist_id}","starred_url":"https://api.github.com/users/laurocaetano/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/laurocaetano/subscriptions","organizations_url":"https://api.github.com/users/laurocaetano/orgs","repos_url":"https://api.github.com/users/laurocaetano/repos","events_url":"https://api.github.com/users/laurocaetano/events{/privacy}","received_events_url":"https://api.github.com/users/laurocaetano/received_events","type":"User","site_admin":false},"body":"This task is responsible for digesting and compressing the assets files as described at #735.\r\n\r\nI used the [discourse's](https://github.com/discourse/discourse) assets to make a benchmark since it has ~ 991 assets files. The results were pretty nice.\r\n\r\n```\r\nphoenix (introduce-digest-task) $ time mix phoenix.digest /Users/laurocaetano/Desktop/discourse/app/assets -o /tmp/assets\r\nCheck your digested files at '/tmp/assets'\r\n\r\nreal\t0m2.446s\r\nuser\t0m1.926s\r\nsys\t0m1.290s\r\n```\r\n\r\nThis task will read the input/output path and proceed as following:\r\n  * Filter for files (excluding directories)\r\n  * Get the file digest\r\n  * Compress the file\r\n  * Save the files to disk\r\n  * Generate the manifest\r\n\r\nIt also takes account the relative path when saving the files to the output path, see:\r\n\r\n```\r\n   |-fonts\r\n   |-images\r\n   |---favicons\r\n   |-javascripts\r\n   |---admin\r\n   |-----components\r\n   |-----controllers\r\n   |-------modals\r\n   |-----helpers\r\n   |-----models\r\n   |-----routes\r\n   |-----templates\r\n   |-------components\r\n   |-------logs\r\n   |-------modal\r\n   |-------reports\r\n   |-------site-settings\r\n   |-----views\r\n   |-------logs\r\n   |-------modals\r\n   |---defer\r\n   |---discourse\r\n#....\r\n```\r\nand the `manifest.json` will look like:\r\n\r\n```json\r\n   {\r\n     \"./phoenix.png\" : \"./phoenix-5bd99a0d17dd41bc9d9bf6840abcc089.png\",\r\n     \"./phoenix.js\" : \"./phoenix-4d4e132be213566829e0f28686fc04a0.js\"\r\n   }\r\n```\r\n\r\nThoughts? \r\n\r\nThanks @josevalim for helping :heart: ","created_at":"2015-04-09T21:59:21Z","updated_at":"2015-04-10T17:41:02Z","closed_at":null,"merged_at":null,"merge_commit_sha":"f5cc2ca5cd41ceeb83bc7f2729cfa27017231ccb","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/758/commits","review_comments_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/758/comments","review_comment_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments{/number}","comments_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/758/comments","statuses_url":"https://api.github.com/repos/phoenixframework/phoenix/statuses/5b42d43584e3bba4e8a6ef82a47dce8a8213acaa","head":{"label":"laurocaetano:introduce-digest-task","ref":"introduce-digest-task","sha":"5b42d43584e3bba4e8a6ef82a47dce8a8213acaa","user":{"login":"laurocaetano","id":611891,"avatar_url":"https://avatars.githubusercontent.com/u/611891?v=3","gravatar_id":"","url":"https://api.github.com/users/laurocaetano","html_url":"https://github.com/laurocaetano","followers_url":"https://api.github.com/users/laurocaetano/followers","following_url":"https://api.github.com/users/laurocaetano/following{/other_user}","gists_url":"https://api.github.com/users/laurocaetano/gists{/gist_id}","starred_url":"https://api.github.com/users/laurocaetano/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/laurocaetano/subscriptions","organizations_url":"https://api.github.com/users/laurocaetano/orgs","repos_url":"https://api.github.com/users/laurocaetano/repos","events_url":"https://api.github.com/users/laurocaetano/events{/privacy}","received_events_url":"https://api.github.com/users/laurocaetano/received_events","type":"User","site_admin":false},"repo":{"id":32942574,"name":"phoenix","full_name":"laurocaetano/phoenix","owner":{"login":"laurocaetano","id":611891,"avatar_url":"https://avatars.githubusercontent.com/u/611891?v=3","gravatar_id":"","url":"https://api.github.com/users/laurocaetano","html_url":"https://github.com/laurocaetano","followers_url":"https://api.github.com/users/laurocaetano/followers","following_url":"https://api.github.com/users/laurocaetano/following{/other_user}","gists_url":"https://api.github.com/users/laurocaetano/gists{/gist_id}","starred_url":"https://api.github.com/users/laurocaetano/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/laurocaetano/subscriptions","organizations_url":"https://api.github.com/users/laurocaetano/orgs","repos_url":"https://api.github.com/users/laurocaetano/repos","events_url":"https://api.github.com/users/laurocaetano/events{/privacy}","received_events_url":"https://api.github.com/users/laurocaetano/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/laurocaetano/phoenix","description":"A highly connected web framework for the Elixir programming language","fork":true,"url":"https://api.github.com/repos/laurocaetano/phoenix","forks_url":"https://api.github.com/repos/laurocaetano/phoenix/forks","keys_url":"https://api.github.com/repos/laurocaetano/phoenix/keys{/key_id}","collaborators_url":"https://api.github.com/repos/laurocaetano/phoenix/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/laurocaetano/phoenix/teams","hooks_url":"https://api.github.com/repos/laurocaetano/phoenix/hooks","issue_events_url":"https://api.github.com/repos/laurocaetano/phoenix/issues/events{/number}","events_url":"https://api.github.com/repos/laurocaetano/phoenix/events","assignees_url":"https://api.github.com/repos/laurocaetano/phoenix/assignees{/user}","branches_url":"https://api.github.com/repos/laurocaetano/phoenix/branches{/branch}","tags_url":"https://api.github.com/repos/laurocaetano/phoenix/tags","blobs_url":"https://api.github.com/repos/laurocaetano/phoenix/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/laurocaetano/phoenix/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/laurocaetano/phoenix/git/refs{/sha}","trees_url":"https://api.github.com/repos/laurocaetano/phoenix/git/trees{/sha}","statuses_url":"https://api.github.com/repos/laurocaetano/phoenix/statuses/{sha}","languages_url":"https://api.github.com/repos/laurocaetano/phoenix/languages","stargazers_url":"https://api.github.com/repos/laurocaetano/phoenix/stargazers","contributors_url":"https://api.github.com/repos/laurocaetano/phoenix/contributors","subscribers_url":"https://api.github.com/repos/laurocaetano/phoenix/subscribers","subscription_url":"https://api.github.com/repos/laurocaetano/phoenix/subscription","commits_url":"https://api.github.com/repos/laurocaetano/phoenix/commits{/sha}","git_commits_url":"https://api.github.com/repos/laurocaetano/phoenix/git/commits{/sha}","comments_url":"https://api.github.com/repos/laurocaetano/phoenix/comments{/number}","issue_comment_url":"https://api.github.com/repos/laurocaetano/phoenix/issues/comments{/number}","contents_url":"https://api.github.com/repos/laurocaetano/phoenix/contents/{+path}","compare_url":"https://api.github.com/repos/laurocaetano/phoenix/compare/{base}...{head}","merges_url":"https://api.github.com/repos/laurocaetano/phoenix/merges","archive_url":"https://api.github.com/repos/laurocaetano/phoenix/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/laurocaetano/phoenix/downloads","issues_url":"https://api.github.com/repos/laurocaetano/phoenix/issues{/number}","pulls_url":"https://api.github.com/repos/laurocaetano/phoenix/pulls{/number}","milestones_url":"https://api.github.com/repos/laurocaetano/phoenix/milestones{/number}","notifications_url":"https://api.github.com/repos/laurocaetano/phoenix/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/laurocaetano/phoenix/labels{/name}","releases_url":"https://api.github.com/repos/laurocaetano/phoenix/releases{/id}","created_at":"2015-03-26T17:39:20Z","updated_at":"2015-03-26T17:39:21Z","pushed_at":"2015-04-09T23:44:23Z","git_url":"git://github.com/laurocaetano/phoenix.git","ssh_url":"git@github.com:laurocaetano/phoenix.git","clone_url":"https://github.com/laurocaetano/phoenix.git","svn_url":"https://github.com/laurocaetano/phoenix","homepage":"http://www.phoenixframework.org","size":2599,"stargazers_count":0,"watchers_count":0,"language":"Elixir","has_issues":false,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"phoenixframework:master","ref":"master","sha":"fae6af3dbda8183d93473885087e0e66ac3b765c","user":{"login":"phoenixframework","id":6510388,"avatar_url":"https://avatars.githubusercontent.com/u/6510388?v=3","gravatar_id":"","url":"https://api.github.com/users/phoenixframework","html_url":"https://github.com/phoenixframework","followers_url":"https://api.github.com/users/phoenixframework/followers","following_url":"https://api.github.com/users/phoenixframework/following{/other_user}","gists_url":"https://api.github.com/users/phoenixframework/gists{/gist_id}","starred_url":"https://api.github.com/users/phoenixframework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phoenixframework/subscriptions","organizations_url":"https://api.github.com/users/phoenixframework/orgs","repos_url":"https://api.github.com/users/phoenixframework/repos","events_url":"https://api.github.com/users/phoenixframework/events{/privacy}","received_events_url":"https://api.github.com/users/phoenixframework/received_events","type":"Organization","site_admin":false},"repo":{"id":16072585,"name":"phoenix","full_name":"phoenixframework/phoenix","owner":{"login":"phoenixframework","id":6510388,"avatar_url":"https://avatars.githubusercontent.com/u/6510388?v=3","gravatar_id":"","url":"https://api.github.com/users/phoenixframework","html_url":"https://github.com/phoenixframework","followers_url":"https://api.github.com/users/phoenixframework/followers","following_url":"https://api.github.com/users/phoenixframework/following{/other_user}","gists_url":"https://api.github.com/users/phoenixframework/gists{/gist_id}","starred_url":"https://api.github.com/users/phoenixframework/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/phoenixframework/subscriptions","organizations_url":"https://api.github.com/users/phoenixframework/orgs","repos_url":"https://api.github.com/users/phoenixframework/repos","events_url":"https://api.github.com/users/phoenixframework/events{/privacy}","received_events_url":"https://api.github.com/users/phoenixframework/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/phoenixframework/phoenix","description":"A highly connected web framework for the Elixir programming language","fork":false,"url":"https://api.github.com/repos/phoenixframework/phoenix","forks_url":"https://api.github.com/repos/phoenixframework/phoenix/forks","keys_url":"https://api.github.com/repos/phoenixframework/phoenix/keys{/key_id}","collaborators_url":"https://api.github.com/repos/phoenixframework/phoenix/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/phoenixframework/phoenix/teams","hooks_url":"https://api.github.com/repos/phoenixframework/phoenix/hooks","issue_events_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/events{/number}","events_url":"https://api.github.com/repos/phoenixframework/phoenix/events","assignees_url":"https://api.github.com/repos/phoenixframework/phoenix/assignees{/user}","branches_url":"https://api.github.com/repos/phoenixframework/phoenix/branches{/branch}","tags_url":"https://api.github.com/repos/phoenixframework/phoenix/tags","blobs_url":"https://api.github.com/repos/phoenixframework/phoenix/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/phoenixframework/phoenix/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/phoenixframework/phoenix/git/refs{/sha}","trees_url":"https://api.github.com/repos/phoenixframework/phoenix/git/trees{/sha}","statuses_url":"https://api.github.com/repos/phoenixframework/phoenix/statuses/{sha}","languages_url":"https://api.github.com/repos/phoenixframework/phoenix/languages","stargazers_url":"https://api.github.com/repos/phoenixframework/phoenix/stargazers","contributors_url":"https://api.github.com/repos/phoenixframework/phoenix/contributors","subscribers_url":"https://api.github.com/repos/phoenixframework/phoenix/subscribers","subscription_url":"https://api.github.com/repos/phoenixframework/phoenix/subscription","commits_url":"https://api.github.com/repos/phoenixframework/phoenix/commits{/sha}","git_commits_url":"https://api.github.com/repos/phoenixframework/phoenix/git/commits{/sha}","comments_url":"https://api.github.com/repos/phoenixframework/phoenix/comments{/number}","issue_comment_url":"https://api.github.com/repos/phoenixframework/phoenix/issues/comments{/number}","contents_url":"https://api.github.com/repos/phoenixframework/phoenix/contents/{+path}","compare_url":"https://api.github.com/repos/phoenixframework/phoenix/compare/{base}...{head}","merges_url":"https://api.github.com/repos/phoenixframework/phoenix/merges","archive_url":"https://api.github.com/repos/phoenixframework/phoenix/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/phoenixframework/phoenix/downloads","issues_url":"https://api.github.com/repos/phoenixframework/phoenix/issues{/number}","pulls_url":"https://api.github.com/repos/phoenixframework/phoenix/pulls{/number}","milestones_url":"https://api.github.com/repos/phoenixframework/phoenix/milestones{/number}","notifications_url":"https://api.github.com/repos/phoenixframework/phoenix/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/phoenixframework/phoenix/labels{/name}","releases_url":"https://api.github.com/repos/phoenixframework/phoenix/releases{/id}","created_at":"2014-01-20T14:14:11Z","updated_at":"2015-04-10T13:44:14Z","pushed_at":"2015-04-10T13:44:14Z","git_url":"git://github.com/phoenixframework/phoenix.git","ssh_url":"git@github.com:phoenixframework/phoenix.git","clone_url":"https://github.com/phoenixframework/phoenix.git","svn_url":"https://github.com/phoenixframework/phoenix","homepage":"http://www.phoenixframework.org","size":9201,"stargazers_count":2182,"watchers_count":2182,"language":"Elixir","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":227,"mirror_url":null,"open_issues_count":22,"forks":227,"open_issues":22,"watchers":2182,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/758"},"html":{"href":"https://github.com/phoenixframework/phoenix/pull/758"},"issue":{"href":"https://api.github.com/repos/phoenixframework/phoenix/issues/758"},"comments":{"href":"https://api.github.com/repos/phoenixframework/phoenix/issues/758/comments"},"review_comments":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/758/comments"},"review_comment":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/phoenixframework/phoenix/pulls/758/commits"},"statuses":{"href":"https://api.github.com/repos/phoenixframework/phoenix/statuses/5b42d43584e3bba4e8a6ef82a47dce8a8213acaa"}}}}