defmodule Searchex.Cli do @moduledoc false @completion_script File.read("eex/searchex_completion.bash") @doc """ Entry point for the `searchex` executable. Takes a single argument `argv` which is a list of command-line options. """ def main(argv) do route(argv) |> render end # List of command options. The command should be the same as the function # name. Argument and Description are used to generate help text. cmd_opts = [ # Cmd Arity Module Argument Description {"ls" , 0, "Config" , "" , "list collections" }, {"info" , 0, "Render" , "" , "collection statistics" }, {"fetch" , 1, "Config" , "GITHUB_REPO" , "eg 'fetch elixir-search/sample'" }, {"new" , 1, "Config" , "TARGET_PATH" , "new collection for TARGET_PATH" }, {"cat" , 1, "Config" , "COLLECTION" , "cat config" }, {"modify" , 1, "Render" , "COLLECTION" , "edit the config file" }, {"rm" , 1, "Config" , "COLLECTION" , "remove config" }, {"build" , 1, "Render" , "COLLECTION" , "build the collection" }, {"query" , 2, "Render" , "COLLECTION ''" , "search the collection" }, {"results" , 1, "Render" , "COLLECTION" , "results from the last query" }, {"show" , 2, "Render" , "COLLECTION ID" , "show text of results ID" }, {"edit" , 2, "Render" , "COLLECTION ID" , "edit results ID" }, {"clean" , 1, "Render" , "COLLECTION" , "remove cache for COLLECTION" }, {"version" , 0, "" , "" , "show installed version" }, {"help" , 0, "Cli" , "" , "this command" }, ] @cmd_opts cmd_opts # These command options are not included in the CLI 'help' output. alt_opts = [ # Cmd Arity Module Argument Description {"catalog" , 1, "Render" , "COLLECTION" , "build the collection catalog" }, {"index" , 1, "Render" , "COLLECTION" , "build the collection index" }, {"all_commands", 0, "Cli" , "" , "used for tab completion" }, {"cfg_commands", 0, "Cli" , "" , "used for tab completion" }, {"completion" , 0, "Cli" , "" , "renders the completion script" }, ] @alt_opts alt_opts # ---------------------------------------------------------------------------------------------------- # Generate route functions for each command option for {cmd, len, mod, _args, _detail} <- cmd_opts ++ alt_opts do modfun = Util.Ext.Enum.join(["Searchex", mod, cmd], ".") {efunc, _} = Code.eval_string("&#{modfun}/#{len}") @cmd cmd @func efunc case len do 0 -> def route([@cmd]) , do: @func.() 1 -> def route([@cmd, col_name]) , do: @func.(col_name) 2 -> def route([@cmd, col_name, qry]), do: @func.(col_name, qry) _ -> raise("ERROR Bad Option Data (#{cmd}/#{len})") end end def route([]) , do: help() def route(argv), do: error(argv) # ---------------------------------------------------------------------------------------------------- def help do Searchex.Render.Help.to_table(@cmd_opts) :ok end # Show the tab-completion script def completion do @completion_script end # List all commands - used for tab-completion def all_commands do cmds = @cmd_opts |> Enum.map(fn({cmd, _arity, _module, _arg, _desc}) -> cmd end) |> Enum.join("\n") {:ok, cmds} end # List commands that take the name of an existing config - used for tab-completion def cfg_commands do cmds = @cmd_opts |> Enum.filter(fn({cmd, arity, _module, _arg, _desc}) -> cmd != "new" && arity > 0 end) |> Enum.map(fn({cmd, _arity, _module, _arg, _desc}) -> cmd end) |> Enum.join("\n") {:ok, cmds} end # For testing... def command_list_for_testing do @cmd_opts ++ @alt_opts |> Enum.map(&(elem(&1,0))) end # ---------------------------------------------------------------------------------------------------- # Render command output to stdout defp render({_, str}) when is_binary(str), do: lcl_puts(str) defp render({_, lst}) when is_list(lst) , do: lcl_test(lst) defp render({_, ele}) , do: lcl_insp(ele) defp render(:ok) , do: :ok defp render({:ok}) , do: :ok defp render(str) when is_binary(str) , do: lcl_puts(str) defp render(ele) , do: lcl_insp(ele) defp lcl_test(list) do case Enum.all?(List.flatten(list), fn(el) -> is_binary(el) end) do true -> lcl_puts(Enum.join(list, "\n")) _ -> lcl_insp(list) end end defp lcl_puts(string) do unless Util.Ext.String.empty?(string), do: Util.Ext.IO.puts string end defp lcl_insp(ele) do Util.Ext.IO.inspect(ele, width: 999) end # ---------------------------------------------------------------------------------------------------- def rpad(string, val), do: String.pad_trailing(string, val) def lpad(string, val), do: String.pad_leading(string, val) defp prog , do: Util.Ext.App.name # defp prog_version , do: Util.Ext.App.version defp usage_message, do: "Type '#{prog()} help' for usage information." defp error(argv) do str = """ ERROR unrecognized command (#{prog()} #{Enum.join(argv, " ")}) #{usage_message()} """ {:error, str} end end