Packages

Make scripting easy with a simple pattern and help generation.

Current section

Files

Jump to
scripting generator
Raw

generator

#!/usr/bin/env elixir
# -*- elixir -*-
# Build embedding preamble for scripting
[ { module, binary } ] = Code.compile_file("lib/scripting.ex")
Process.put(:scripting_binary, binary)

defmodule Generator do
  use Scripting

  @switches [ output_file: :string ]
  @aliases [ o: :output_file ]

  @moduledoc """
  A suite of commands for generating starting scaffolding for a Scripting based
  script or to add Script module embedding to an existing script.

  For loading a companion BEAM file instead of embedding, options are:

  1. Put Elixir.Scripting.beam in the same directory as your script, add
    this line to the top:

    Code.prepend_path( Path.dirname( __ENV__.file ) )

  2. Put Elixir.Scripting.beam in a common direcory, such as ebin
    in the user's home dir:

    Code.prepend_path( Path.join( System.user_home(), "ebin"))

  Either way, generate the beam file with:
  elixirc lib/scripting.ex

  """
  @common_template """
  defmodule <%= module %> do
    use Scripting
    @switches []
    @aliases []
    @moduledoc "Put the documentation header here"
    def help_footer, do: "end of help for <%= module %>"

    @doc "Sample command starting point"
    command( "command_name", [ "cn"] ) do
      IO.inspect( [ positional_args: args, parsed_options: opts ])
      IO.puts("This is your first command")
    end

    command_default()
  end
  <%= module %>.run()
  """

  # erlang options:
  # +B disable emulator break handler
  # -noshell runs without a shell so that Unix pipes work properly
  @interpreter_directive ~S|#!/usr/bin/env elixir --erl +B --erl -noshell| <> "\n"
  @preamble_start @interpreter_directive <> "# -- Scripting preamble start --\n"
  @preamble_end "\n# -- Scripting preamble end --\n\n"

  def help_footer do
    """
    Option aliases:
    #{inspect(@aliases, pretty: true)}
    """
  end
  def write_output( content, out ) do
    File.write( out, content )
    File.chmod!( out, 0o755 )
    IO.puts( "wrote #{out}")
  end

  def generate_with_source( body ) do
    [ @preamble_start,
      "# by ",
      __ENV__.file,
      "\n",
      File.read!("lib/scripting.ex"),
      @preamble_end,
      body
    ]
  end

  def generate_with_beam( body ) do
    scripting_beam = Base.encode64(Process.get(:scripting_binary))
    otp = :erlang.system_info(:otp_release)

    [ @preamble_start,
      "if :erlang.system_info(:otp_release) < '", otp,
      "', do: raise ~S|The emulator is less than the included BEAM file|\n",
      "# by " <> __ENV__.file <> " on OTP:#{otp}\n",
    """
    scripting_beam="#{scripting_beam}"
    :code.load_binary( Scripting, 'Elixir.Scripting.beam', Base.decode64!(scripting_beam))
    """,
      @preamble_end,
      body ]
  end

  @doc """
  <module name>
  Generate an elixir script file with Scripting embedded and include
  the basic scaffolding
  Required options:
  --output-file <filename>
  """
  command("generate-with-beam", [ "gb" ]) do
    out = Keyword.fetch!(opts, :output_file)
    [ module ] = args

    EEx.eval_string( @common_template, module: module )
    |> generate_with_beam()
    |> write_output( out )
  end

  @doc """
  <module name>
  Generate scripting scaffold with source of Scripting module
  required options:
  --output-file <filename>
  """
  command("generate-with-source", [ "gs" ]) do
    out = Keyword.fetch!(opts, :output_file)
    [ module ] = args


    EEx.eval_string( @common_template, module: module)
    |> generate_with_source
    |> write_output( out )
  end

  @doc """
  <module name>
  Generate a template for a script with the given module name.
  Will create a file with the given module name by default.
  options:
  --output-file <filename> for a different output file
  """
  command("generate-template", ["g", "gt"] ) do
    [ module ] = args
    m = String.capitalize(module)
    out = String.downcase(module)
    [ @interpreter_directive, EEx.eval_string( @common_template, module: m ) ]
    |> write_output( out )
  end

  @doc """
  <input file>
  Embed a bare script that needs Scripting with required preamble to
  make it portable
  required options:
  --output-file <filename> (should be different than source)
  """
  command("embed-with-source", [ "es" ]) do
    out = Keyword.fetch!(opts, :output_file)
    [ input_file ] = args

    File.read!(input_file)
    |> generate_with_source()
    |> write_output( out )
  end

  @doc """
  <input file>
  Embed a bare script that needs Scripting with BEAM embedded in output
  required options:
  --output-file <filename> (should be different than source)
  """
  command("embed-with-beam", [ "eb" ]) do
    out = Keyword.fetch!(opts, :output_file)
    [ input_file ] = args

    File.read!(input_file)
    |> generate_with_beam()
    |> write_output( out )
  end

  command_default()
end
Generator.run()