Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduction of global and hiding flags and options #24

Closed
wants to merge 14 commits into from
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ defmodule Statcalc do
short: "-v",
help: "Verbosity level",
multiple: true,
global: true
],
],
options: [
Expand Down
4 changes: 4 additions & 0 deletions lib/optimus.ex
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ defmodule Optimus do
| {:long, String.t()}
| {:help, String.t()}
| {:multiple, boolean}
| {:global, boolean}
| {:hide, boolean}
@type flag_spec_item :: {name :: atom, [flag_spec_param]}

@type option_spec_param ::
Expand All @@ -67,6 +69,8 @@ defmodule Optimus do
| {:required, boolean}
| {:parser, parser}
| {:default, any}
| {:global, boolean}
| {:hide, boolean}
@type option_spec_item :: {name :: atom, [option_spec_param]}

@type spec_item ::
Expand Down
49 changes: 41 additions & 8 deletions lib/optimus/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@ defmodule Optimus.Builder do
alias Optimus.PropertyParsers, as: PP

def build(props) do
build_(props)
end

def build_(props, global_props \\ %{}) do
with :ok <- validate_keyword_list(props),
{:ok, name} <- build_name(props),
{:ok, description} <- build_description(props),
Expand All @@ -14,7 +18,8 @@ defmodule Optimus.Builder do
{:ok, args} <- build_args(props[:args]),
{:ok, flags} <- build_flags(props[:flags]),
{:ok, options} <- build_options(props[:options]),
{:ok, subcommands} <- build_subcommands(props[:subcommands]),
{:ok, global_props} <- build_global_props(flags, options, global_props),
{:ok, subcommands} <- build_subcommands(props[:subcommands], global_props),
:ok <- validate_args(args),
:ok <- validate_conflicts(flags, options),
do:
Expand Down Expand Up @@ -82,21 +87,23 @@ defmodule Optimus.Builder do
with {:ok, arg} <- module.new(arg_spec), do: build_specs_(module, other, [arg | parsed])
end

defp build_subcommands(nil), do: {:ok, []}
defp build_subcommands(nil, _gloal_props), do: {:ok, []}

defp build_subcommands(subcommands) do
defp build_subcommands(subcommands, gloal_props) do
if Keyword.keyword?(subcommands) do
build_subcommands_(subcommands, [])
build_subcommands_(subcommands, gloal_props, [])
else
{:error, "subcommand specs are expected to be a Keyword list"}
end
end

defp build_subcommands_([], parsed), do: {:ok, Enum.reverse(parsed)}
defp build_subcommands_([], _gloal_props, parsed), do: {:ok, Enum.reverse(parsed)}

defp build_subcommands_([{subcommand_name, props} | other], parsed) do
case build(props) do
defp build_subcommands_([{subcommand_name, props} | other], global_props, parsed) do
case build_(props, global_props) do
{:ok, subcommand} ->
subcommand = merge_globals_into_subcommand(subcommand, global_props)

subcommand_with_name =
case subcommand.name do
nil ->
Expand All @@ -106,7 +113,7 @@ defmodule Optimus.Builder do
%Optimus{subcommand | subcommand: subcommand_name}
end

build_subcommands_(other, [subcommand_with_name | parsed])
build_subcommands_(other, global_props, [subcommand_with_name | parsed])

{:error, error} ->
{:error, "error building subcommand #{inspect(subcommand_name)}: #{error}"}
Expand Down Expand Up @@ -152,4 +159,30 @@ defmodule Optimus.Builder do
nil -> :ok
end
end

defp build_global_props(local_flags, local_options, global_props) do
global_flags = build_global_props_by_type(local_flags, :flags, global_props)
global_options = build_global_props_by_type(local_options, :options, global_props)

{:ok, %{flags: global_flags, options: global_options}}
end

defp build_global_props_by_type(local_props, type, global_props) do
local_props
# Keep only global flags
|> Enum.filter(& &1.global)
# Hide global props on subcommands
|> Enum.map(&%{&1 | hide: true})
# Add previous defined global flags
|> Kernel.++(Map.get(global_props, type, []))
end

defp merge_globals_into_subcommand(
subcommand,
%{flags: global_flags, options: global_options} = _gloal_props
) do
subcommand
|> Map.update(:flags, [], fn flags -> flags ++ global_flags end)
|> Map.update(:options, [], fn options -> options ++ global_options end)
end
end
4 changes: 3 additions & 1 deletion lib/optimus/flag.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ defmodule Optimus.Flag do
:short,
:long,
:help,
:multiple
:multiple,
:global,
:hide
]

def new(spec) do
Expand Down
20 changes: 19 additions & 1 deletion lib/optimus/flag/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,18 @@ defmodule Optimus.Flag.Builder do
{:ok, long} <- build_long(props),
{:ok, help} <- build_help(props),
{:ok, multiple} <- build_multiple(props),
{:ok, global} <- build_global(props),
{:ok, hide} <- build_hide(props),
{:ok, flag} <-
validate(%Flag{flag | short: short, long: long, help: help, multiple: multiple}),
validate(%Flag{
flag
| short: short,
long: long,
help: help,
multiple: multiple,
global: global,
hide: hide
}),
do: {:ok, flag}
end

Expand Down Expand Up @@ -44,6 +54,14 @@ defmodule Optimus.Flag.Builder do
PP.build_bool(:multiple, props[:multiple], false)
end

def build_global(props) do
PP.build_bool(:global, props[:global], false)
end

def build_hide(props) do
PP.build_bool(:hide, props[:hide], false)
end

defp validate(flag) do
if flag.short || flag.long do
{:ok, flag}
Expand Down
5 changes: 5 additions & 0 deletions lib/optimus/formatable_help.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,16 @@ defmodule Optimus.FormatableHelp do
widths = get_column_widths(formatables, max_width)

formatables
|> Enum.filter(&is_visible?(&1))
|> Enum.map(&format_columns(widths, &1))
|> Enum.concat()
|> Enum.map(&Enum.join(&1))
end

defp is_visible?(formatable) do
not Map.get(formatable, :hide, false)
end

defp format_columns({:ok, widths}, formatable) do
{:ok, formatted} =
CF.format(widths, ["", Format.format(formatable), "", Format.help(formatable)])
Expand Down
9 changes: 5 additions & 4 deletions lib/optimus/option.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ defmodule Optimus.Option do
:multiple,
:required,
:default,
:parser
:parser,
:global,
:hide
]

def new(spec) do
Expand All @@ -27,9 +29,8 @@ defmodule Optimus.Option do

{:error, reason} ->
{:error,
"invalid value #{inspect(raw_value)} for #{Optimus.Format.format_in_error(option)} option: #{
reason
}", rest}
"invalid value #{inspect(raw_value)} for #{Optimus.Format.format_in_error(option)} option: #{reason}",
rest}
end
else
{:error, "multiple occurrences of option #{Optimus.Format.format_in_error(option)}",
Expand Down
14 changes: 13 additions & 1 deletion lib/optimus/option/builder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ defmodule Optimus.Option.Builder do
{:ok, required} <- build_required(props),
{:ok, default} <- build_default(props),
{:ok, parser} <- build_parser(props),
{:ok, global} <- build_global(props),
{:ok, hide} <- build_hide(props),
{:ok, option} <-
validate(%Option{
option
Expand All @@ -29,7 +31,9 @@ defmodule Optimus.Option.Builder do
multiple: multiple,
required: required,
default: default,
parser: parser
parser: parser,
global: global,
hide: hide
}),
do: {:ok, option}
end
Expand Down Expand Up @@ -75,6 +79,14 @@ defmodule Optimus.Option.Builder do
PP.build_parser(:parser, props[:parser])
end

defp build_global(props) do
PP.build_bool(:global, props[:global], false)
end

defp build_hide(props) do
PP.build_bool(:hide, props[:hide], false)
end

defp validate(option) do
if option.short || option.long do
{:ok, option}
Expand Down
4 changes: 3 additions & 1 deletion lib/optimus/term.ex
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ defmodule Optimus.Term do
{width, _} -> {:ok, width}
:error -> {:error, "invalid tputs result"}
end
{error, _} -> {:error, "tputs error: #{error}"}

{error, _} ->
{:error, "tputs error: #{error}"}
end
rescue
error in ErlangError ->
Expand Down
4 changes: 2 additions & 2 deletions lib/optimus/usage.ex
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
defmodule Optimus.Usage do
def usage(optimus, subcommand_path \\ []) do
{subcommand, subcommand_name} = Optimus.fetch_subcommand(optimus, subcommand_path)
flag_info = format_usage(subcommand.flags)
option_info = format_usage(subcommand.options)
flag_info = subcommand.flags |> Enum.filter(&(not &1.hide)) |> format_usage()
option_info = subcommand.options |> Enum.filter(&(not &1.hide)) |> format_usage()
arg_info = format_arg_usage(subcommand)
usage_parts = [subcommand_name, flag_info, option_info, arg_info]

Expand Down
2 changes: 1 addition & 1 deletion test/optimus/term_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ defmodule Optimus.TermTest do
use ExUnit.Case

test "width" do
unless System.get_env("CI") do
unless System.get_env("CI") do
assert {:ok, n} = Optimus.Term.width()
assert is_integer(n)
assert n > 0
Expand Down
Loading