#!/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 """ Generate an elixir script file with Scripting embedded and include the basic scaffolding Required options: --output-file """ 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 """ Generate scripting scaffold with source of Scripting module required options: --output-file """ 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 """ 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 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 """ Embed a bare script that needs Scripting with required preamble to make it portable required options: --output-file (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 """ Embed a bare script that needs Scripting with BEAM embedded in output required options: --output-file (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()