lib/mix/tasks/setup.ex

#
#  Created by Boyd Multerer on August, 2018.
#  Copyright © 2018 Kry10 Industries. All rights reserved.
#

defmodule Mix.Tasks.Scenic.Setup do
  @moduledoc """
  Does much of the work to set up Scenic in an existing project, such as Nerves.

  The typical use of this task is to install Scenic into a Nerves project. This assumes
  that you have already installed Nerves.

  [You should read use the Nerves Installation Guide.](https://hexdocs.pm/nerves/installation.html)


  ### Create a new project

  Then create a new Nerves project and set up Scenic within it

  ```bash
  mix nerves.new hello_nerves
  cd hello_nerves
  mix scenic.setup
  ```

  This also works to set up Scenic in a blank Elixir project

  ```bash
  mix new hello_world
  cd hello_world
  mix scenic.setup
  ```

  At this point, the main file structures are set up, but not completely hooked together.

  ### Set up the Scenic dependency

  Add the following lines the this list of deps in the project's mix.exs file. Notice that they
  are usable for all the Nerves targets. (Actually, the local driver doesn't work for bbb yet
  and is very slow and needs work on rpi4, but the point is that it works across host and
  the device target...)

  ```elixir
  {:scenic, "~> 0.11.0-beta.0"},
  {:scenic_driver_local, "~> 0.11.0-beta.0"},
  ```

  ### Make sure your application is configured to start

  This should be in your `mix.exs` file. If using Nerves, it will start `MyApplication.Application`

  ```elixir
  def application do
    [
      mod: {MyApplication, []},
      extra_applications: [:logger, :scenic]
    ]
  end
  ```

  ### Add Scenic to your app's supervisor

  Next, you need to add Scenic to your app's supervisor so that it starts scenic.
  Something like this should be in your `MyApp` or `MyApp.Application` module.

  ```elixir
  def start(_type, _args) do
    # start the application with the configured viewport
    viewport_config = Application.get_env(:<%= @app %>, :viewport)
    children = [
      {Scenic, [viewport_config]},
      <%= @mod %>.PubSub.Supervisor
    ]
    |> Supervisor.start_link( strategy: :one_for_one )
  end
  ```

  ### Configure your assets

  Add the following to your config.exs file. Change the app name as appropriate. This
  configuration is usually the same for all targets.

  ```elixir
  config :scenic, :assets, module: MyApplication.Assets
  ```

  ### Configure your ViewPort

  Next, you need to configure your ViewPort. This instructs Scenic how to draw
  to the screen, or window. This is typically different for the various Nerves
  targets.

  The following example would go in the host.exs file. Or, if this is just a regular
  elixir project running on a Mac/PC/Linux machine, it could go in config.exs

  ```elixir
  config :my_application, :viewport,
    size: {800, 600},
    theme: :dark,
    default_scene: MyApplication.Scene.Home,
    drivers: [
      [
        module: Scenic.Driver.Local,
        window: [title: "My Application"],
        on_close: :stop_system
      ]
    ]
  ```

  This configuration could be for a Nerves device. In this case an rpi3, that I've been
  using, but it could be any device with a fixed screen.

  ```elixir
  config :my_application, :viewport,
    size: {800, 600},
    theme: :dark,
    default_scene: MyApplication.Scene.Home,
    drivers: [
      [
        module: Scenic.Driver.Local,
        position: [scaled: true, centered: true, orientation: :normal],
      ]
    ]
  ```

  Scenic.Driver.Local has quite a few options you can set.
  Please see it's documentation for more.

  ### Get the Dependencies and Run

  You should now be ready to fetch the dependencies and run your project. Run these
  commands from within your project's main directory.

  ```bash
  mix deps.get
  iex -S mix
  ```

  ## The Starter Application

  The starter application created by the generator adds a minimal set of Scenic files
  displays information about the system it is running on.

  ## What to read next

  Next, you should read guides describing the overall Scenic structure. This is
  in the documentation for Scenic itself
  """
  use Mix.Task

  import Mix.Generator
  alias ScenicNew.Common

  @shortdoc "Creates a new Scenic v#{Common.scenic_version()} application"

  @switches [
    app: :string,
    module: :string
  ]

  import IEx

  # --------------------------------------------------------
  def run(argv) do
    {opts, argv} = OptionParser.parse!(argv, strict: @switches)
    Common.elixir_version_check!()

    path =
      case argv do
        [] -> "."
        [path | _] -> path
      end

    app = opts[:app] || Path.basename(Path.expand(path))
    Common.check_application_name!(app, !opts[:app])
    mod = opts[:module] || Macro.camelize(app)
    Common.check_mod_name_validity!(mod)
    Common.check_mod_name_availability!(mod)

    pry()

    File.cd!(path, fn ->
      generate(app, mod, path, opts)
    end)
  end

  # --------------------------------------------------------
  defp generate(app, mod, _path, _opts) do
    assigns = [
      app: app,
      mod: mod,
      elixir_version: get_version(System.version()),
      scenic_version: Common.scenic_version()
    ]

    create_file("lib/assets.ex", assets_template(assigns))

    create_directory("assets")
    create_file("assets/readme.txt", Common.assets_readme(assigns))

    create_directory("lib/scenes")
    create_file("lib/scenes/home.ex", scene_home_template(assigns))
    create_file("lib/scenes/readme.txt", Common.scene_readme(assigns))

    create_directory("lib/components")
    create_file("lib/components/readme.txt", Common.comp_readme(assigns))

    create_directory("lib/pubsub")
    create_file("lib/pubsub/supervisor.ex", pubsub_sup_template(assigns))
    create_file("lib/pubsub/readme.txt", Common.pubsub_readme(assigns))

    """

    The Scenic files were added successfully.

    You still need to configure it.

    ------
    Add the following lines the this list of deps in the project's mix.exs file. Notice that they
    are usable for all the Nerves targets. (Actually, the local driver doesn't work for bbb yet
    and is very slow and needs work on rpi4, but the point is that it works across host and
    the device target...)

        {:scenic, "~> #{Common.scenic_version()}"},
        {:scenic_driver_local, "~> #{Common.scenic_version()}"},


    ------
    Make sure your mix.exs file starts your application and scenic

        def application do
          [
            mod: {#{mod}, []},
            extra_applications: [:logger, :scenic]
          ]
        end


    ------
    Add Scenic to your app's supervisor so that it starts scenic.
    Something like this should be in your `#{app}.ex` module.

        def start(_type, _args) do
          # start the application with the configured viewport
          viewport_config = Application.get_env(:#{app}, :viewport)
          children = [
            {Scenic, [viewport_config]},
            #{mod}.PubSub.Supervisor
          ]
          |> Supervisor.start_link( strategy: :one_for_one )
        end


    ------
    Add the following to your config.exs file. Change the app name as appropriate. This
    configuration is usually the same for all targets.

        config :scenic, :assets, module: #{mod}.Assets


    ------
    Configure your ViewPort. This instructs Scenic how to draw
    to the screen, or window. This is typically different for the various Nerves
    targets. The following example would go in the host.exs file. Or, if this is
    just a regular elixir project running on a Mac/PC/Linux machine,
    it could go in config.exs

        config :#{app}, :viewport,
          size: {800, 600},
          theme: :dark,
          default_scene: #{mod}.Scene.Home,
          drivers: [
            [
              module: Scenic.Driver.Local,
              window: [title: "#{app}"],
              on_close: :stop_system
            ]
          ]

    This configuration could be for a Nerves device.

        config :#{app}, :viewport,
          size: {800, 600},
          theme: :dark,
          default_scene: #{mod}.Scene.Home,
          drivers: [
            [
              module: Scenic.Driver.Local,
              position: [scaled: true, centered: true, orientation: :normal],
            ]
          ]


    ------
    Finally, build and run your app:

        $ mix deps.get

    You can start your app with:

        $ mix scenic.run

    You can also run it interactively like this:

        $ iex -S mix

    """
    |> Mix.shell().info()
  end

  # --------------------------------------------------------
  defp get_version(version) do
    {:ok, version} = Version.parse(version)

    "#{version.major}.#{version.minor}" <>
      case version.pre do
        [h | _] -> "-#{h}"
        [] -> ""
      end
  end

  # ============================================================================
  # template files
  templates = [
    # formatter: "templates/formatter.exs",
    # gitignore: "templates/gitignore",
    assets: "templates/new/lib/assets.ex.eex",
    pubsub_sup: "templates/new/lib/pubsub/supervisor.ex.eex",
    scene_home: "templates/new/lib/scenes/home.ex.eex"
  ]

  Enum.each(templates, fn {name, content} ->
    embed_template(name, from_file: content)
  end)
end