Make Supervisor.Spec docs examples more formatter-compatible (#7967)

defmodule Supervisor.Spec do
  @moduledoc """
  Outdated functions for building child specifications.

  The functions in this module are deprecated and they do not work
  with the module-based child specs introduced in Elixir v1.5.
  Please see the `Supervisor` documentation instead.

  Convenience functions for defining supervisor specifications.

  ## Example

  By using the functions in this module one can specify the children
  to be used under a supervisor, started with `Supervisor.start_link/2`:

      import Supervisor.Spec

      children = [
        worker(MyWorker, [arg1, arg2, arg3]),
        supervisor(MySupervisor, [arg1])
      ]

      Supervisor.start_link(children, strategy: :one_for_one)

  Sometimes, it may be handy to define supervisors backed
  by a module:

      defmodule MySupervisor do
        use Supervisor

        def start_link(arg) do
          Supervisor.start_link(__MODULE__, arg)
        end

        def init(arg) do
          children = [
            worker(MyWorker, [arg], restart: :temporary)
          ]

          supervise(children, strategy: :simple_one_for_one)
        end
      end

  Notice in this case we don't have to explicitly import
  `Supervisor.Spec` as `use Supervisor` automatically does so.
  Defining a module-based supervisor can be useful, for example,
  to perform initialization tasks in the `c:init/1` callback.

  ## Supervisor and worker options

  In the example above, we defined specs for workers and supervisors.
  These specs (both for workers as well as supervisors) accept the
  following options:

    * `:id` - a name used to identify the child specification
      internally by the supervisor; defaults to the given module
      name for the child worker/supervisor

    * `:function` - the function to invoke on the child to start it

    * `:restart` - an atom that defines when a terminated child process should
      be restarted (see the "Restart values" section below)

    * `:shutdown` - an atom that defines how a child process should be
      terminated (see the "Shutdown values" section below)

    * `:modules` - it should be a list with one element `[module]`,
      where module is the name of the callback module only if the
      child process is a `Supervisor` or `GenServer`; if the child
      process is a `GenEvent`, `:modules` should be `:dynamic`

  ### Restart values (:restart)

  The following restart values are supported in the `:restart` option:

    * `:permanent` - the child process is always restarted

    * `:temporary` - the child process is never restarted (not even
      when the supervisor's strategy is `:rest_for_one` or `:one_for_all`)

    * `:transient` - the child process is restarted only if it
      terminates abnormally, i.e., with an exit reason other than
      `:normal`, `:shutdown` or `{:shutdown, term}`

  Notice that supervisor that reached maximum restart intensity will exit with `:shutdown` reason.
  In this case the supervisor will only be restarted if its child specification was defined with
  the `:restart` option is set to `:permanent` (the default).

  ### Shutdown values (:shutdown)

  The following shutdown values are supported in the `:shutdown` option:

    * `:brutal_kill` - the child process is unconditionally terminated
      using `Process.exit(child, :kill)`

    * `:infinity` - if the child process is a supervisor, this is a mechanism
      to give the subtree enough time to shutdown; it can also be used with
      workers with care

    * any integer - the value of `:shutdown` can also be any integer meaning
      that the supervisor tells the child process to terminate by calling
      `Process.exit(child, :shutdown)` and then waits for an exit signal back.
      If no exit signal is received within the specified time (the value of this
      option, in milliseconds), the child process is unconditionally terminated
      using `Process.exit(child, :kill)`

  """

  @moduledoc deprecated:
               "Use the new child specifications outlined in the Supervisor module instead"

  # TODO: Deprecate all functions in this module on Elixir v1.9.
  # Also deprecate entry in Supervisor.Default.

  @typedoc "Supported strategies"
  @type strategy :: :simple_one_for_one | :one_for_one | :one_for_all | :rest_for_one

  @typedoc "Supported restart values"
  @type restart :: :permanent | :transient | :temporary

  @typedoc "Supported shutdown values"
  @type shutdown :: timeout | :brutal_kill

  @typedoc "Supported worker values"
  @type worker :: :worker | :supervisor

  @typedoc "Supported module values"
  @type modules :: :dynamic | [module]

  @typedoc "Supported id values"
  @type child_id :: term

  @typedoc "The supervisor specification"
  @type spec ::
          {child_id, start_fun :: {module, atom, [term]}, restart, shutdown, worker, modules}

  @doc """
  Receives a list of children (workers or supervisors) to
  supervise and a set of options.

  Returns a tuple containing the supervisor specification. This tuple can be
  used as the return value of the `c:init/1` callback when implementing a
  module-based supervisor.

  ## Examples

      supervise(children, strategy: :one_for_one)

  ## Options

    * `:strategy` - the restart strategy option. It can be either
      `:one_for_one`, `:rest_for_one`, `:one_for_all`, or
      `:simple_one_for_one`. You can learn more about strategies
      in the `Supervisor` module docs.

    * `:max_restarts` - the maximum number of restarts allowed in
      a time frame. Defaults to `3`.

    * `:max_seconds` - the time frame in which `:max_restarts` applies.
      Defaults to `5`.

  The `:strategy` option is required and by default a maximum of 3 restarts is
  allowed within 5 seconds. Check the `Supervisor` module for a detailed
  description of the available strategies.
  """
  @spec supervise(
          [spec],
          strategy: strategy,
          max_restarts: non_neg_integer,
          max_seconds: pos_integer
        ) :: {:ok, tuple}
  def supervise(children, options) do
    unless strategy = options[:strategy] do
      raise ArgumentError, "expected :strategy option to be given"
    end

    maxR = Keyword.get(options, :max_restarts, 3)
    maxS = Keyword.get(options, :max_seconds, 5)

    assert_unique_ids(Enum.map(children, &get_id/1))
    {:ok, {{strategy, maxR, maxS}, children}}
  end

  defp get_id({id, _, _, _, _, _}) do
    id
  end

  defp get_id(other) do
    raise ArgumentError,
          "invalid tuple specification given to supervise/2. If you are trying to use " <>
            "the map child specification that is part of the Elixir v1.5, use Supervisor.init/2 " <>
            "instead of Supervisor.Spec.supervise/2. See the Supervisor module for more information. " <>
            "Got: #{inspect(other)}"
  end

  defp assert_unique_ids([id | rest]) do
    if id in rest do
      raise ArgumentError,
            "duplicated id #{inspect(id)} found in the supervisor specification, " <>
              "please explicitly pass the :id option when defining this worker/supervisor"
    else
      assert_unique_ids(rest)
    end
  end

  defp assert_unique_ids([]) do
    :ok
  end

  @doc """
  Defines the given `module` as a worker which will be started
  with the given arguments.

      worker(ExUnit.Runner, [], restart: :permanent)

  By default, the function `start_link` is invoked on the given
  module. Overall, the default values for the options are:

      [
        id: module,
        function: :start_link,
        restart: :permanent,
        shutdown: 5000,
        modules: [module]
      ]

  Check the documentation for the `Supervisor.Spec` module for more
  information on the options.
  """
  @spec worker(
          module,
          [term],
          restart: restart,
          shutdown: shutdown,
          id: term,
          function: atom,
          modules: modules
        ) :: spec
  def worker(module, args, options \\ []) do
    child(:worker, module, args, options)
  end

  @doc """
  Defines the given `module` as a supervisor which will be started
  with the given arguments.

      supervisor(ExUnit.Runner, [], restart: :permanent)

  By default, the function `start_link` is invoked on the given
  module. Overall, the default values for the options are:

      [
        id: module,
        function: :start_link,
        restart: :permanent,
        shutdown: :infinity,
        modules: [module]
      ]

  Check the documentation for the `Supervisor.Spec` module for more
  information on the options.
  """
  @spec supervisor(
          module,
          [term],
          restart: restart,
          shutdown: shutdown,
          id: term,
          function: atom,
          modules: modules
        ) :: spec
  def supervisor(module, args, options \\ []) do
    options = Keyword.put_new(options, :shutdown, :infinity)
    child(:supervisor, module, args, options)
  end

  defp child(type, module, args, options) do
    id = Keyword.get(options, :id, module)
    modules = Keyword.get(options, :modules, modules(module))
    function = Keyword.get(options, :function, :start_link)
    restart = Keyword.get(options, :restart, :permanent)
    shutdown = Keyword.get(options, :shutdown, 5000)

    {id, {module, function, args}, restart, shutdown, type, modules}
  end

  defp modules(GenEvent), do: :dynamic
  defp modules(module), do: [module]
end

defmodule Supervisor.Spec do
  @moduledoc """
  Outdated functions for building child specifications.

  The functions in this module are deprecated and they do not work
  with the module-based child specs introduced in Elixir v1.5.
  Please see the `Supervisor` documentation instead.

  Convenience functions for defining supervisor specifications.

  ## Example

  By using the functions in this module one can specify the children
  to be used under a supervisor, started with `Supervisor.start_link/2`:

      import Supervisor.Spec

      children = [
        worker(MyWorker, [arg1, arg2, arg3]),
        supervisor(MySupervisor, [arg1])
      ]

      Supervisor.start_link(children, strategy: :one_for_one)

  Sometimes, it may be handy to define supervisors backed
  by a module:

      defmodule MySupervisor do
        use Supervisor

        def start_link(arg) do
          Supervisor.start_link(__MODULE__, arg)
        end

        def init(arg) do
          children = [
            worker(MyWorker, [arg], restart: :temporary)
          ]

          supervise(children, strategy: :simple_one_for_one)
        end
      end

  Notice in this case we don't have to explicitly import
  `Supervisor.Spec` as `use Supervisor` automatically does so.
  Defining a module-based supervisor can be useful, for example,
  to perform initialization tasks in the `c:init/1` callback.

  ## Supervisor and worker options

  In the example above, we defined specs for workers and supervisors.
  These specs (both for workers as well as supervisors) accept the
  following options:

    * `:id` - a name used to identify the child specification
      internally by the supervisor; defaults to the given module
      name for the child worker/supervisor

    * `:function` - the function to invoke on the child to start it

    * `:restart` - an atom that defines when a terminated child process should
      be restarted (see the "Restart values" section below)

    * `:shutdown` - an atom that defines how a child process should be
      terminated (see the "Shutdown values" section below)

    * `:modules` - it should be a list with one element `[module]`,
      where module is the name of the callback module only if the
      child process is a `Supervisor` or `GenServer`; if the child
      process is a `GenEvent`, `:modules` should be `:dynamic`

  ### Restart values (:restart)

  The following restart values are supported in the `:restart` option:

    * `:permanent` - the child process is always restarted

    * `:temporary` - the child process is never restarted (not even
      when the supervisor's strategy is `:rest_for_one` or `:one_for_all`)

    * `:transient` - the child process is restarted only if it
      terminates abnormally, i.e., with an exit reason other than
      `:normal`, `:shutdown` or `{:shutdown, term}`

  Notice that supervisor that reached maximum restart intensity will exit with `:shutdown` reason.
  In this case the supervisor will only be restarted if its child specification was defined with
  the `:restart` option is set to `:permanent` (the default).

  ### Shutdown values (:shutdown)

  The following shutdown values are supported in the `:shutdown` option:

    * `:brutal_kill` - the child process is unconditionally terminated
      using `Process.exit(child, :kill)`

    * `:infinity` - if the child process is a supervisor, this is a mechanism
      to give the subtree enough time to shutdown; it can also be used with
      workers with care

    * any integer - the value of `:shutdown` can also be any integer meaning
      that the supervisor tells the child process to terminate by calling
      `Process.exit(child, :shutdown)` and then waits for an exit signal back.
      If no exit signal is received within the specified time (the value of this
      option, in milliseconds), the child process is unconditionally terminated
      using `Process.exit(child, :kill)`

  """

  @moduledoc deprecated:
               "Use the new child specifications outlined in the Supervisor module instead"

  # TODO: Deprecate all functions in this module on Elixir v1.9.
  # Also deprecate entry in Supervisor.Default.

  @typedoc "Supported strategies"
  @type strategy :: :simple_one_for_one | :one_for_one | :one_for_all | :rest_for_one

  @typedoc "Supported restart values"
  @type restart :: :permanent | :transient | :temporary

  @typedoc "Supported shutdown values"
  @type shutdown :: timeout | :brutal_kill

  @typedoc "Supported worker values"
  @type worker :: :worker | :supervisor

  @typedoc "Supported module values"
  @type modules :: :dynamic | [module]

  @typedoc "Supported id values"
  @type child_id :: term

  @typedoc "The supervisor specification"
  @type spec ::
          {child_id, start_fun :: {module, atom, [term]}, restart, shutdown, worker, modules}

  @doc """
  Receives a list of children (workers or supervisors) to
  supervise and a set of options.

  Returns a tuple containing the supervisor specification. This tuple can be
  used as the return value of the `c:init/1` callback when implementing a
  module-based supervisor.

  ## Examples

      supervise(children, strategy: :one_for_one)

  ## Options

    * `:strategy` - the restart strategy option. It can be either
      `:one_for_one`, `:rest_for_one`, `:one_for_all`, or
      `:simple_one_for_one`. You can learn more about strategies
      in the `Supervisor` module docs.

    * `:max_restarts` - the maximum number of restarts allowed in
      a time frame. Defaults to `3`.

    * `:max_seconds` - the time frame in which `:max_restarts` applies.
      Defaults to `5`.

  The `:strategy` option is required and by default a maximum of 3 restarts is
  allowed within 5 seconds. Check the `Supervisor` module for a detailed
  description of the available strategies.
  """
  @spec supervise(
          [spec],
          strategy: strategy,
          max_restarts: non_neg_integer,
          max_seconds: pos_integer
        ) :: {:ok, tuple}
  def supervise(children, options) do
    unless strategy = options[:strategy] do
      raise ArgumentError, "expected :strategy option to be given"
    end

    maxR = Keyword.get(options, :max_restarts, 3)
    maxS = Keyword.get(options, :max_seconds, 5)

    assert_unique_ids(Enum.map(children, &get_id/1))
    {:ok, {{strategy, maxR, maxS}, children}}
  end

  defp get_id({id, _, _, _, _, _}) do
    id
  end

  defp get_id(other) do
    raise ArgumentError,
          "invalid tuple specification given to supervise/2. If you are trying to use " <>
            "the map child specification that is part of the Elixir v1.5, use Supervisor.init/2 " <>
            "instead of Supervisor.Spec.supervise/2. See the Supervisor module for more information. " <>
            "Got: #{inspect(other)}"
  end

  defp assert_unique_ids([id | rest]) do
    if id in rest do
      raise ArgumentError,
            "duplicated id #{inspect(id)} found in the supervisor specification, " <>
              "please explicitly pass the :id option when defining this worker/supervisor"
    else
      assert_unique_ids(rest)
    end
  end

  defp assert_unique_ids([]) do
    :ok
  end

  @doc """
  Defines the given `module` as a worker which will be started
  with the given arguments.

      worker(ExUnit.Runner, [], restart: :permanent)

  By default, the function `start_link` is invoked on the given
  module. Overall, the default values for the options are:

      [id: module,
       function: :start_link,
       restart: :permanent,
       shutdown: 5000,
       modules: [module]]

  Check the documentation for the `Supervisor.Spec` module for more
  information on the options.
  """
  @spec worker(
          module,
          [term],
          restart: restart,
          shutdown: shutdown,
          id: term,
          function: atom,
          modules: modules
        ) :: spec
  def worker(module, args, options \\ []) do
    child(:worker, module, args, options)
  end

  @doc """
  Defines the given `module` as a supervisor which will be started
  with the given arguments.

      supervisor(ExUnit.Runner, [], restart: :permanent)

  By default, the function `start_link` is invoked on the given
  module. Overall, the default values for the options are:

      [id: module,
       function: :start_link,
       restart: :permanent,
       shutdown: :infinity,
       modules: [module]]

  Check the documentation for the `Supervisor.Spec` module for more
  information on the options.
  """
  @spec supervisor(
          module,
          [term],
          restart: restart,
          shutdown: shutdown,
          id: term,
          function: atom,
          modules: modules
        ) :: spec
  def supervisor(module, args, options \\ []) do
    options = Keyword.put_new(options, :shutdown, :infinity)
    child(:supervisor, module, args, options)
  end

  defp child(type, module, args, options) do
    id = Keyword.get(options, :id, module)
    modules = Keyword.get(options, :modules, modules(module))
    function = Keyword.get(options, :function, :start_link)
    restart = Keyword.get(options, :restart, :permanent)
    shutdown = Keyword.get(options, :shutdown, 5000)

    {id, {module, function, args}, restart, shutdown, type, modules}
  end

  defp modules(GenEvent), do: :dynamic
  defp modules(module), do: [module]
end


Add '--quiet' option to 'mix deps.get'

Add '--quiet' option to 'mix deps.get'

Closes #1028.

defmodule Mix.Tasks.Deps.Get do
  use Mix.Task

  @shortdoc "Get all out of date dependencies"
  @recursive true

  @moduledoc """
  Get all out of date dependencies, i.e. dependencies
  that are not available or have an invalid lock.

  ## Command line options

  * `--no-compile` skip compilation of dependencies
  * `--quiet` only output error messages or if an action was performed
  """

  import Mix.Deps, only: [all: 2, by_name!: 1, format_dep: 1, check_lock: 2, out_of_date?: 1]

  def run(args) do
    { opts, rest } = OptionParser.parse(args, switches: [no_compile: :boolean, quiet: :boolean])

    if rest != [] do
      { _, acc } = Enum.map_reduce by_name!(rest), init, deps_getter(&1, &2)
    else
      acc = all(init, deps_getter(&1, &2))
    end

    finalize_get(acc, opts)
  end

  defp init do
    { [], Mix.Deps.Lock.read }
  end

  defp finalize_get({ apps, lock }, opts) do
    if apps == [] do
      unless opts[:quiet], do: Mix.shell.info "All dependencies up to date"
    else
      Mix.Deps.Lock.write(lock)

      unless opts[:no_compile], do: Mix.Task.run "deps.compile", apps
    end
  end

  defp deps_getter(dep, { acc, lock }) do
    shell = Mix.shell
    dep = check_lock(dep, lock)

    if out_of_date?(dep) do
      Mix.Dep[app: app, scm: scm, opts: opts] = dep
      shell.info "* Getting #{format_dep(dep)}"

      old  = lock[app]
      opts = Keyword.put(opts, :lock, old)

      new =
        if scm.checked_out?(opts) do
          scm.update(opts)
        else
          scm.checkout(opts)
        end

      if new do
        # Update the dependency returned so it is now
        # available and nested dependencies can be fetched
        { Mix.Deps.update(dep), { [app|acc], Keyword.put(lock, app, new) } }
      else
        { dep, { acc, lock } }
      end
    else
      { dep, { acc, lock } }
    end
  end
end

defmodule Mix.Tasks.Deps.Get do
  use Mix.Task

  @shortdoc "Get all out of date dependencies"
  @recursive true

  @moduledoc """
  Get all out of date dependencies, i.e. dependencies
  that are not available or have an invalid lock.

  ## Command line options

  * `--no-compile` skip compilation of dependencies
  """

  import Mix.Deps, only: [all: 2, by_name!: 1, format_dep: 1, check_lock: 2, out_of_date?: 1]

  def run(args) do
    { opts, rest } = OptionParser.parse(args, switches: [no_compile: :boolean])

    if rest != [] do
      { _, acc } = Enum.map_reduce by_name!(rest), init, deps_getter(&1, &2)
    else
      acc = all(init, deps_getter(&1, &2))
    end

    finalize_get acc, opts[:no_compile]
  end

  defp init do
    { [], Mix.Deps.Lock.read }
  end

  defp finalize_get({ apps, lock }, no_compile) do
    if apps == [] do
      Mix.shell.info "All dependencies up to date"
    else
      Mix.Deps.Lock.write(lock)

      unless no_compile, do: Mix.Task.run "deps.compile", apps
    end
  end

  defp deps_getter(dep, { acc, lock }) do
    shell = Mix.shell
    dep = check_lock(dep, lock)

    if out_of_date?(dep) do
      Mix.Dep[app: app, scm: scm, opts: opts] = dep
      shell.info "* Getting #{format_dep(dep)}"

      old  = lock[app]
      opts = Keyword.put(opts, :lock, old)

      new =
        if scm.checked_out?(opts) do
          scm.update(opts)
        else
          scm.checkout(opts)
        end

      if new do
        # Update the dependency returned so it is now
        # available and nested dependencies can be fetched
        { Mix.Deps.update(dep), { [app|acc], Keyword.put(lock, app, new) } }
      else
        { dep, { acc, lock } }
      end
    else
      { dep, { acc, lock } }
    end
  end
end


Copy include directory before compiling ebin for rebar3

Closes #10517

defmodule Mix.Tasks.Deps.Compile do
  use Mix.Task

  @shortdoc "Compiles dependencies"

  @moduledoc """
  Compiles dependencies.

  By default, compile all dependencies. A list of dependencies
  can be given to compile multiple dependencies in order.

  This task attempts to detect if the project contains one of
  the following files and act accordingly:

    * `mix.exs` - invokes `mix compile`
    * `rebar.config` - invokes `rebar compile`
    * `Makefile.win`- invokes `nmake /F Makefile.win` (only on Windows)
    * `Makefile` - invokes `gmake` on DragonFlyBSD, FreeBSD, NetBSD, and OpenBSD,
      invokes `make` on any other operating system (except on Windows)

  The compilation can be customized by passing a `compile` option
  in the dependency:

      {:some_dependency, "0.1.0", compile: "command to compile"}

  If a list of dependencies is given, Mix will attempt to compile
  them as is. For example, if project `a` depends on `b`, calling
  `mix deps.compile a` will compile `a` even if `b` is out of
  date. This is to allow parts of the dependency tree to be
  recompiled without propagating those changes upstream. To ensure
  `b` is included in the compilation step, pass `--include-children`.

  ## Command line options

    * `--force` - force compilation of deps
    * `--skip-umbrella-children` - skips umbrella applications from compiling
    * `--skip-local-deps` - skips non-remote dependencies, such as path deps, from compiling

  """

  @switches [
    include_children: :boolean,
    force: :boolean,
    skip_umbrella_children: :boolean,
    skip_local_deps: :boolean
  ]

  @impl true
  def run(args) do
    unless "--no-archives-check" in args do
      Mix.Task.run("archive.check", args)
    end

    Mix.Project.get!()
    deps = Mix.Dep.load_and_cache()

    case OptionParser.parse(args, switches: @switches) do
      {opts, [], _} ->
        # Because this command may be invoked explicitly with
        # deps.compile, we simply try to compile any available
        # dependency.
        compile(Enum.filter(deps, &Mix.Dep.available?/1), opts)

      {opts, tail, _} ->
        compile(Mix.Dep.filter_by_name(tail, deps, opts), opts)
    end
  end

  @doc false
  def compile(deps, options \\ []) do
    shell = Mix.shell()
    config = Mix.Project.deps_config()
    Mix.Task.run("deps.precompile")

    compiled =
      deps
      |> reject_umbrella_children(options)
      |> reject_local_deps(options)
      |> Enum.map(fn %Mix.Dep{app: app, status: status, opts: opts, scm: scm} = dep ->
        check_unavailable!(app, status)
        maybe_clean(dep, options)

        compiled? =
          cond do
            not is_nil(opts[:compile]) ->
              do_compile(dep, config)

            Mix.Dep.mix?(dep) ->
              do_mix(dep, config)

            Mix.Dep.make?(dep) ->
              do_make(dep, config)

            dep.manager == :rebar ->
              do_rebar(dep, config)

            dep.manager == :rebar3 ->
              do_rebar3(dep, config)

            true ->
              shell.error(
                "Could not compile #{inspect(app)}, no \"mix.exs\", \"rebar.config\" or \"Makefile\" " <>
                  "(pass :compile as an option to customize compilation, set it to \"false\" to do nothing)"
              )

              false
          end

        unless Mix.Dep.mix?(dep), do: build_structure(dep, config)

        # We should touch fetchable dependencies even if they
        # did not compile otherwise they will always be marked
        # as stale, even when there is nothing to do.
        fetchable? = touch_fetchable(scm, opts[:build])

        compiled? and fetchable?
      end)

    if true in compiled, do: Mix.Task.run("will_recompile"), else: :ok
  end

  defp maybe_clean(dep, opts) do
    # If a dependency was marked as fetched or with an out of date lock
    # or missing the app file, we always compile it from scratch.
    if Keyword.get(opts, :force, false) or Mix.Dep.compilable?(dep) do
      File.rm_rf!(Path.join([Mix.Project.build_path(), "lib", Atom.to_string(dep.app)]))
    end
  end

  defp touch_fetchable(scm, path) do
    if scm.fetchable? do
      path = Path.join(path, ".mix")
      File.mkdir_p!(path)
      File.touch!(Path.join(path, "compile.fetch"))
      true
    else
      false
    end
  end

  defp check_unavailable!(app, {:unavailable, _}) do
    Mix.raise(
      "Cannot compile dependency #{inspect(app)} because " <>
        "it isn't available, run \"mix deps.get\" first"
    )
  end

  defp check_unavailable!(_, _) do
    :ok
  end

  defp do_mix(dep, _config) do
    Mix.Dep.in_dependency(dep, fn _ ->
      config = Mix.Project.config()

      if req = old_elixir_req(config) do
        Mix.shell().error(
          "warning: the dependency #{inspect(dep.app)} requires Elixir #{inspect(req)} " <>
            "but you are running on v#{System.version()}"
        )
      end

      try do
        options = [
          "--no-deps-loading",
          "--no-apps-loading",
          "--no-archives-check",
          "--no-elixir-version-check",
          "--no-warnings-as-errors"
        ]

        res = Mix.Task.run("compile", options)
        match?({:ok, _}, res)
      catch
        kind, reason ->
          app = dep.app

          Mix.shell().error(
            "could not compile dependency #{inspect(app)}, \"mix compile\" failed. " <>
              "You can recompile this dependency with \"mix deps.compile #{app}\", update it " <>
              "with \"mix deps.update #{app}\" or clean it with \"mix deps.clean #{app}\""
          )

          :erlang.raise(kind, reason, __STACKTRACE__)
      end
    end)
  end

  defp do_rebar(dep, config) do
    lib_path = Path.join(config[:env_path], "lib")
    cmd = "#{rebar_cmd(dep)} compile skip_deps=true deps_dir=#{inspect(lib_path)}"
    do_command(dep, config, cmd, false)
  end

  defp do_rebar3(%Mix.Dep{opts: opts} = dep, config) do
    dep_path = opts[:build]
    config_path = Path.join(dep_path, "mix.rebar.config")
    lib_path = Path.join(config[:env_path], "lib/*/ebin")

    # REBAR_BARE_COMPILER_OUTPUT_DIR is only honored by rebar3 >= 3.14
    env = [
      {"REBAR_CONFIG", config_path},
      {"TERM", "dumb"},
      {"REBAR_BARE_COMPILER_OUTPUT_DIR", dep_path}
    ]

    cmd = "#{rebar_cmd(dep)} bare compile --paths #{lib_path}"

    File.mkdir_p!(dep_path)
    File.write!(config_path, rebar_config(dep))

    # We need to copy symlink in case it is needed for
    # compiling the dependency itself. Later on we will
    # build the whole structure and also symlink/copy
    # priv and ebin.
    Mix.Utils.symlink_or_copy(Path.join(opts[:dest], "include"), Path.join(dep_path, "include"))

    do_command(dep, config, cmd, false, env)
  end

  defp rebar_config(dep) do
    dep.extra
    |> Mix.Rebar.dependency_config()
    |> Mix.Rebar.serialize_config()
  end

  defp rebar_cmd(%Mix.Dep{manager: manager} = dep) do
    Mix.Rebar.rebar_cmd(manager) || handle_rebar_not_found(dep)
  end

  defp handle_rebar_not_found(%Mix.Dep{app: app, manager: manager}) do
    shell = Mix.shell()

    shell.info(
      "Could not find \"#{manager}\", which is needed to build dependency #{inspect(app)}"
    )

    shell.info("I can install a local copy which is just used by Mix")

    install_question =
      "Shall I install #{manager}? (if running non-interactively, " <>
        "use \"mix local.rebar --force\")"

    unless shell.yes?(install_question) do
      error_message =
        "Could not find \"#{manager}\" to compile " <>
          "dependency #{inspect(app)}, please ensure \"#{manager}\" is available"

      Mix.raise(error_message)
    end

    (Mix.Tasks.Local.Rebar.run([]) && Mix.Rebar.local_rebar_cmd(manager)) ||
      Mix.raise("\"#{manager}\" installation failed")
  end

  defp do_make(dep, config) do
    command = make_command(dep)
    do_command(dep, config, command, true, [{"IS_DEP", "1"}])
  end

  defp make_command(dep) do
    makefile_win? = makefile_win?(dep)

    command =
      case :os.type() do
        {:win32, _} when makefile_win? ->
          "nmake /F Makefile.win"

        {:unix, type} when type in [:dragonfly, :freebsd, :netbsd, :openbsd] ->
          "gmake"

        _ ->
          "make"
      end

    if erlang_mk?(dep) do
      "#{command} clean && #{command}"
    else
      command
    end
  end

  defp do_compile(%Mix.Dep{opts: opts} = dep, config) do
    if command = opts[:compile] do
      do_command(dep, config, command, true)
    else
      false
    end
  end

  defp do_command(dep, config, command, print_app?, env \\ []) do
    %Mix.Dep{app: app, system_env: system_env, opts: opts} = dep

    File.cd!(opts[:dest], fn ->
      env = [{"ERL_LIBS", Path.join(config[:env_path], "lib")} | system_env] ++ env

      if Mix.shell().cmd(command, env: env, print_app: print_app?) != 0 do
        Mix.raise(
          "Could not compile dependency #{inspect(app)}, \"#{command}\" command failed. " <>
            "You can recompile this dependency with \"mix deps.compile #{app}\", update it " <>
            "with \"mix deps.update #{app}\" or clean it with \"mix deps.clean #{app}\""
        )
      end
    end)

    true
  end

  defp build_structure(%Mix.Dep{opts: opts} = dep, config) do
    build_path = Path.dirname(opts[:build])

    Enum.each(Mix.Dep.source_paths(dep), fn {source, base} ->
      app = Path.join(build_path, base)
      build_structure(source, app, config)
      Code.prepend_path(Path.join(app, "ebin"))
    end)
  end

  defp build_structure(dest, build, config) do
    File.cd!(dest, fn ->
      config = Keyword.put(config, :app_path, build)
      Mix.Project.build_structure(config, symlink_ebin: true)
    end)
  end

  defp old_elixir_req(config) do
    req = config[:elixir]

    if req && not Version.match?(System.version(), req) do
      req
    end
  end

  defp erlang_mk?(%Mix.Dep{opts: opts}) do
    File.regular?(Path.join(opts[:dest], "erlang.mk"))
  end

  defp makefile_win?(%Mix.Dep{opts: opts}) do
    File.regular?(Path.join(opts[:dest], "Makefile.win"))
  end

  defp reject_umbrella_children(deps, options) do
    if options[:skip_umbrella_children] do
      Enum.reject(deps, fn %{opts: opts} -> Keyword.get(opts, :from_umbrella) == true end)
    else
      deps
    end
  end

  defp reject_local_deps(deps, options) do
    if options[:skip_local_deps] do
      Enum.filter(deps, fn %{scm: scm} -> scm.fetchable? end)
    else
      deps
    end
  end
end

defmodule Mix.Tasks.Deps.Compile do
  use Mix.Task

  @shortdoc "Compiles dependencies"

  @moduledoc """
  Compiles dependencies.

  By default, compile all dependencies. A list of dependencies
  can be given to compile multiple dependencies in order.

  This task attempts to detect if the project contains one of
  the following files and act accordingly:

    * `mix.exs` - invokes `mix compile`
    * `rebar.config` - invokes `rebar compile`
    * `Makefile.win`- invokes `nmake /F Makefile.win` (only on Windows)
    * `Makefile` - invokes `gmake` on DragonFlyBSD, FreeBSD, NetBSD, and OpenBSD,
      invokes `make` on any other operating system (except on Windows)

  The compilation can be customized by passing a `compile` option
  in the dependency:

      {:some_dependency, "0.1.0", compile: "command to compile"}

  If a list of dependencies is given, Mix will attempt to compile
  them as is. For example, if project `a` depends on `b`, calling
  `mix deps.compile a` will compile `a` even if `b` is out of
  date. This is to allow parts of the dependency tree to be
  recompiled without propagating those changes upstream. To ensure
  `b` is included in the compilation step, pass `--include-children`.

  ## Command line options

    * `--force` - force compilation of deps
    * `--skip-umbrella-children` - skips umbrella applications from compiling
    * `--skip-local-deps` - skips non-remote dependencies, such as path deps, from compiling

  """

  @switches [
    include_children: :boolean,
    force: :boolean,
    skip_umbrella_children: :boolean,
    skip_local_deps: :boolean
  ]

  @impl true
  def run(args) do
    unless "--no-archives-check" in args do
      Mix.Task.run("archive.check", args)
    end

    Mix.Project.get!()
    deps = Mix.Dep.load_and_cache()

    case OptionParser.parse(args, switches: @switches) do
      {opts, [], _} ->
        # Because this command may be invoked explicitly with
        # deps.compile, we simply try to compile any available
        # dependency.
        compile(Enum.filter(deps, &Mix.Dep.available?/1), opts)

      {opts, tail, _} ->
        compile(Mix.Dep.filter_by_name(tail, deps, opts), opts)
    end
  end

  @doc false
  def compile(deps, options \\ []) do
    shell = Mix.shell()
    config = Mix.Project.deps_config()
    Mix.Task.run("deps.precompile")

    compiled =
      deps
      |> reject_umbrella_children(options)
      |> reject_local_deps(options)
      |> Enum.map(fn %Mix.Dep{app: app, status: status, opts: opts, scm: scm} = dep ->
        check_unavailable!(app, status)
        maybe_clean(dep, options)

        compiled? =
          cond do
            not is_nil(opts[:compile]) ->
              do_compile(dep, config)

            Mix.Dep.mix?(dep) ->
              do_mix(dep, config)

            Mix.Dep.make?(dep) ->
              do_make(dep, config)

            dep.manager == :rebar ->
              do_rebar(dep, config)

            dep.manager == :rebar3 ->
              do_rebar3(dep, config)

            true ->
              shell.error(
                "Could not compile #{inspect(app)}, no \"mix.exs\", \"rebar.config\" or \"Makefile\" " <>
                  "(pass :compile as an option to customize compilation, set it to \"false\" to do nothing)"
              )

              false
          end

        unless Mix.Dep.mix?(dep), do: build_structure(dep, config)

        # We should touch fetchable dependencies even if they
        # did not compile otherwise they will always be marked
        # as stale, even when there is nothing to do.
        fetchable? = touch_fetchable(scm, opts[:build])

        compiled? and fetchable?
      end)

    if true in compiled, do: Mix.Task.run("will_recompile"), else: :ok
  end

  defp maybe_clean(dep, opts) do
    # If a dependency was marked as fetched or with an out of date lock
    # or missing the app file, we always compile it from scratch.
    if Keyword.get(opts, :force, false) or Mix.Dep.compilable?(dep) do
      File.rm_rf!(Path.join([Mix.Project.build_path(), "lib", Atom.to_string(dep.app)]))
    end
  end

  defp touch_fetchable(scm, path) do
    if scm.fetchable? do
      path = Path.join(path, ".mix")
      File.mkdir_p!(path)
      File.touch!(Path.join(path, "compile.fetch"))
      true
    else
      false
    end
  end

  defp check_unavailable!(app, {:unavailable, _}) do
    Mix.raise(
      "Cannot compile dependency #{inspect(app)} because " <>
        "it isn't available, run \"mix deps.get\" first"
    )
  end

  defp check_unavailable!(_, _) do
    :ok
  end

  defp do_mix(dep, _config) do
    Mix.Dep.in_dependency(dep, fn _ ->
      config = Mix.Project.config()

      if req = old_elixir_req(config) do
        Mix.shell().error(
          "warning: the dependency #{inspect(dep.app)} requires Elixir #{inspect(req)} " <>
            "but you are running on v#{System.version()}"
        )
      end

      try do
        options = [
          "--no-deps-loading",
          "--no-apps-loading",
          "--no-archives-check",
          "--no-elixir-version-check",
          "--no-warnings-as-errors"
        ]

        res = Mix.Task.run("compile", options)
        match?({:ok, _}, res)
      catch
        kind, reason ->
          app = dep.app

          Mix.shell().error(
            "could not compile dependency #{inspect(app)}, \"mix compile\" failed. " <>
              "You can recompile this dependency with \"mix deps.compile #{app}\", update it " <>
              "with \"mix deps.update #{app}\" or clean it with \"mix deps.clean #{app}\""
          )

          :erlang.raise(kind, reason, __STACKTRACE__)
      end
    end)
  end

  defp do_rebar(dep, config) do
    lib_path = Path.join(config[:env_path], "lib")
    cmd = "#{rebar_cmd(dep)} compile skip_deps=true deps_dir=#{inspect(lib_path)}"
    do_command(dep, config, cmd, false)
  end

  defp do_rebar3(%Mix.Dep{opts: opts} = dep, config) do
    dep_path = opts[:build]
    config_path = Path.join(dep_path, "mix.rebar.config")
    lib_path = Path.join(config[:env_path], "lib/*/ebin")

    # REBAR_BARE_COMPILER_OUTPUT_DIR is only honored by rebar3 >= 3.14
    env = [
      {"REBAR_CONFIG", config_path},
      {"TERM", "dumb"},
      {"REBAR_BARE_COMPILER_OUTPUT_DIR", dep_path}
    ]

    cmd = "#{rebar_cmd(dep)} bare compile --paths #{lib_path}"

    File.mkdir_p!(dep_path)
    File.write!(config_path, rebar_config(dep))
    do_command(dep, config, cmd, false, env)
  end

  defp rebar_config(dep) do
    dep.extra
    |> Mix.Rebar.dependency_config()
    |> Mix.Rebar.serialize_config()
  end

  defp rebar_cmd(%Mix.Dep{manager: manager} = dep) do
    Mix.Rebar.rebar_cmd(manager) || handle_rebar_not_found(dep)
  end

  defp handle_rebar_not_found(%Mix.Dep{app: app, manager: manager}) do
    shell = Mix.shell()

    shell.info(
      "Could not find \"#{manager}\", which is needed to build dependency #{inspect(app)}"
    )

    shell.info("I can install a local copy which is just used by Mix")

    install_question =
      "Shall I install #{manager}? (if running non-interactively, " <>
        "use \"mix local.rebar --force\")"

    unless shell.yes?(install_question) do
      error_message =
        "Could not find \"#{manager}\" to compile " <>
          "dependency #{inspect(app)}, please ensure \"#{manager}\" is available"

      Mix.raise(error_message)
    end

    (Mix.Tasks.Local.Rebar.run([]) && Mix.Rebar.local_rebar_cmd(manager)) ||
      Mix.raise("\"#{manager}\" installation failed")
  end

  defp do_make(dep, config) do
    command = make_command(dep)
    do_command(dep, config, command, true, [{"IS_DEP", "1"}])
  end

  defp make_command(dep) do
    makefile_win? = makefile_win?(dep)

    command =
      case :os.type() do
        {:win32, _} when makefile_win? ->
          "nmake /F Makefile.win"

        {:unix, type} when type in [:dragonfly, :freebsd, :netbsd, :openbsd] ->
          "gmake"

        _ ->
          "make"
      end

    if erlang_mk?(dep) do
      "#{command} clean && #{command}"
    else
      command
    end
  end

  defp do_compile(%Mix.Dep{opts: opts} = dep, config) do
    if command = opts[:compile] do
      do_command(dep, config, command, true)
    else
      false
    end
  end

  defp do_command(dep, config, command, print_app?, env \\ []) do
    %Mix.Dep{app: app, system_env: system_env, opts: opts} = dep

    File.cd!(opts[:dest], fn ->
      env = [{"ERL_LIBS", Path.join(config[:env_path], "lib")} | system_env] ++ env

      if Mix.shell().cmd(command, env: env, print_app: print_app?) != 0 do
        Mix.raise(
          "Could not compile dependency #{inspect(app)}, \"#{command}\" command failed. " <>
            "You can recompile this dependency with \"mix deps.compile #{app}\", update it " <>
            "with \"mix deps.update #{app}\" or clean it with \"mix deps.clean #{app}\""
        )
      end
    end)

    true
  end

  defp build_structure(%Mix.Dep{opts: opts} = dep, config) do
    build_path = Path.dirname(opts[:build])

    Enum.each(Mix.Dep.source_paths(dep), fn {source, base} ->
      app = Path.join(build_path, base)
      build_structure(source, app, config)
      Code.prepend_path(Path.join(app, "ebin"))
    end)
  end

  defp build_structure(dest, build, config) do
    File.cd!(dest, fn ->
      config = Keyword.put(config, :app_path, build)
      Mix.Project.build_structure(config, symlink_ebin: true)
    end)
  end

  defp old_elixir_req(config) do
    req = config[:elixir]

    if req && not Version.match?(System.version(), req) do
      req
    end
  end

  defp erlang_mk?(%Mix.Dep{opts: opts}) do
    File.regular?(Path.join(opts[:dest], "erlang.mk"))
  end

  defp makefile_win?(%Mix.Dep{opts: opts}) do
    File.regular?(Path.join(opts[:dest], "Makefile.win"))
  end

  defp reject_umbrella_children(deps, options) do
    if options[:skip_umbrella_children] do
      Enum.reject(deps, fn %{opts: opts} -> Keyword.get(opts, :from_umbrella) == true end)
    else
      deps
    end
  end

  defp reject_local_deps(deps, options) do
    if options[:skip_local_deps] do
      Enum.filter(deps, fn %{scm: scm} -> scm.fetchable? end)
    else
      deps
    end
  end
end


Work around and document for mix test on windows, closes #2375

Code.require_file "../../test_helper.exs", __DIR__

defmodule Mix.Tasks.LocalTest do
  use MixTest.Case

  defmodule ArchiveProject do
    def project do
      [ app: :archive, version: "0.1.0" ]
    end
  end

  defmodule ArchiveProject2 do
    def project do
      [ app: :archive, version: "0.2.0" ]
    end
  end

  test "archive" do
    File.rm_rf! tmp_path("userhome")
    System.put_env "MIX_HOME", tmp_path("userhome/.mix")
    Mix.Project.push(ArchiveProject)

    in_fixture "archive", fn() ->
      # Install it!
      Mix.Tasks.Archive.run []
      assert File.regular? "archive-0.1.0.ez"

      send self, {:mix_shell_input, :yes?, true}
      Mix.Tasks.Local.Install.run []
      assert File.regular? tmp_path("userhome/.mix/archives/archive-0.1.0.ez")

      archive = tmp_path("userhome/.mix/archives/archive-0.1.0.ez/archive-0.1.0/ebin")
      assert to_char_list(archive) in :code.get_path

      # List it!
      Mix.Local.append_archives
      Mix.Tasks.Local.run []
      assert_received {:mix_shell, :info, ["mix local.sample # A local install sample"]}

      # Run it!
      Mix.Task.run "local.sample"
      assert_received {:mix_shell, :info, ["sample"]}

      # Install new version!
      Mix.Project.push(ArchiveProject2)
      Mix.Tasks.Archive.run ["--no_compile"]
      assert File.regular? "archive-0.2.0.ez"

      send self, {:mix_shell_input, :yes?, true}
      Mix.Tasks.Local.Install.run []
      assert File.regular? tmp_path("userhome/.mix/archives/archive-0.2.0.ez")

      # We don't do the assertion below on Windows because
      # the archive is open by Erlang code server and the archive
      # is not effectively removed until the Erlang process exits.
      unless match? {:win32, _}, :os.type do
        refute File.regular? tmp_path("userhome/.mix/archives/archive-0.1.0.ez")
      end

      Mix.Local.append_archives

      # Remove it!
      send self, {:mix_shell_input, :yes?, true}
      Mix.Tasks.Local.Uninstall.run ["archive"]
      refute File.regular? tmp_path("userhome/.mix/archives/archive-0.2.0.ez")
    end
  end

  test "MIX_PATH" do
    File.rm_rf! tmp_path("mixpath")
    System.put_env "MIX_PATH", tmp_path("mixpath/ebin")

    File.mkdir_p! tmp_path("mixpath/ebin")
    Mix.Local.append_paths

    # Install on MIX_PATH manually
    File.copy! fixture_path("archive/ebin/Elixir.Mix.Tasks.Local.Sample.beam"),
               tmp_path("mixpath/ebin/Elixir.Mix.Tasks.Local.Sample.beam")

    # Run it
    Mix.Task.run "local.sample"
    assert_received {:mix_shell, :info, ["sample"]}
  after
    purge [Mix.Tasks.Local.Sample]
  end
end

Code.require_file "../../test_helper.exs", __DIR__

defmodule Mix.Tasks.LocalTest do
  use MixTest.Case

  defmodule ArchiveProject do
    def project do
      [ app: :archive, version: "0.1.0" ]
    end
  end

  defmodule ArchiveProject2 do
    def project do
      [ app: :archive, version: "0.2.0" ]
    end
  end

  test "archive" do
    File.rm_rf! tmp_path("userhome")
    System.put_env "MIX_HOME", tmp_path("userhome/.mix")
    Mix.Project.push(ArchiveProject)

    in_fixture "archive", fn() ->
      # Install it!
      Mix.Tasks.Archive.run []
      assert File.regular? "archive-0.1.0.ez"

      send self, {:mix_shell_input, :yes?, true}
      Mix.Tasks.Local.Install.run []
      assert File.regular? tmp_path("userhome/.mix/archives/archive-0.1.0.ez")

      archive = tmp_path("userhome/.mix/archives/archive-0.1.0.ez/archive-0.1.0/ebin")
      assert to_char_list(archive) in :code.get_path

      # List it!
      Mix.Local.append_archives
      Mix.Tasks.Local.run []
      assert_received {:mix_shell, :info, ["mix local.sample # A local install sample"]}

      # Run it!
      Mix.Task.run "local.sample"
      assert_received {:mix_shell, :info, ["sample"]}

      # Install new version!
      Mix.Project.push(ArchiveProject2)
      Mix.Tasks.Archive.run ["--no_compile"]
      assert File.regular? "archive-0.2.0.ez"

      send self, {:mix_shell_input, :yes?, true}
      Mix.Tasks.Local.Install.run []
      assert File.regular? tmp_path("userhome/.mix/archives/archive-0.2.0.ez")
      refute File.regular? tmp_path("userhome/.mix/archives/archive-0.1.0.ez")
      Mix.Local.append_archives

      # Remove it!
      send self, {:mix_shell_input, :yes?, true}
      Mix.Tasks.Local.Uninstall.run ["archive"]
      refute File.regular? tmp_path("userhome/.mix/archives/archive-0.2.0.ez")
    end
  end

  test "MIX_PATH" do
    File.rm_rf! tmp_path("mixpath")
    System.put_env "MIX_PATH", tmp_path("mixpath/ebin")

    File.mkdir_p! tmp_path("mixpath/ebin")
    Mix.Local.append_paths

    # Install on MIX_PATH manually
    File.copy! fixture_path("archive/ebin/Elixir.Mix.Tasks.Local.Sample.beam"),
               tmp_path("mixpath/ebin/Elixir.Mix.Tasks.Local.Sample.beam")

    # Run it
    Mix.Task.run "local.sample"
    assert_received {:mix_shell, :info, ["sample"]}
  after
    purge [Mix.Tasks.Local.Sample]
  end
end


return an error if the local build script fails with a non zero exit code

defmodule Nerves.Package.Providers.Local do
  @moduledoc """
  Builds an artifact locally.

  This provider will only function on certain Linux host configurations
  """

  @behaviour Nerves.Package.Provider

  alias Nerves.Package.Artifact
  import Mix.Nerves.Utils

  @doc """
  Builds an artifact locally.
  """
  @spec artifact(Nerves.Package.t, Nerves.Package.t, term) :: :ok
  def artifact(pkg, toolchain, opts) do
    {_, type} = :os.type
    build(type, pkg, toolchain, opts)
  end

  defp build(:linux, pkg, toolchain, _opts) do
    System.delete_env("BINDIR")
    dest = Artifact.dir(pkg, toolchain)
    File.rm_rf(dest)
    File.mkdir_p!(dest)

    script = Path.join(Nerves.Env.package(:nerves_system_br).path, "create-build.sh")
    platform_config = pkg.config[:platform_config][:defconfig]
    defconfig = Path.join("#{pkg.path}", platform_config)
    shell(script, [defconfig, dest])

    {:ok, pid} = Nerves.Utils.Stream.start_link(file: "build.log")
    stream = IO.stream(pid, :line)

    case shell("make", [], [cd: dest, stream: stream]) do
      {_, 0} -> :ok
      {error, _} -> {:error, error}
    end
  end

  defp build(type , _pkg, _toolchain, _opts) do
    {:error, """
    Local provider is not available for host system: #{type}
    Please use the Docker provider to build this package artifact
    """}
  end

  # def shell(_pkg, _opts) do
  #   :ok
  # end
  #
  # def clean(_pkg, _opts) do
  #   :ok
  # end
end

defmodule Nerves.Package.Providers.Local do
  @moduledoc """
  Builds an artifact locally.

  This provider will only function on certain Linux host configurations
  """

  @behaviour Nerves.Package.Provider

  alias Nerves.Package.Artifact
  import Mix.Nerves.Utils

  @doc """
  Builds an artifact locally.
  """
  @spec artifact(Nerves.Package.t, Nerves.Package.t, term) :: :ok
  def artifact(pkg, toolchain, opts) do
    {_, type} = :os.type
    build(type, pkg, toolchain, opts)
  end

  defp build(:linux, pkg, toolchain, _opts) do
    System.delete_env("BINDIR")
    dest = Artifact.dir(pkg, toolchain)
    File.rm_rf(dest)
    File.mkdir_p!(dest)

    script = Path.join(Nerves.Env.package(:nerves_system_br).path, "create-build.sh")
    platform_config = pkg.config[:platform_config][:defconfig]
    defconfig = Path.join("#{pkg.path}", platform_config)
    shell(script, [defconfig, dest])

    {:ok, pid} = Nerves.Utils.Stream.start_link(file: "build.log")
    stream = IO.stream(pid, :line)

    shell "make", [], [cd: dest, stream: stream]
    :ok
  end

  defp build(type , _pkg, _toolchain, _opts) do
    {:error, """
    Local provider is not available for host system: #{type}
    Please use the Docker provider to build this package artifact
    """}
  end

  # def shell(_pkg, _opts) do
  #   :ok
  # end
  #
  # def clean(_pkg, _opts) do
  #   :ok
  # end
end


Add more documentation

Add more documentation

defmodule WordsWithEnemies.WordFinder do
  @moduledoc """
  Uses the Stream API to efficiently search a wordlist
  for words based on certain criteria. Each function takes
  and returns a stream, allowing for easy chaining. When the
  chain is complete, a function in the `Enum` module must be
  used to retrieve the words. For example:

      words
      |> using("peiaewneolafdqe")
      |> between(min: 8, max: 12)
      |> starting_with("p")
      |> Enum.take(1)

      ["palinode"]
  """

  alias WordsWithEnemies.Letters

  @wordlist "priv/static/sowpods.txt"

  @doc """
  Transforms the wordlist into a list, and stores
  it in an agent for efficient access later on.
  """
  def start_link do
    words =
      @wordlist
      |> File.read!
      |> String.split("\n", trim: true)

    Agent.start_link(fn -> words end, name: __MODULE__)
  end

  @doc """
  Returns all words in the word list.
  """
  @spec words :: list
  def words, do: Agent.get(__MODULE__, &(&1))

  @doc """
  Returns a stream of `words` that can be made from `letters`.
  """
  @spec using(Enumerable.t, list) :: Enumerable.t
  @spec using(Enumerable.t, String.t) :: Enumerable.t

  def using(words, letters) when is_list(letters) do
    words |> Stream.filter(&possible?(&1, letters))
  end
  def using(words, letters) when is_bitstring(letters) do
    using(words, String.codepoints(letters))
  end

  defp possible?(word, letters) when length(word) > length(letters), do: false
  defp possible?(word, letters) when is_bitstring(word) do
    word
    |> String.codepoints
    |> do_possible?(letters)
  end

  defp do_possible?([], _letters), do: true
  defp do_possible?([letter|others], letters) do
    if letter in letters do
      do_possible?(others, List.delete(letters, letter))
    else
      false
    end
  end

  @doc """
  Returns a stream of `words` that are between `min`
  and `max` characters in length.
  """
  @spec between(Enumerable.t, list) :: Enumerable.t
  def between(words, [min: min]) do
    words |> Stream.filter(&min_length?(&1, min))
  end
  def between(words, [max: max]) do
    words |> Stream.filter(&max_length?(&1, max))
  end
  def between(words, [min: min, max: max]) do
    words |> Stream.filter(&length_between?(&1, min, max))
  end

  defp min_length?(word, min) do
    String.length(word) >= min
  end
  defp max_length?(word, max) do
    String.length(word) <= max
  end
  defp length_between?(word, min, max) do
    min_length?(word, min) and max_length?(word, max)
  end

  @doc """
  Returns a stream of `words` that meet the criteria in
  `constraints`. This is a map containing letters and
  the amount they should appear in the word. For example,
  `%{a: 4, k: 2}` will return all words with exactly 4 a's and 2 k's.
  """
  @spec containing(Enumerable.t, map, list) :: Enumerable.t
  def containing(words, constraints, opts \\ []) do
    opts = Keyword.put_new(opts, :precise, true)
    words |> Stream.filter(&contains?(&1, constraints, opts))
  end

  defp contains?(word, constraints, [precise: precise]) do
    word_freqs = Letters.frequency_table(word)
    constraints = atom_keys_to_string(constraints)

    if has_keys?(word_freqs, constraints) do
      word_keys = Map.keys(word_freqs)

      constraints
      |> Map.keys
      |> Enum.all?(&do_contains?(&1, word_freqs, constraints, precise))
    else
      false
    end
  end

  defp do_contains?(char, word_freqs, constraints, true) do
    word_freqs[char] == constraints[char]
  end
  defp do_contains?(char, word_freqs, constraints, false) do
    word_freqs[char] >= constraints[char]
  end

  defp has_keys?(checking, keys) do
    keys |> Map.keys |> Enum.all?(&(&1 in Map.keys(checking)))
  end

  defp atom_keys_to_string(map) do
    for {key, val} <- map, into: %{} do
      {Atom.to_string(key), val}
    end
  end

  @doc """
  Returns a stream of `words` that begin with `prefix`.
  """
  @spec starting_with(Enumerable.t, String.t | [String.t]) :: Enumerable.t
  def starting_with(words, prefix) do
    words |> Stream.filter(&String.starts_with?(&1, prefix))
  end

  @doc """
  Returns a stream of `words` that begin with `suffix`.
  """
  @spec ending_with(Enumerable.t, String.t | [String.t]) :: Enumerable.t
  def ending_with(words, suffix) do
    words |> Stream.filter(&String.ends_with?(&1, suffix))
  end

  @doc "Returns `true` if `word` is valid."
  @spec valid?(String.t) :: boolean
  def valid?(word), do: word in words

  @doc """
  Returns the similarity value of two words as a value
  from 0 - 1, taking into account the length of the words
  and the letters that the two have in common.

  ## Examples

    iex> WordFinder.similarity "pineapple", "apple"
    1

    iex> WordFinder.similarity "cozy", "fullers"
    0
  """
  @spec similar_to(Enumerable.t, String.t, integer) :: Enumerable.t
  def similar_to(words, compare_word, min_similarity) do
    Stream.filter(words, fn word ->
      similarity(word, compare_word) >= min_similarity
    end)
  end

  @spec similarity(String.t, String.t) :: integer
  def similarity(a, b) do
    a
    |> compare_words(b)
    |> do_similarity
  end

  defp do_similarity({[_|_], []}), do: 0
  defp do_similarity

  @doc ~S"""
  Compares the contents of two strings and removes
  any duplicates between them on a 1:1 basis.

  ## Examples

      iex> WordFinder.compare "pineapple", "planet"
      {["i", "p", "p", "e"], ["t"]}

      iex> WordFinder.compare "pineapple", "apple"
      {["p", "i", "n", "e"], []}
  """
  @spec compare_words(String.t, String.t) :: {String.t, String.t}
  @spec compare_words(list, list) :: {String.t, String.t}

  def compare_words(a, b) when is_bitstring(a) and is_bitstring(b) do
    a = a |> String.codepoints
    b = b |> String.codepoints
    compare_words(a, b)
  end
  def compare_words(word_a, word_b) do
    do_compare_words(word_a, word_a, word_b)
  end

  defp do_compare_words([], word_a, word_b), do: {word_a, word_b}
  defp do_compare_words([current|others], word_a, word_b) do
    if current in word_a and current in word_b do
      word_a = word_a |> List.delete(current)
      word_b = word_b |> List.delete(current)
      do_compare_words(others, word_a, word_b)
    else
      do_compare_words(others, word_a, word_b)
    end
  end
end

defmodule WordsWithEnemies.WordFinder do
  @moduledoc """
  Uses the Stream API to efficiently search a wordlist
  for words based on certain criteria. Each function takes
  and returns a stream, allowing for easy chaining. When the
  chain is complete, a function in the `Enum` module must be
  used to retrieve the words. For example:

      words
      |> using("peiaewneolafdqe")
      |> between(min: 8, max: 12)
      |> starting_with("p")
      |> Enum.take(1)

      ["palinode"]
  """

  alias WordsWithEnemies.Letters

  @wordlist "priv/static/sowpods.txt"

  def start_link do
    words = @wordlist
            |> File.read!
            |> String.split("\n", trim: true)

    Agent.start_link(fn -> words end, name: __MODULE__)
  end

  @doc """
  Returns all words in the word list.
  """
  @spec words :: list
  def words do
    Agent.get(__MODULE__, &(&1))
  end

  @doc """
  Returns a stream of `words` that can be made from `letters`.
  """
  @spec using(Enumerable.t, list) :: Enumerable.t
  @spec using(Enumerable.t, String.t) :: Enumerable.t

  def using(words, letters) when is_list(letters) do
    words |> Stream.filter(&possible?(&1, letters))
  end
  def using(words, letters) when is_bitstring(letters) do
    using(words, String.codepoints(letters))
  end

  defp possible?(word, letters) when length(word) > length(letters), do: false
  defp possible?(word, letters) when is_bitstring(word) do
    word
    |> String.codepoints
    |> do_possible?(letters)
  end

  defp do_possible?([], _letters), do: true
  defp do_possible?([letter|others], letters) do
    if letter in letters do
      do_possible?(others, List.delete(letters, letter))
    else
      false
    end
  end

  @doc """
  Returns a stream of `words` that are between `min`
  and `max` characters in length.
  """
  @spec between(Enumerable.t, list) :: Enumerable.t
  def between(words, [min: min]) do
    words |> Stream.filter(&min_length?(&1, min))
  end
  def between(words, [max: max]) do
    words |> Stream.filter(&max_length?(&1, max))
  end
  def between(words, [min: min, max: max]) do
    words |> Stream.filter(&length_between?(&1, min, max))
  end

  defp min_length?(word, min) do
    String.length(word) >= min
  end
  defp max_length?(word, max) do
    String.length(word) <= max
  end
  defp length_between?(word, min, max) do
    min_length?(word, min) and max_length?(word, max)
  end

  @doc """
  Returns a stream of `words` that meet the criteria in
  `constraints`. This is a map containing letters and
  the amount they should appear in the word. For example,
  `%{a: 4, k: 2}` will return all words with exactly 4 a's and 2 k's.
  """
  @spec containing(Enumerable.t, map, list) :: Enumerable.t
  def containing(words, constraints, opts \\ []) do
    opts = Keyword.put_new(opts, :precise, true)
    words |> Stream.filter(&contains?(&1, constraints, opts))
  end

  defp contains?(word, constraints, [precise: precise]) do
    word_freqs = Letters.frequency_table(word)
    constraints = atom_keys_to_string(constraints)

    if has_keys?(word_freqs, constraints) do
      word_keys = Map.keys(word_freqs)

      constraints
      |> Map.keys
      |> Enum.all?(&do_contains?(&1, word_freqs, constraints, precise))
    else
      false
    end
  end

  defp do_contains?(char, word_freqs, constraints, true) do
    word_freqs[char] == constraints[char]
  end
  defp do_contains?(char, word_freqs, constraints, false) do
    word_freqs[char] >= constraints[char]
  end

  defp has_keys?(checking, keys) do
    keys |> Map.keys |> Enum.all?(&(&1 in Map.keys(checking)))
  end

  defp atom_keys_to_string(map) do
    for {key, val} <- map, into: %{} do
      {Atom.to_string(key), val}
    end
  end

  @doc """
  Returns a stream of `words` that begin with `prefix`.
  """
  @spec starting_with(Enumerable.t, String.t | [String.t]) :: Enumerable.t
  def starting_with(words, prefix) do
    words |> Stream.filter(&String.starts_with?(&1, prefix))
  end

  @doc """
  Returns a stream of `words` that begin with `suffix`.
  """
  @spec ending_with(Enumerable.t, String.t | [String.t]) :: Enumerable.t
  def ending_with(words, suffix) do
    words |> Stream.filter(&String.ends_with?(&1, suffix))
  end

  @doc "Returns `true` if `word` is valid."
  @spec valid?(String.t) :: boolean
  def valid?(word), do: word in words

  @doc """
  Returns the similarity value of two words as a value
  from 0 - 1, taking into account the length of the words
  and the letters that the two have in common.

  ## Examples

    iex> WordFinder.similarity "pineapple", "apple"
    1

    iex> WordFinder.similarity "cozy", "fullers"
    0
  """
  @spec similar_to(Enumerable.t, String.t, integer) :: Enumerable.t
  def similar_to(words, compare_word, min_similarity) do
    Stream.filter(words, fn word ->
      similarity(word, compare_word) >= min_similarity
    end)
  end

  @spec similarity(String.t, String.t) :: integer
  def similarity(a, b) do
    a
    |> compare_words(b)
    |> do_similarity
  end

  defp do_similarity({[_|_], []}), do: 0
  defp do_similarity

  @doc ~S"""
  Compares the contents of two strings and removes
  any duplicates between them on a 1:1 basis.

  ## Examples

      iex> WordFinder.compare "pineapple", "planet"
      {["i", "p", "p", "e"], ["t"]}

      iex> WordFinder.compare "pineapple", "apple"
      {["p", "i", "n", "e"], []}
  """
  @spec compare_words(String.t, String.t) :: {String.t, String.t}
  @spec compare_words(list, list) :: {String.t, String.t}

  def compare_words(a, b) when is_bitstring(a) and is_bitstring(b) do
    a = a |> String.codepoints
    b = b |> String.codepoints
    compare_words(a, b)
  end
  def compare_words(word_a, word_b) do
    do_compare_words(word_a, word_a, word_b)
  end

  defp do_compare_words([], word_a, word_b), do: {word_a, word_b}
  defp do_compare_words([current|others], word_a, word_b) do
    if current in word_a and current in word_b do
      word_a = word_a |> List.delete(current)
      word_b = word_b |> List.delete(current)
      do_compare_words(others, word_a, word_b)
    else
      do_compare_words(others, word_a, word_b)
    end
  end
end


Fix typo

Fix typo

defmodule FarmbotCore.Asset.PointGroup do
  @moduledoc """
  PointGroup is a list of points referenced by API id
  """

  use FarmbotCore.Asset.Schema, path: "/api/point_groups"

  schema "point_groups" do
    field(:id, :id)

    has_one(:local_meta, FarmbotCore.Asset.Private.LocalMeta,
      on_delete: :delete_all,
      references: :local_id,
      foreign_key: :asset_local_id
    )

    field(:name, :string)
    field(:point_ids, {:array, :integer})
    field(:sort_type, :string)
    field(:criteria, :map)

    field(:monitor, :boolean, default: true)
    timestamps()
  end

  view point_group do
    %{
      id: point_group.id,
      name: point_group.name,
      point_ids: point_group.point_ids,
      sort_type: point_group.sort_type
    }
  end

  def changeset(point_group, params \\ %{}) do
    point_group
    |> cast(params, [:id, :name, :point_ids, :sort_type, :monitor, :created_at, :updated_at])
    |> validate_required([])
  end
end

defmodule FarmbotCore.Asset.PointGroup do
  @moduledoc """
  PointGroup is a list of points referenced by API id
  """

  use FarmbotCore.Asset.Schema, path: "/api/point_groups"

  schema "point_groups" do
    field(:id, :id)

    has_one(:local_meta, FarmbotCore.Asset.Private.LocalMeta,
      on_delete: :delete_all,
      references: :local_id,
      foreign_key: :asset_local_id
    )

    field(:name, :string)
    field(:point_ids, {:array, :integer})
    field(:sort_type, :string)
    field(:meta, :map)

    field(:monitor, :boolean, default: true)
    timestamps()
  end

  view point_group do
    %{
      id: point_group.id,
      name: point_group.name,
      point_ids: point_group.point_ids,
      sort_type: point_group.sort_type
    }
  end

  def changeset(point_group, params \\ %{}) do
    point_group
    |> cast(params, [:id, :name, :point_ids, :sort_type, :monitor, :created_at, :updated_at])
    |> validate_required([])
  end
end


Accept bot [] and %{} as params at plugin.list.


Switch to one-line fn head

Switch to one-line fn head

defmodule CodeGeneration do

  # input parsed instructions and the completed symbol table

  def compile([{:class, inner} | rest]) do
    class_code = compile_class(inner)
    {code, rest1} = compile(rest)
    {class_code ++ code, rest1}
  end

  def compile(_all), do: {[], []}

  def compile_class([{:keyword, "class"},
                     {:identifier, class_name, :attr, _},
                     {:symbol, "{"}
                     | rest]) do
    compile_class(class_name, rest)
  end

  def compile_class(class_name,
        [{:subroutineDec, inner} | rest]) do
    compile_subroutine_dec(class_name, inner)
    ++ compile_class(class_name, rest)
  end

  def compile_class(_class_name, [{:symbol, "}"}]), do: []

  def compile_subroutine_dec(class_name,
        [{:keyword, "function"},
         {keyword_or_identifier, _return_type},
         {:identifier, fn_name, :attr, _},
         {:symbol, "("},
         {:parameterList, _param_list},
         {:symbol, ")"},
         {:subroutineBody, body_parsed}])
  when keyword_or_identifier in [:keyword, :identifier] do
    body_vm = compile_subroutine_body(body_parsed)
    ["function #{class_name}.#{fn_name} #{number_of_locals(body_parsed)}"]
    ++ body_vm
  end

  def compile_subroutine_body([{:symbol, "{"} | rest]) do
    {locals, rest1} = compile_var_dec(rest)
    {statements, rest2} = compile_statements(rest1)
    [{:symbol, "}"}] = rest2
    locals ++ statements
  end

  def compile_var_dec([{:varDec, _} | rest]), do: compile_var_dec(rest)

  def compile_var_dec(all), do: {[], all}

  def compile_statements([{:statements, statements_parsed} | rest]) do
    {compile_statement(statements_parsed), rest}
  end

  def compile_statement([{:doStatement, do_parsed} | rest]) do
    compile_do_statement(do_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([{:letStatement, let_parsed} | rest]) do
    compile_let_statement(let_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([{:whileStatement, while_parsed} | rest]) do
    compile_while_statement(while_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([{:ifStatement, if_parsed} | rest]) do
    compile_if_statement(if_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([{:returnStatement, return_parsed} | rest]) do
    compile_return_statement(return_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([]), do: []

  def compile_return_statement([{:keyword, "return"},
                                {:symbol, ";"}]) do
    ["return"]
  end

  def compile_return_statement([{:keyword, "return"},
                                {:expression, exp_parsed},
                                {:symbol, ";"}]) do
    compile_exp(exp_parsed)
    ++ ["return"]
  end

  def segment_of(:static), do: "static"
  def segment_of(:field),  do: "this"
  def segment_of(:arg),    do: "argument"
  def segment_of(:var),    do: "local"

  def compile_let_statement([{:keyword, "let"},
                             {:identifier, _var_name, :attr, %{kind: kind, index: index}},
                             {:symbol, "="},
                             {:expression, exp_parsed},
                             {:symbol, ";"}]) do

    compile_exp(exp_parsed)
    ++ ["pop #{segment_of(kind)} #{index}"]
  end

  def compile_do_statement([{:keyword, "do"},
                            {:identifier, class_name, :attr, _},
                            {:symbol, "."},
                            {:identifier, fn_name, :attr, _},
                            {:symbol, "("},
                            {:expressionList, exp_list_parsed},
                            {:symbol, ")"},
                            {:symbol, ";"}]) do
    compile_exp_list(exp_list_parsed)
    ++ ["call #{class_name}.#{fn_name} #{number_of_expressions(exp_list_parsed)}"]
  end

  def compile_while_statement([{:keyword, "while"},
                               {:symbol, "("},
                               {:expression, exp_parsed},
                               {:symbol, ")"},
                               {:symbol, "{"} | rest]) do
    label1 = "whileL1$#{JackUuid.generate()}"
    label2 = "whileL2$#{JackUuid.generate()}"
    exp = compile_exp(exp_parsed)
    {statements, rest1} = compile_statements(rest)
    [{:symbol, "}"}] = rest1
    ["label #{label1}"]
    ++ exp
    ++ ["not",
        "if-goto #{label2}"]
    ++ statements
    ++ ["goto #{label1}",
        "label #{label2}"]
  end

  def compile_if_statement([{:keyword, "if"},
                            {:symbol, "("},
                            {:expression, exp_parsed},
                            {:symbol, ")"},
                            {:symbol, "{"},
                            {:statements, statements_parsed},
                            {:symbol, "}"},
                            {:keyword, "else"},
                            {:symbol, "{"},
                            {:statements, else_parsed},
                            {:symbol, "}"}]) do
    label1 = "ifL1$#{JackUuid.generate()}"
    label2 = "ifL2$#{JackUuid.generate()}"
    exp = compile_exp(exp_parsed)
    statements = compile_statement(statements_parsed)
    else_statements = compile_statement(else_parsed)
    exp
    ++ ["not",
        "if-goto #{label1}"]
    ++ statements
    ++ ["goto #{label2}",
        "label #{label1}"]
    ++ else_statements
    ++ ["label #{label2}"]
  end

  def compile_exp_list([{:expression, exp_parsed} | more]) do
    compile_exp(exp_parsed) ++ compile_exp_list(more)
  end

  def compile_exp_list([{:symbol, ","} | more]), do: compile_exp_list(more)

  def compile_exp_list([]), do: []

  def compile_exp([{:term, term} | more]) do
    compile_term(term) ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "+"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["add"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "-"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["sub"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "*"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["call Math.multiply 2"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "/"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["call Math.divide 2"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "&"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["and"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "|"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["or"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "<"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["lt"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, ">"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["gt"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "="},
                   {:term, term} | more]) do
    compile_term(term) ++ ["eq"] ++ compile_exp(more)
  end

  def compile_exp([]), do: []

  def compile_term([{:keyword, "true"}]), do: ["push constant 1", "neg"]
  def compile_term([{:keyword, "false"}]), do: ["push constant 0"]

  def compile_term([{:identifier, _var_name, :attr, %{kind: kind, index: index}}]) do
    ["push #{segment_of(kind)} #{index}"]
  end

  def compile_term([{:symbol, "-"},{:term, term}]) do
    compile_term(term) ++ ["neg"]
  end

  def compile_term([{:symbol, "~"},{:term, term}]) do
    compile_term(term) ++ ["not"]
  end

  def compile_term([{:integerConstant, i}]) do
    ["push constant #{i}"]
  end

  def compile_term([{:symbol, "("},
                    {:expression, exp_parsed},
                    {:symbol, ")"}]) do
    compile_exp(exp_parsed)
  end

  def compile_term([{:identifier, class_name, :attr, _},
                    {:symbol, "."},
                    {:identifier, fn_name, :attr, %{category: :subroutine}},
                    {:symbol, "("},
                    {:expressionList, exp_list_parsed},
                    {:symbol, ")"}]) do
    compile_exp_list(exp_list_parsed)
    ++
    ["call #{class_name}.#{fn_name} #{number_of_expressions(exp_list_parsed)}"]
  end

  def compile_term([]), do: []

  def number_of_expressions(exp_list) do
    Enum.filter(exp_list, fn (tup) ->
      List.keymember?([tup], :expression, 0)
    end) |> length()
  end

  def number_of_locals(subroutine_body) do
    Enum.filter(subroutine_body, fn(tup) ->
      List.keymember?([tup], :varDec, 0)
    end)
    |> Enum.flat_map(fn ({:varDec, v}) ->
      Enum.filter(v, fn(tup) ->
        List.keymember?([tup], :identifier, 0)
      end)
    end)
    |> length()
  end

end

defmodule CodeGeneration do

  # input parsed instructions and the completed symbol table

  def compile([{:class, inner} | rest]) do
    class_code = compile_class(inner)
    {code, rest1} = compile(rest)
    {class_code ++ code, rest1}
  end

  def compile(_all), do: {[], []}

  def compile_class([{:keyword, "class"},
                     {:identifier, class_name, :attr, _},
                     {:symbol, "{"}
                     | rest]) do
    compile_class(class_name, rest)
  end

  def compile_class(class_name,
        [{:subroutineDec, inner} | rest]) do
    compile_subroutine_dec(class_name, inner)
    ++ compile_class(class_name, rest)
  end

  def compile_class(_class_name, [{:symbol, "}"}]) do
    []
  end

  def compile_subroutine_dec(class_name,
        [{:keyword, "function"},
         {keyword_or_identifier, _return_type},
         {:identifier, fn_name, :attr, _},
         {:symbol, "("},
         {:parameterList, _param_list},
         {:symbol, ")"},
         {:subroutineBody, body_parsed}])
  when keyword_or_identifier in [:keyword, :identifier] do
    body_vm = compile_subroutine_body(body_parsed)
    ["function #{class_name}.#{fn_name} #{number_of_locals(body_parsed)}"]
    ++ body_vm
  end

  def compile_subroutine_body([{:symbol, "{"} | rest]) do
    {locals, rest1} = compile_var_dec(rest)
    {statements, rest2} = compile_statements(rest1)
    [{:symbol, "}"}] = rest2
    locals ++ statements
  end

  def compile_var_dec([{:varDec, _} | rest]), do: compile_var_dec(rest)

  def compile_var_dec(all), do: {[], all}

  def compile_statements([{:statements, statements_parsed} | rest]) do
    {compile_statement(statements_parsed), rest}
  end

  def compile_statement([{:doStatement, do_parsed} | rest]) do
    compile_do_statement(do_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([{:letStatement, let_parsed} | rest]) do
    compile_let_statement(let_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([{:whileStatement, while_parsed} | rest]) do
    compile_while_statement(while_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([{:ifStatement, if_parsed} | rest]) do
    compile_if_statement(if_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([{:returnStatement, return_parsed} | rest]) do
    compile_return_statement(return_parsed)
    ++ compile_statement(rest)
  end

  def compile_statement([]), do: []

  def compile_return_statement([{:keyword, "return"},
                                {:symbol, ";"}]) do
    ["return"]
  end

  def compile_return_statement([{:keyword, "return"},
                                {:expression, exp_parsed},
                                {:symbol, ";"}]) do
    compile_exp(exp_parsed)
    ++ ["return"]
  end

  def segment_of(:static), do: "static"
  def segment_of(:field),  do: "this"
  def segment_of(:arg),    do: "argument"
  def segment_of(:var),    do: "local"

  def compile_let_statement([{:keyword, "let"},
                             {:identifier, _var_name, :attr, %{kind: kind, index: index}},
                             {:symbol, "="},
                             {:expression, exp_parsed},
                             {:symbol, ";"}]) do

    compile_exp(exp_parsed)
    ++ ["pop #{segment_of(kind)} #{index}"]
  end

  def compile_do_statement([{:keyword, "do"},
                            {:identifier, class_name, :attr, _},
                            {:symbol, "."},
                            {:identifier, fn_name, :attr, _},
                            {:symbol, "("},
                            {:expressionList, exp_list_parsed},
                            {:symbol, ")"},
                            {:symbol, ";"}]) do
    compile_exp_list(exp_list_parsed)
    ++ ["call #{class_name}.#{fn_name} #{number_of_expressions(exp_list_parsed)}"]
  end

  def compile_while_statement([{:keyword, "while"},
                               {:symbol, "("},
                               {:expression, exp_parsed},
                               {:symbol, ")"},
                               {:symbol, "{"} | rest]) do
    label1 = "whileL1$#{JackUuid.generate()}"
    label2 = "whileL2$#{JackUuid.generate()}"
    exp = compile_exp(exp_parsed)
    {statements, rest1} = compile_statements(rest)
    [{:symbol, "}"}] = rest1
    ["label #{label1}"]
    ++ exp
    ++ ["not",
        "if-goto #{label2}"]
    ++ statements
    ++ ["goto #{label1}",
        "label #{label2}"]
  end

  def compile_if_statement([{:keyword, "if"},
                            {:symbol, "("},
                            {:expression, exp_parsed},
                            {:symbol, ")"},
                            {:symbol, "{"},
                            {:statements, statements_parsed},
                            {:symbol, "}"},
                            {:keyword, "else"},
                            {:symbol, "{"},
                            {:statements, else_parsed},
                            {:symbol, "}"}]) do
    label1 = "ifL1$#{JackUuid.generate()}"
    label2 = "ifL2$#{JackUuid.generate()}"
    exp = compile_exp(exp_parsed)
    statements = compile_statement(statements_parsed)
    else_statements = compile_statement(else_parsed)
    exp
    ++ ["not",
        "if-goto #{label1}"]
    ++ statements
    ++ ["goto #{label2}",
        "label #{label1}"]
    ++ else_statements
    ++ ["label #{label2}"]
  end

  def compile_exp_list([{:expression, exp_parsed} | more]) do
    compile_exp(exp_parsed) ++ compile_exp_list(more)
  end

  def compile_exp_list([{:symbol, ","} | more]), do: compile_exp_list(more)

  def compile_exp_list([]), do: []

  def compile_exp([{:term, term} | more]) do
    compile_term(term) ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "+"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["add"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "-"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["sub"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "*"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["call Math.multiply 2"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "/"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["call Math.divide 2"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "&"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["and"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "|"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["or"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "<"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["lt"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, ">"},
                   {:term, term} | more]) do
    compile_term(term) ++ ["gt"] ++ compile_exp(more)
  end

  def compile_exp([{:symbol, "="},
                   {:term, term} | more]) do
    compile_term(term) ++ ["eq"] ++ compile_exp(more)
  end

  def compile_exp([]), do: []

  def compile_term([{:keyword, "true"}]), do: ["push constant 1", "neg"]
  def compile_term([{:keyword, "false"}]), do: ["push constant 0"]

  def compile_term([{:identifier, _var_name, :attr, %{kind: kind, index: index}}]) do
    ["push #{segment_of(kind)} #{index}"]
  end

  def compile_term([{:symbol, "-"},{:term, term}]) do
    compile_term(term) ++ ["neg"]
  end

  def compile_term([{:symbol, "~"},{:term, term}]) do
    compile_term(term) ++ ["not"]
  end

  def compile_term([{:integerConstant, i}]) do
    ["push constant #{i}"]
  end

  def compile_term([{:symbol, "("},
                    {:expression, exp_parsed},
                    {:symbol, ")"}]) do
    compile_exp(exp_parsed)
  end

  def compile_term([{:identifier, class_name, :attr, _},
                    {:symbol, "."},
                    {:identifier, fn_name, :attr, %{category: :subroutine}},
                    {:symbol, "("},
                    {:expressionList, exp_list_parsed},
                    {:symbol, ")"}]) do
    compile_exp_list(exp_list_parsed)
    ++
    ["call #{class_name}.#{fn_name} #{number_of_expressions(exp_list_parsed)}"]
  end

  def compile_term([]), do: []

  def number_of_expressions(exp_list) do
    Enum.filter(exp_list, fn (tup) ->
      List.keymember?([tup], :expression, 0)
    end) |> length()
  end

  def number_of_locals(subroutine_body) do
    Enum.filter(subroutine_body, fn(tup) ->
      List.keymember?([tup], :varDec, 0)
    end)
    |> Enum.flat_map(fn ({:varDec, v}) ->
      Enum.filter(v, fn(tup) ->
        List.keymember?([tup], :identifier, 0)
      end)
    end)
    |> length()
  end

end