{"action":"created","comment":{"url":"https://api.github.com/repos/operable/cog/pulls/comments/56336971","id":56336971,"diff_hunk":"@@ -198,169 +181,107 @@ defmodule Cog.Command.Pipeline.Executor do\n     end\n   end\n \n-  @doc \"\"\"\n-  `bind` -> {:next_state, :maybe_expand}\n+  def plan_next_invocation(:timeout, %__MODULE__{invocations: [current_invocation|remaining],\n+                                                 output: previous_output,\n+                                                 user_permissions: permissions}=state) do\n \n-  Binds the current invocation to the scope. The scope is created based on the\n-  context. nil context: blank scope, map context: singular scope, list context:\n-  multiple scopes\n-  \"\"\"\n-  def bind(:timeout, %__MODULE__{current: current, context: context}=state) do\n-    scope = get_scope(context)\n-    |> resolve_scope(current)\n-    case bind_scope(scope, current) do\n+    # If a previous command generated output, we don't need to retain\n+    # any templating information, because the current command now\n+    # controls how the output will be presented\n+    context = strip_templates(previous_output)\n+\n+    case Cog.Command.Pipeline.Planner.plan(current_invocation, context, permissions) do\n+      {:ok, plans} ->\n+        {:next_state, :execute_plan, %{state |\n+                                       output: [],\n+                                       current_plan: nil,\n+                                       plans: plans,\n+                                       invocations: remaining}, 0}\n       {:error, {:not_found, var}} ->\n         Helpers.send_reply(\"I can't find the variable '$#{var}'.\", state.request, state.mq_conn)\n         fail_pipeline(state, :binding_error, \"Error preparing to execute command pipeline '#{state.request[\"text\"]}': Unknown variable '#{var}'\")\n+      {:error, :no_rule} ->\n+        why = \"No rules match the supplied invocation of '#{current_invocation}'. Check your args and options, then confirm that the proper rules are in place.\"\n+        Helpers.send_denied(current_invocation, why, state.request, state.mq_conn)\n+        fail_pipeline(state, :missing_rules, \"No rule matching '#{current_invocation}'\")\n+      {:error, {:denied, rule}} ->\n+        why = \"You will need the '#{rule.permission_selector.perms.value}' permission to run this command.\"\n+        Helpers.send_denied(current_invocation, why, state.request, state.mq_conn)\n+        fail_pipeline(state, :permission_denied, \"User #{state.request[\"sender\"][\"handle\"]} denied access to '#{current_invocation}'\")\n       {:error, msg} ->\n-        Helpers.send_reply(msg, state.request, state.mq_conn)\n+        # TODO: what is this error case?\n+        Helpers.send_error(msg, state.request, state.mq_conn)\n         fail_pipeline(state, :binding_error, \"Error preparing to execute command pipeline '#{state.request[\"text\"]}': #{msg}\")\n-      bound ->\n-        {current_bound, bound_scope} = collect_bound(bound)\n-        {:next_state, :maybe_expand, %{state | current_bound: current_bound, scope: bound_scope}, 0}\n-    end\n-  end\n-\n-  @doc \"\"\"\n-  `expand` -> {:stop, :shutdown} | {:next_state, :get_options}\n-\n-  If a command begins with 'user' or 'site' it is treated as an alias and expanded.\n-  \"\"\"\n-  def maybe_expand(:timeout, %__MODULE__{current: current_bound}=state) do\n-    case AliasExpander.expand(current_bound.command, state.user) do\n-      {:ok, :not_an_alias} ->\n-        {:next_state, :get_options, state, 0}\n-      {:ok, invocations} ->\n-        [new_current|rest] = invocations\n-        {:next_state, :bind, %{state | current: new_current, remaining: rest ++ state.remaining}, 0}\n-      {:error, msg} ->\n-        Helpers.send_reply(msg, state.request, state.mq_conn)\n-        fail_pipeline(state, :alias_expansion_error, \"Error expanding alias '#{current_bound.command}': #{msg}\")\n-    end\n-  end\n-\n-  @doc \"\"\"\n-  `get_options` -> {:stop, :shutdown} | {:next_state, :check_permission}\n-\n-  Runs the option interpreter on the current bound invocation.\n-  \"\"\"\n-  def get_options(:timeout, %__MODULE__{current_bound: current_bound}=state) do\n-    case interpret_options(current_bound) do\n-      {:ok, current_bound} ->\n-        {:next_state, :maybe_enforce, %{state | current_bound: current_bound}, 0}\n-      {:not_found, current_bound} ->\n-        Helpers.send_idk(state.request, current_bound.command, state.mq_conn)\n-        fail_pipeline(state, :option_interpreter_error, \"Command '#{current_bound.command}' not found\")\n       error ->\n         {:error, msg} = error\n         Helpers.send_error(msg, state.request, state.mq_conn)\n         fail_pipeline(state, :option_interpreter_error, \"Error parsing options: #{inspect error}\")\n     end\n   end\n+  def plan_next_invocation(:timeout, %__MODULE__{invocations: [], output: [], destinations: destinations}=state) do\n+    message = \"Pipeline executed successfully, but no output was returned\"\n+    adapter = state.request[\"adapter\"]\n+    Enum.each(destinations, &publish_response(response_fn(message, adapter), &1, state))\n+    {:stop, :shutdown, %{state | output: []}}\n+  end\n+  def plan_next_invocation(:timeout, %__MODULE__{invocations: [], output: output, destinations: destinations}=state) do\n+    # No more invocations, but we've got data; run it through\n+    # templating and send it out to all the destinations\n+    command = state.current_plan.command\n+    adapter = state.request[\"adapter\"]\n \n-  @doc \"\"\"\n-  `maybe_enforce` -> {:next_state, :maybe_collect_command} | {:next_state, :check_permission}\n+    message = Enum.map_join(output, \"\\n\", fn({data,template}) ->\n+      render_template(command, adapter, template, data)","path":"lib/cog/command/pipeline/executor.ex","position":345,"original_position":355,"commit_id":"350e9850fa85cf17d02f6b7c1ec745f74b769624","original_commit_id":"d1822c2b7b3be361ee5d20f46ace95fdfc576e17","user":{"login":"christophermaier","id":207178,"avatar_url":"https://avatars.githubusercontent.com/u/207178?v=3","gravatar_id":"","url":"https://api.github.com/users/christophermaier","html_url":"https://github.com/christophermaier","followers_url":"https://api.github.com/users/christophermaier/followers","following_url":"https://api.github.com/users/christophermaier/following{/other_user}","gists_url":"https://api.github.com/users/christophermaier/gists{/gist_id}","starred_url":"https://api.github.com/users/christophermaier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/christophermaier/subscriptions","organizations_url":"https://api.github.com/users/christophermaier/orgs","repos_url":"https://api.github.com/users/christophermaier/repos","events_url":"https://api.github.com/users/christophermaier/events{/privacy}","received_events_url":"https://api.github.com/users/christophermaier/received_events","type":"User","site_admin":false},"body":"As I was working through the code, I thought our rendering was more complicated than this. I was surprised as anyone to see that this is what it boils down to.\r\n\r\nThe example you provide @vanstee implies that all that data is in a single map (which I think is what the mustache templates also seem to require). In the case that we have a `once` command at the end of a pipeline, if it consolidates everything into a single map and has a template that can handle that, it would render properly (`table` works like this, currently).\r\n\r\nThat said, this works for everything that we currently have tests for; if there are edge cases that are being missed, we'll need to add some tests first and then reason back to a good approach.","created_at":"2016-03-16T13:58:41Z","updated_at":"2016-03-16T13:58:41Z","html_url":"https://github.com/operable/cog/pull/390#discussion_r56336971","pull_request_url":"https://api.github.com/repos/operable/cog/pulls/390","_links":{"self":{"href":"https://api.github.com/repos/operable/cog/pulls/comments/56336971"},"html":{"href":"https://github.com/operable/cog/pull/390#discussion_r56336971"},"pull_request":{"href":"https://api.github.com/repos/operable/cog/pulls/390"}}},"pull_request":{"url":"https://api.github.com/repos/operable/cog/pulls/390","id":62972875,"html_url":"https://github.com/operable/cog/pull/390","diff_url":"https://github.com/operable/cog/pull/390.diff","patch_url":"https://github.com/operable/cog/pull/390.patch","issue_url":"https://api.github.com/repos/operable/cog/issues/390","number":390,"state":"open","locked":false,"title":"New and improved executor","user":{"login":"christophermaier","id":207178,"avatar_url":"https://avatars.githubusercontent.com/u/207178?v=3","gravatar_id":"","url":"https://api.github.com/users/christophermaier","html_url":"https://github.com/christophermaier","followers_url":"https://api.github.com/users/christophermaier/followers","following_url":"https://api.github.com/users/christophermaier/following{/other_user}","gists_url":"https://api.github.com/users/christophermaier/gists{/gist_id}","starred_url":"https://api.github.com/users/christophermaier/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/christophermaier/subscriptions","organizations_url":"https://api.github.com/users/christophermaier/orgs","repos_url":"https://api.github.com/users/christophermaier/repos","events_url":"https://api.github.com/users/christophermaier/events{/privacy}","received_events_url":"https://api.github.com/users/christophermaier/received_events","type":"User","site_admin":false},"body":"Uses the new parser and simplifies A LOT of what the executor does.\r\n\r\nIntroduces a new concept of an \"execution plan\" for pipeline stages,\r\nin an attempt to decouple from the parser AST (though that's not quite\r\ncomplete just yet).\r\n\r\nLots more remains to be done, but this takes care of a huge chunk of\r\nthe work.\r\n\r\nCloses #286","created_at":"2016-03-15T20:38:24Z","updated_at":"2016-03-16T13:58:41Z","closed_at":null,"merged_at":null,"merge_commit_sha":"bfc6a8714af77041ecc91892f2c6232ead44c0a0","assignee":null,"milestone":null,"commits_url":"https://api.github.com/repos/operable/cog/pulls/390/commits","review_comments_url":"https://api.github.com/repos/operable/cog/pulls/390/comments","review_comment_url":"https://api.github.com/repos/operable/cog/pulls/comments{/number}","comments_url":"https://api.github.com/repos/operable/cog/issues/390/comments","statuses_url":"https://api.github.com/repos/operable/cog/statuses/350e9850fa85cf17d02f6b7c1ec745f74b769624","head":{"label":"operable:cm/new-executor","ref":"cm/new-executor","sha":"350e9850fa85cf17d02f6b7c1ec745f74b769624","user":{"login":"operable","id":6070463,"avatar_url":"https://avatars.githubusercontent.com/u/6070463?v=3","gravatar_id":"","url":"https://api.github.com/users/operable","html_url":"https://github.com/operable","followers_url":"https://api.github.com/users/operable/followers","following_url":"https://api.github.com/users/operable/following{/other_user}","gists_url":"https://api.github.com/users/operable/gists{/gist_id}","starred_url":"https://api.github.com/users/operable/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/operable/subscriptions","organizations_url":"https://api.github.com/users/operable/orgs","repos_url":"https://api.github.com/users/operable/repos","events_url":"https://api.github.com/users/operable/events{/privacy}","received_events_url":"https://api.github.com/users/operable/received_events","type":"Organization","site_admin":false},"repo":{"id":49292670,"name":"cog","full_name":"operable/cog","owner":{"login":"operable","id":6070463,"avatar_url":"https://avatars.githubusercontent.com/u/6070463?v=3","gravatar_id":"","url":"https://api.github.com/users/operable","html_url":"https://github.com/operable","followers_url":"https://api.github.com/users/operable/followers","following_url":"https://api.github.com/users/operable/following{/other_user}","gists_url":"https://api.github.com/users/operable/gists{/gist_id}","starred_url":"https://api.github.com/users/operable/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/operable/subscriptions","organizations_url":"https://api.github.com/users/operable/orgs","repos_url":"https://api.github.com/users/operable/repos","events_url":"https://api.github.com/users/operable/events{/privacy}","received_events_url":"https://api.github.com/users/operable/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/operable/cog","description":"Bringing the power of the command line to chat","fork":false,"url":"https://api.github.com/repos/operable/cog","forks_url":"https://api.github.com/repos/operable/cog/forks","keys_url":"https://api.github.com/repos/operable/cog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/operable/cog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/operable/cog/teams","hooks_url":"https://api.github.com/repos/operable/cog/hooks","issue_events_url":"https://api.github.com/repos/operable/cog/issues/events{/number}","events_url":"https://api.github.com/repos/operable/cog/events","assignees_url":"https://api.github.com/repos/operable/cog/assignees{/user}","branches_url":"https://api.github.com/repos/operable/cog/branches{/branch}","tags_url":"https://api.github.com/repos/operable/cog/tags","blobs_url":"https://api.github.com/repos/operable/cog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/operable/cog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/operable/cog/git/refs{/sha}","trees_url":"https://api.github.com/repos/operable/cog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/operable/cog/statuses/{sha}","languages_url":"https://api.github.com/repos/operable/cog/languages","stargazers_url":"https://api.github.com/repos/operable/cog/stargazers","contributors_url":"https://api.github.com/repos/operable/cog/contributors","subscribers_url":"https://api.github.com/repos/operable/cog/subscribers","subscription_url":"https://api.github.com/repos/operable/cog/subscription","commits_url":"https://api.github.com/repos/operable/cog/commits{/sha}","git_commits_url":"https://api.github.com/repos/operable/cog/git/commits{/sha}","comments_url":"https://api.github.com/repos/operable/cog/comments{/number}","issue_comment_url":"https://api.github.com/repos/operable/cog/issues/comments{/number}","contents_url":"https://api.github.com/repos/operable/cog/contents/{+path}","compare_url":"https://api.github.com/repos/operable/cog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/operable/cog/merges","archive_url":"https://api.github.com/repos/operable/cog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/operable/cog/downloads","issues_url":"https://api.github.com/repos/operable/cog/issues{/number}","pulls_url":"https://api.github.com/repos/operable/cog/pulls{/number}","milestones_url":"https://api.github.com/repos/operable/cog/milestones{/number}","notifications_url":"https://api.github.com/repos/operable/cog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/operable/cog/labels{/name}","releases_url":"https://api.github.com/repos/operable/cog/releases{/id}","deployments_url":"https://api.github.com/repos/operable/cog/deployments","created_at":"2016-01-08T19:45:06Z","updated_at":"2016-03-16T12:32:36Z","pushed_at":"2016-03-15T22:13:52Z","git_url":"git://github.com/operable/cog.git","ssh_url":"git@github.com:operable/cog.git","clone_url":"https://github.com/operable/cog.git","svn_url":"https://github.com/operable/cog","homepage":"http://operable.io","size":696,"stargazers_count":248,"watchers_count":248,"language":"Elixir","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":14,"mirror_url":null,"open_issues_count":106,"forks":14,"open_issues":106,"watchers":248,"default_branch":"master"}},"base":{"label":"operable:master","ref":"master","sha":"7604b76c1a0a2e59488d26e9059b140d75f34096","user":{"login":"operable","id":6070463,"avatar_url":"https://avatars.githubusercontent.com/u/6070463?v=3","gravatar_id":"","url":"https://api.github.com/users/operable","html_url":"https://github.com/operable","followers_url":"https://api.github.com/users/operable/followers","following_url":"https://api.github.com/users/operable/following{/other_user}","gists_url":"https://api.github.com/users/operable/gists{/gist_id}","starred_url":"https://api.github.com/users/operable/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/operable/subscriptions","organizations_url":"https://api.github.com/users/operable/orgs","repos_url":"https://api.github.com/users/operable/repos","events_url":"https://api.github.com/users/operable/events{/privacy}","received_events_url":"https://api.github.com/users/operable/received_events","type":"Organization","site_admin":false},"repo":{"id":49292670,"name":"cog","full_name":"operable/cog","owner":{"login":"operable","id":6070463,"avatar_url":"https://avatars.githubusercontent.com/u/6070463?v=3","gravatar_id":"","url":"https://api.github.com/users/operable","html_url":"https://github.com/operable","followers_url":"https://api.github.com/users/operable/followers","following_url":"https://api.github.com/users/operable/following{/other_user}","gists_url":"https://api.github.com/users/operable/gists{/gist_id}","starred_url":"https://api.github.com/users/operable/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/operable/subscriptions","organizations_url":"https://api.github.com/users/operable/orgs","repos_url":"https://api.github.com/users/operable/repos","events_url":"https://api.github.com/users/operable/events{/privacy}","received_events_url":"https://api.github.com/users/operable/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/operable/cog","description":"Bringing the power of the command line to chat","fork":false,"url":"https://api.github.com/repos/operable/cog","forks_url":"https://api.github.com/repos/operable/cog/forks","keys_url":"https://api.github.com/repos/operable/cog/keys{/key_id}","collaborators_url":"https://api.github.com/repos/operable/cog/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/operable/cog/teams","hooks_url":"https://api.github.com/repos/operable/cog/hooks","issue_events_url":"https://api.github.com/repos/operable/cog/issues/events{/number}","events_url":"https://api.github.com/repos/operable/cog/events","assignees_url":"https://api.github.com/repos/operable/cog/assignees{/user}","branches_url":"https://api.github.com/repos/operable/cog/branches{/branch}","tags_url":"https://api.github.com/repos/operable/cog/tags","blobs_url":"https://api.github.com/repos/operable/cog/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/operable/cog/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/operable/cog/git/refs{/sha}","trees_url":"https://api.github.com/repos/operable/cog/git/trees{/sha}","statuses_url":"https://api.github.com/repos/operable/cog/statuses/{sha}","languages_url":"https://api.github.com/repos/operable/cog/languages","stargazers_url":"https://api.github.com/repos/operable/cog/stargazers","contributors_url":"https://api.github.com/repos/operable/cog/contributors","subscribers_url":"https://api.github.com/repos/operable/cog/subscribers","subscription_url":"https://api.github.com/repos/operable/cog/subscription","commits_url":"https://api.github.com/repos/operable/cog/commits{/sha}","git_commits_url":"https://api.github.com/repos/operable/cog/git/commits{/sha}","comments_url":"https://api.github.com/repos/operable/cog/comments{/number}","issue_comment_url":"https://api.github.com/repos/operable/cog/issues/comments{/number}","contents_url":"https://api.github.com/repos/operable/cog/contents/{+path}","compare_url":"https://api.github.com/repos/operable/cog/compare/{base}...{head}","merges_url":"https://api.github.com/repos/operable/cog/merges","archive_url":"https://api.github.com/repos/operable/cog/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/operable/cog/downloads","issues_url":"https://api.github.com/repos/operable/cog/issues{/number}","pulls_url":"https://api.github.com/repos/operable/cog/pulls{/number}","milestones_url":"https://api.github.com/repos/operable/cog/milestones{/number}","notifications_url":"https://api.github.com/repos/operable/cog/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/operable/cog/labels{/name}","releases_url":"https://api.github.com/repos/operable/cog/releases{/id}","deployments_url":"https://api.github.com/repos/operable/cog/deployments","created_at":"2016-01-08T19:45:06Z","updated_at":"2016-03-16T12:32:36Z","pushed_at":"2016-03-15T22:13:52Z","git_url":"git://github.com/operable/cog.git","ssh_url":"git@github.com:operable/cog.git","clone_url":"https://github.com/operable/cog.git","svn_url":"https://github.com/operable/cog","homepage":"http://operable.io","size":696,"stargazers_count":248,"watchers_count":248,"language":"Elixir","has_issues":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":14,"mirror_url":null,"open_issues_count":106,"forks":14,"open_issues":106,"watchers":248,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/operable/cog/pulls/390"},"html":{"href":"https://github.com/operable/cog/pull/390"},"issue":{"href":"https://api.github.com/repos/operable/cog/issues/390"},"comments":{"href":"https://api.github.com/repos/operable/cog/issues/390/comments"},"review_comments":{"href":"https://api.github.com/repos/operable/cog/pulls/390/comments"},"review_comment":{"href":"https://api.github.com/repos/operable/cog/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/operable/cog/pulls/390/commits"},"statuses":{"href":"https://api.github.com/repos/operable/cog/statuses/350e9850fa85cf17d02f6b7c1ec745f74b769624"}}}}