rodish 1.1.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,58 +7,11 @@ module Rodish
7
7
  # Rodish::OptionPaser is a subclass of Ruby's standard OptionParser
8
8
  # (from the optparse library).
9
9
  class OptionParser < ::OptionParser
10
- # A hash of subcommands for the option parser. If not empty,
11
- # shows available subcommands when showing options.
12
- attr_accessor :subcommands
13
-
14
10
  # Don't add officious, which includes options that call exit.
15
11
  # With Rodish, there are no secret options, only options you define.
16
12
  def add_officious
17
13
  end
18
14
 
19
- # Add the available subcommands to the returned string if there are
20
- # any subcommands.
21
- def to_s
22
- string = super
23
-
24
- if subcommands.length > 6
25
- string += "\nSubcommands:\n #{subcommands.keys.sort.join("\n ")}\n"
26
- elsif !subcommands.empty?
27
- string += "\nSubcommands: #{subcommands.keys.sort.join(" ")}\n"
28
- end
29
-
30
- string
31
- end
32
-
33
- # Helper method that takes an array of values, wraps them to the given
34
- # limit, and adds each line as a separator. This is useful when you
35
- # have a large amount of information you want to display and you want
36
- # to wrap if for display to the user when showing options.
37
- def wrap(prefix, values, separator: " ", limit: 80)
38
- line = [prefix]
39
- lines = [line]
40
- prefix_length = length = prefix.length
41
- sep_length = separator.length
42
- indent = " " * prefix_length
43
-
44
- values.each do |value|
45
- value_length = value.length
46
- new_length = sep_length + length + value_length
47
- if new_length > limit
48
- line = [indent, separator, value]
49
- lines << line
50
- length = prefix_length
51
- else
52
- line << separator << value
53
- end
54
- length += sep_length + value_length
55
- end
56
-
57
- lines.each do |line|
58
- separator line.join
59
- end
60
- end
61
-
62
15
  # Halt processing with a CommandExit using the given string.
63
16
  # This can be used to implement early exits, by calling this
64
17
  # method in a block:
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # _context_sensitive_help is an internal plugin to implement context
4
+ # sensitive help output.
5
+
6
+ #
7
+ module Rodish
8
+ module Plugins
9
+ module ContextSensitiveHelp_
10
+ # Object that wraps a block, which is instance execed in the provided
11
+ # context when called.
12
+ class ContextHelp
13
+ def initialize(block)
14
+ @block = block
15
+ end
16
+
17
+ def call(context)
18
+ context.instance_exec(&@block)
19
+ end
20
+ end
21
+
22
+ module CommandMethods
23
+ # Render help with context-sensitive information.
24
+ def context_help(context)
25
+ lines = help_lines(include_context_help: true)
26
+ lines.map! do |line|
27
+ if line.is_a?(ContextHelp)
28
+ line.call(context)
29
+ else
30
+ line
31
+ end
32
+ end
33
+ lines.flatten!
34
+ lines.join("\n")
35
+ end
36
+
37
+ # Exclude ContextHelp lines unless they are explicitly requested.
38
+ def help_lines(include_context_help: false)
39
+ lines = super()
40
+ lines.reject!{|l| l.is_a?(ContextHelp)} unless include_context_help
41
+ lines
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ # _wrap is an an internal plugin module used by other plugins.
4
+
5
+ #
6
+ module Rodish
7
+ module Plugins
8
+ module Wrap_
9
+ module_function
10
+
11
+ # Return an array of strings, each no longer than limit,
12
+ # showing the prefix and all values.
13
+ public def wrap(prefix, values, separator: " ", limit: 80)
14
+ line = [prefix]
15
+ lines = [line]
16
+ prefix_length = length = prefix.length
17
+ sep_length = separator.length
18
+ indent = " " * prefix_length
19
+
20
+ values.each do |value|
21
+ value = value.to_s
22
+ value_length = value.length
23
+ new_length = sep_length + length + value_length
24
+ if new_length > limit
25
+ line = [indent, separator, value]
26
+ lines << line
27
+ length = prefix_length
28
+ else
29
+ line << separator << value
30
+ end
31
+ length += sep_length + value_length
32
+ end
33
+
34
+ lines.map{|l| l.join}
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The after_options_hook plugin supports an after_options configuration
4
+ # method, specifying a block to execute in context after parsing options,
5
+ # before executing the command or any subcommands. It is passed the remaining
6
+ # argv and already parsed options:
7
+ #
8
+ # after_options do |argv, options|
9
+ # # ...
10
+ # end
11
+
12
+ #
13
+ module Rodish
14
+ module Plugins
15
+ module AfterOptionsHook
16
+ module DSLMethods
17
+ # Sets the after_options block. This block is executed in the same
18
+ # context as the run block would be executed, directly after
19
+ # option parsing.
20
+ def after_options(&block)
21
+ @command.after_options = block
22
+ end
23
+ end
24
+
25
+ module CommandMethods
26
+ # A hook to execute after parsing options for the command.
27
+ attr_accessor :after_options
28
+
29
+ # Run after_options hook if present after parsing options.
30
+ def process_command_options(context, options, argv)
31
+ super
32
+ context.instance_exec(argv, options, &after_options) if after_options
33
+ end
34
+ end
35
+ end
36
+
37
+ register(:after_options_hook, AfterOptionsHook)
38
+ end
39
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The cache_help caches help output when the command is frozen, so
4
+ # it is only calculated once. This is useful if help output for
5
+ # the command does not depend on external state, and help for the
6
+ # command could be requested multiple times during program runtime.
7
+
8
+ #
9
+ module Rodish
10
+ module Plugins
11
+ module CacheHelpOutput
12
+ module CommandMethods
13
+ # Cache and help output when freezing the command.
14
+ def freeze
15
+ @help = help.freeze
16
+ super
17
+ end
18
+
19
+ # Return cached help if it is present.
20
+ def help
21
+ @help || super
22
+ end
23
+ end
24
+ end
25
+
26
+ register(:cache_help_output, CacheHelpOutput)
27
+ end
28
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The help_examples plugin supports showing examples in help output.
4
+ # By default, examples are shown at the end of the help output, but you
5
+ # can change the order using the help_order method.
6
+
7
+ #
8
+ module Rodish
9
+ module Plugins
10
+ module HelpExamples
11
+ module DSLMethods
12
+ # Add an example to show in the help output for the command.
13
+ def help_example(example)
14
+ (@command.help_examples ||= []) << example
15
+ end
16
+ end
17
+
18
+ module CommandMethods
19
+ # An array of strings for any examples to display in the help.
20
+ attr_accessor :help_examples
21
+
22
+ private
23
+
24
+ # The string to use for the examples heading in help output.
25
+ def help_examples_heading
26
+ "Examples:"
27
+ end
28
+
29
+ # Include examples at the end of the help text by default.
30
+ def default_help_order
31
+ super << :examples
32
+ end
33
+
34
+ def _help_examples(output)
35
+ if help_examples
36
+ output << help_examples_heading
37
+ help_examples.each do |example|
38
+ output << " #{example}"
39
+ end
40
+ output << ""
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ register(:help_examples, HelpExamples)
47
+ end
48
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Support showing allowed option values in help output. The allowed
4
+ # options are wrapped. The also allows for context-sensitive allowed
5
+ # options, where the allowed options may vary per-call.
6
+
7
+ require_relative "_context_sensitive_help"
8
+ require_relative "_wrap"
9
+
10
+ #
11
+ module Rodish
12
+ module Plugins
13
+ module HelpOptionValues
14
+ def self.before_load(app)
15
+ app.plugin(ContextSensitiveHelp_)
16
+ end
17
+
18
+ # Used to wrap context sensitive option values, when the allowed options
19
+ # differ depending on the context.
20
+ class ContextWrappedOptionValues < ContextSensitiveHelp_::ContextHelp
21
+ def initialize(name, block)
22
+ @name = name
23
+ super(block)
24
+ end
25
+
26
+ # Get the allowed values using the provided context, then wrap the values.
27
+ def call(context)
28
+ Wrap_.wrap(" #{@name}", super)
29
+ end
30
+ end
31
+
32
+ module DSLMethods
33
+ # Add allowed option values to show in help output.
34
+ def help_option_values(option_name, values=nil, &block)
35
+ values ||= ContextWrappedOptionValues.new(option_name, block)
36
+ (@command.help_option_values ||= {})[option_name] = values
37
+ end
38
+ end
39
+
40
+ module CommandMethods
41
+ # An hash with option name string keys, and keys that are either
42
+ # arrays of strings or ContextWrappedOptionValues instances.
43
+ attr_accessor :help_option_values
44
+
45
+ private
46
+
47
+ # The string to use for the allowed option values heading in help output.
48
+ def help_allowed_option_values_heading
49
+ "Allowed Option Values:"
50
+ end
51
+
52
+ # Include option values after options.
53
+ def default_help_order
54
+ order = super
55
+ index = order.index(:options) || -2
56
+ order.insert(index+1, :option_values)
57
+ order
58
+ end
59
+
60
+ # Include allowed options in the help output, if there are any
61
+ # option values for this command.
62
+ def _help_option_values(output)
63
+ if help_option_values
64
+ output << help_allowed_option_values_heading
65
+ help_option_values.each do |name, values|
66
+ if values.is_a?(ContextWrappedOptionValues)
67
+ output << values
68
+ else
69
+ output.concat(Wrap_.wrap(" #{name}", values))
70
+ end
71
+ end
72
+ output << ""
73
+ end
74
+ end
75
+ end
76
+ end
77
+
78
+ register(:help_option_values, HelpOptionValues)
79
+ end
80
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The help_order plugin allows overriding the order of help sections in
4
+ # commands:
5
+ #
6
+ # help_order(:desc, :options)
7
+ #
8
+ # You can provide the default order for help sections using the
9
+ # +default_help_order+ keyword argument:
10
+ #
11
+ # plugin :help_order, default_help_order: [:usage, :options]
12
+
13
+ #
14
+ module Rodish
15
+ module Plugins
16
+ module HelpOrder
17
+ def self.after_load(app, default_help_order: nil)
18
+ if default_help_order
19
+ app::DSL::Command.class_exec do
20
+ define_method(:default_help_order){default_help_order.dup}
21
+ alias_method(:default_help_order, :default_help_order)
22
+ private(:default_help_order)
23
+ end
24
+ end
25
+ end
26
+
27
+ module DSLMethods
28
+ # Override the order of help sections for the command.
29
+ def help_order(*sections)
30
+ @command.help_order = sections
31
+ end
32
+ end
33
+
34
+ module CommandMethods
35
+ # The order of sections in returned help. If not set for the
36
+ # command, uses the default order
37
+ attr_writer :help_order
38
+
39
+ private
40
+
41
+ def help_order
42
+ @help_order || super
43
+ end
44
+ end
45
+ end
46
+
47
+ register(:help_order, HelpOrder)
48
+ end
49
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The invalid_args_message plugin allows for using a specific
4
+ # error message when an invalid number of arguments has been
5
+ # passed to a command.
6
+ #
7
+ # You can pass the invalid_args_message keyword argument to the
8
+ # following configuration methods:
9
+ #
10
+ # * args
11
+ # * is
12
+ # * run_is
13
+
14
+ #
15
+ module Rodish
16
+ module Plugins
17
+ module InvalidArgsMessage
18
+ module DSLMethods
19
+ # Support setting +invalid_args_message+ when calling +args+.
20
+ def args(args, **kw)
21
+ _set_invalid_args_message(kw) do
22
+ super
23
+ @command
24
+ end
25
+ end
26
+
27
+ # Support setting +invalid_args_message+ when calling +is+.
28
+ def is(command_name, **kw, &block)
29
+ _set_invalid_args_message(kw) do
30
+ super
31
+ end
32
+ end
33
+
34
+ # Support setting +invalid_args_message+ when calling +run_is+.
35
+ def run_is(command_name, **kw, &block)
36
+ _set_invalid_args_message(kw) do
37
+ super
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ # Remove invalid_args_message from keyword hash, yield to get
44
+ # the command, then set the invalid_args_message on the command
45
+ # if it was set.
46
+ def _set_invalid_args_message(kw)
47
+ message = kw.delete(:invalid_args_message)
48
+ command = yield
49
+ command.invalid_args_message = message if message
50
+ command
51
+ end
52
+ end
53
+
54
+ module CommandMethods
55
+ # The error message to use if an invalid number of
56
+ # arguments is provided.
57
+ attr_accessor :invalid_args_message
58
+
59
+ private
60
+
61
+ # Use invalid_args_message if it has been set.
62
+ def invalid_num_args_failure_error_message(argv)
63
+ if @invalid_args_message
64
+ "invalid arguments#{subcommand_name} (#{@invalid_args_message})"
65
+ else
66
+ super
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ register(:invalid_args_message, InvalidArgsMessage)
73
+ end
74
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The is plugin adds an +is+ method as a shortcut for calling the +on+ method
4
+ # and +run+ method.
5
+ #
6
+ # For example:
7
+ #
8
+ # is "hello" do
9
+ # :world
10
+ # end
11
+ #
12
+ # is equivalent to:
13
+ #
14
+ # on "hello" do
15
+ # run do
16
+ # :world
17
+ # end
18
+ # end
19
+ #
20
+ # The +is+ method also takes +args+ keyword arguments to specify the number
21
+ # of arguments.
22
+ #
23
+ # This does not allow you to set a command description or usage, so it is
24
+ # not recommended in new code.
25
+
26
+ #
27
+ module Rodish
28
+ module Plugins
29
+ module Is
30
+ module DSLMethods
31
+ # A shortcut for calling +on+ and +run+.
32
+ #
33
+ # is "hello" do
34
+ # :world
35
+ # end
36
+ #
37
+ # is equivalent to:
38
+ #
39
+ # on "hello" do
40
+ # run do
41
+ # :world
42
+ # end
43
+ # end
44
+ #
45
+ # The +args+ argument sets the number of arguments supported by
46
+ # the command.
47
+ def is(command_name, args: 0, &block)
48
+ _is(:on, command_name, args:, &block)
49
+ end
50
+
51
+ private
52
+
53
+ # Internals of +is+.
54
+ def _is(meth, command_name, args:, &block)
55
+ public_send(meth, command_name) do
56
+ args(args)
57
+ run(&block)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ register(:is, Is)
64
+ end
65
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The post_commands plugin supports subcommands after arguments,
4
+ # instead of requiring subcommands before arguments. You can
5
+ # use Command#run to dispatch to post subcommands inside the
6
+ # Command's run block.
7
+ #
8
+ # run do |argv, opts, command|
9
+ # @something = argv.shift
10
+ # command.run(self, opts, argv)
11
+ # end
12
+
13
+ #
14
+ module Rodish
15
+ module Plugins
16
+ module PostCommands
17
+ def self.after_load(app)
18
+ app.command.instance_exec do
19
+ @post_subcommands ||= {}
20
+ end
21
+ end
22
+
23
+ module DSLMethods
24
+ # Set the banner for post subcommand usage.
25
+ def post_banner(banner)
26
+ @command.post_banner = banner
27
+ end
28
+
29
+ # Similar to +options+, but sets the option parser for post
30
+ # subcommands. This option parser is only used when the
31
+ # command is executed and chooses to run a post subcommand.
32
+ def post_options(banner, key: nil, &block)
33
+ @command.post_banner = banner
34
+ @command.post_option_key = key
35
+ @command.post_option_parser = create_option_parser(&block)
36
+ end
37
+
38
+ # Similar to +autoload_subcommand_dir+, but for post
39
+ # subcommands instead of normal subcommands.
40
+ def autoload_post_subcommand_dir(dir)
41
+ _autoload_subcommand_dir(@command.post_subcommands, dir)
42
+ end
43
+
44
+ # Same as +on+, but for post subcommands instead of normal
45
+ # subcommands.
46
+ def run_on(command_name, &block)
47
+ _on(@command.post_subcommands, command_name, &block)
48
+ end
49
+ end
50
+
51
+ module CommandMethods
52
+ # A hash of post subcommands for the command. Keys are
53
+ # post subcommand name strings.
54
+ attr_reader :post_subcommands
55
+
56
+ # The post option parser for the current command. Called
57
+ # only before dispatching to post subcommands.
58
+ attr_accessor :post_option_parser
59
+
60
+ # Similar to +option_key+, but for post options instead
61
+ # of normal subcommands.
62
+ attr_accessor :post_option_key
63
+
64
+ # A usage banner for any post subcommands.
65
+ attr_accessor :post_banner
66
+
67
+ def initialize(command_path)
68
+ super
69
+ @post_subcommands = {}
70
+ end
71
+
72
+ # Freeze all post subcommands and the post option parsers in
73
+ # addition to the command itself.
74
+ def freeze
75
+ @post_subcommands.each_value(&:freeze)
76
+ @post_subcommands.freeze
77
+ @post_option_parser.freeze
78
+ super
79
+ end
80
+
81
+ # Run a post subcommand using the given context (generally self),
82
+ # options, and argv. Usually called inside a run block, after
83
+ # shifting one or more values off the given argv:
84
+ #
85
+ # run do |argv, opts, command|
86
+ # @name = argv.shift
87
+ # command.run(self, opts, argv)
88
+ # end
89
+ def run(context, options, argv)
90
+ begin
91
+ process_options(argv, options, @post_option_key, @post_option_parser)
92
+ rescue ::OptionParser::InvalidOption => e
93
+ raise CommandFailure.new(e.message, self)
94
+ end
95
+
96
+ arg = argv[0]
97
+ if arg && @post_subcommands[arg]
98
+ process_subcommand(@post_subcommands, context, options, argv)
99
+ else
100
+ process_command_failure(arg, @post_subcommands, "post ")
101
+ end
102
+ end
103
+ alias run_post_subcommand run
104
+
105
+ # Also yield each post subcommand, recursively.
106
+ def each_subcommand(names = [].freeze, &block)
107
+ super
108
+ _each_subcommand(names, @post_subcommands, &block)
109
+ end
110
+
111
+ # Also yield the post banner
112
+ def each_banner
113
+ super
114
+ yield post_banner if post_banner
115
+ nil
116
+ end
117
+
118
+ # Returns a Command instance for the named post subcommand.
119
+ # This will autoload the post subcommand if not already loaded.
120
+ def post_subcommand(name)
121
+ _subcommand(@post_subcommands, name)
122
+ end
123
+
124
+ private
125
+
126
+ # The string to use for the post commands heading in help output.
127
+ def help_post_commands_heading
128
+ "Post Commands:"
129
+ end
130
+
131
+ # The string to use for the post options heading in help output.
132
+ def help_post_options_heading
133
+ "Post Options:"
134
+ end
135
+
136
+ # Include post subcommands as separate help section.
137
+ def __help_command_hashes
138
+ hash = super
139
+ hash[help_post_commands_heading] = @post_subcommands
140
+ hash
141
+ end
142
+
143
+ # Include post option parser as separate help section.
144
+ def __help_option_parser_hashes
145
+ hash = super
146
+ hash[help_post_options_heading] = @post_option_parser
147
+ hash
148
+ end
149
+
150
+ # Also yield each local post subcommand to the block.
151
+ def each_local_subcommand(&block)
152
+ super
153
+ _each_local_subcommand(@post_subcommands, &block)
154
+ end
155
+ end
156
+ end
157
+
158
+ register(:post_commands, PostCommands)
159
+ end
160
+ end