Module: CLIClassTool::Utils

Defined in:
lib/cli_class_tool/utils.rb

Overview

Generic utilities for CLI class-based actions

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.extended(base) ⇒ Object

Hook called when a module extends CLIClassTool::Utils



6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/cli_class_tool/utils.rb', line 6

def self.extended(base)
    possible_name = base.name ? "#{base.name}Error" : nil

    superclass = if possible_name && Object.const_defined?(possible_name)
        Object.const_get(possible_name)
    elsif base.const_defined?(:Error)
        base.const_get(:Error)
    else
        CLIClassTool::RunError
    end

    CLIClassTool.define_run_error(base, superclass)
end

Instance Method Details

#_runOnClass(action, sym) {|Class| ... } ⇒ Object

Run a block on the class responsible for a specific action

Parameters:

  • action (Symbol)

    The action

  • sym (Symbol, nil)

    Optional method to check for existence

Yields:

  • (Class)

    The class handling the action

Returns:

  • (Object)

    Result of the block or error code



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/cli_class_tool/utils.rb', line 65

def _runOnClass(action, sym, &block)
    self::ACTION_CLASS.each(){|x|
        next if x::ACTION_LIST.index(action) == nil

        # Resolve overridden/extended class (addon)
        class_to_use = self.respond_to?(:getExtendedClass) ? self.getExtendedClass(x) : x

        if sym != nil
            has_base = x.singleton_methods().index(sym) != nil
            has_addon = class_to_use != x && class_to_use.singleton_methods().index(sym) != nil

            if has_base || has_addon
                yield(x) if has_base
                yield(class_to_use) if has_addon
                return 0
            end
        else
            return yield(class_to_use)
        end
        return 0
    }
    return -1
end

#actionToString(sym) ⇒ String

Convert an action symbol to a string

Parameters:

  • sym (Symbol)

    Action symbol

Returns:



35
36
37
# File 'lib/cli_class_tool/utils.rb', line 35

def actionToString(sym)
    return sym.to_s()
end

#checkDirectConstructor(theClass) ⇒ Object

Validate that the constructor was only called through loadClass



185
186
187
188
189
190
191
192
193
194
# File 'lib/cli_class_tool/utils.rb', line 185

def checkDirectConstructor(theClass)
    @load_class ||= []
    curLoad = @load_class.last()
    cl = theClass
    while cl != Object
        return if cl == curLoad
        cl = cl.superclass
    end
    raise("Use #{self.name}::loadClass to construct a #{theClass} class")
end

#checkOpts(opts) ⇒ Object

Check options for validity

Parameters:

  • opts (Hash)

    The options hash



103
104
105
106
107
# File 'lib/cli_class_tool/utils.rb', line 103

def checkOpts(opts)
     self._runOnClass(opts[:action], :check_opts) {|kClass|
         kClass.check_opts(opts)
    }
end

#execAction(opts, action, error_class = nil) ⇒ Object

Execute an action

Parameters:

  • opts (Hash)

    The options hash

  • action (Symbol)

    The action to execute

  • error_class (Class, nil) (defaults to: nil)

    Optional base error class to rescue

Returns:

  • (Object)

    Result of the action (often an Integer exit code)



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/cli_class_tool/utils.rb', line 115

def execAction(opts, action, error_class = nil)
    caught_error_class = error_class || StandardError

    self._runOnClass(action, nil) {|kClass|
        begin
            # Some class have their own execAction, because object creation might be tricky.
            if kClass.respond_to?(:execAction)
                ret = kClass.execAction(opts, action)
            else
                # Use load factory method if defined, else fall back to .new
                obj = kClass.respond_to?(:load) ? kClass.load() : kClass.new()
                ret = obj.public_send(action, opts)
            end
            return ret.is_a?(Integer) ? ret : 0
        rescue caught_error_class => e
            puts("# " + "ERROR".red().to_s() + ": Action '#{action}' failed: #{e.message}")
            e.backtrace.each(){|l|
                puts("# " + "ERROR".red().to_s() + ": \t" + l)
            } if self.verbose_log

            begin
                return e.err_code
            rescue
                return 1
            end
        end
    }
end

#getActionAttr(attr) ⇒ Hash, Array

Get attributes from all action classes

Parameters:

  • attr (Symbol)

    Attribute name (e.g., “ACTION_LIST”)

Returns:

  • (Hash, Array)

    Aggregated attributes



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/cli_class_tool/utils.rb', line 43

def getActionAttr(attr)
    action_classes = self::ACTION_CLASS
    common_class = self::Common

    # Resolve overridden/extended class (addon) if getExtendedClass is defined
    resolved_classes = action_classes.map do |x|
        self.respond_to?(:getExtendedClass) ? self.getExtendedClass(x) : x
    end

    if common_class.const_get(attr).class == Hash
        return resolved_classes.inject({}){|h, x| h.merge(x.const_get(attr))}
    else
        return resolved_classes.map(){|x| x.const_get(attr)}.flatten()
    end
end

#loadAddons(path) ⇒ Object

Load all custom addon classes/files from a directory

Parameters:

  • path (String)

    Absolute or relative directory path containing .rb files



161
162
163
164
165
166
167
168
169
170
# File 'lib/cli_class_tool/utils.rb', line 161

def loadAddons(path)
    return unless Dir.exist?(path)

    absolute_dir = File.expand_path(path)
    Dir.entries(absolute_dir).each() do |entry|
        absolute_file = File.join(absolute_dir, entry)
        next if !File.file?(absolute_file) || entry !~ /\.rb$/
        require absolute_file
    end
end

#loadClass(default_class, addon_key, *more) ⇒ Object

Safely load an overridden/extended class instance using a generic addon_key



173
174
175
176
177
178
179
180
181
182
# File 'lib/cli_class_tool/utils.rb', line 173

def loadClass(default_class, addon_key, *more)
    @load_class ||= []
    @load_class.push(default_class)

    # Resolve overridden class using getExtendedClass if available
    extended_class = self.respond_to?(:getExtendedClass) ? self.getExtendedClass(default_class, addon_key) : default_class
    obj = extended_class.new(*more)
    @load_class.pop()
    return obj
end

#run_cli(opts = {}, argv = ARGV) {|parser, phase, action_opts| ... } ⇒ Object

Generic CLI runner and argument parser for class-based applications.

Parameters:

  • opts (Hash) (defaults to: {})

    Initial options hash

  • argv (Array<String>) (defaults to: ARGV)

    Command line arguments (defaults to ARGV)

Yields:

  • (parser, phase, action_opts)

    Custom options setup callback block



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/cli_class_tool/utils.rb', line 201

def run_cli(opts = {}, argv = ARGV)
    # Fetch actions and action helps
    action_helps = self.getActionAttr("ACTION_HELP")

    # 1. Action Parser Setup
    action_parser = OptionParser.new(nil, 60)
    action_parser.banner = "Usage: #{$0} <action> [action options]"
    action_parser.separator ""
    action_parser.separator "Options:"
    action_parser.on("-h", "--help", "Display usage.") { puts action_parser.to_s; exit 0 }
    action_parser.on("--verbose", "Displays more informations.") { self.verbose_log = true }

    # Yield parser to allow caller to customize the global options
    yield(action_parser, :global, opts) if block_given?

    action_parser.separator "Possible actions:"

    # Format actions nicely with padding
    max_len = action_helps.keys.map { |k| self.actionToString(k).length }.max || 0
    col_width = max_len + 4
    action_helps.each do |k, x|
        indent = col_width - self.actionToString(k).length
        action_parser.separator "\t * " + self.actionToString(k) + (" " * indent) + x
    end

    # Include any registered custom addon class listings if defined
    if self.respond_to?(:getCustomClasses) && self.getCustomClasses.length > 0
        action_parser.separator "Custom repo addons available:"
        self.getCustomClasses.each do |k, x|
            action_parser.separator "\t * #{k}"
        end
    end

    rest = action_parser.order!(argv)
    if rest.length <= 0
        STDERR.puts("Error: No action provided")
        puts action_parser.to_s
        exit 1
    end

    action_s = argv[0]
    action = opts[:action] = self.stringToAction(action_s)
    argv.shift()

    # 2. Options Parser Setup
    opts_parser = OptionParser.new(nil, 60)
    opts_parser.banner = "Usage: #{$0} #{action_s} "
    opts_parser.separator "# " + action_helps[action].to_s()
    opts_parser.separator ""
    opts_parser.separator "Options:"
    opts_parser.on("-h", "--help", "Display usage.") { puts opts_parser.to_s; exit 0 }
    opts_parser.on("-n", "--no", "Assume no to all questions.") { opts[:yn_default] = :no }
    opts_parser.on("-y", "--yes", "Assume yes to all questions.") { opts[:yn_default] = :yes }
    opts_parser.on("--verbose", "Displays more informations.") { self.verbose_log = true }

    # Provide custom block hook for option parser customization
    yield(opts_parser, :action, opts) if block_given?

    # Set options on classes
    self.setOpts(action, opts_parser, opts)

    # Order remaining arguments
    if opts[:ignore_opts] != true
        rest = opts_parser.order!(argv)
        raise("Extra Unexpected extra arguments provided: " + rest.map(){|x|"'" + x + "'"}.join(", ")) if rest.length != 0
    else
        opts[:extra_args] = argv
    end

    # Validate options and execute action
    self.checkOpts(opts)
    exit self.execAction(opts, action)
end

#setOpts(action, optsParser, opts) ⇒ Object

Set options for an action

Parameters:

  • action (Symbol)

    The action

  • optsParser (OptionParser)

    The option parser

  • opts (Hash)

    The options hash



94
95
96
97
98
# File 'lib/cli_class_tool/utils.rb', line 94

def setOpts(action, optsParser, opts)
    self._runOnClass(action, :set_opts) {|kClass|
        kClass.set_opts(action, optsParser, opts)
    }
end

#stringToAction(str) ⇒ Symbol

Convert a string to an action symbol, validating it against available actions

Parameters:

  • str (String)

    Action name

Returns:

  • (Symbol)

    Action symbol

Raises:

  • (RuntimeError)

    If action is invalid



25
26
27
28
29
# File 'lib/cli_class_tool/utils.rb', line 25

def stringToAction(str)
    action = str.to_sym()
    raise("Invalid action '#{str}'") if self.getActionAttr("ACTION_LIST").index(action) == nil
    return action
end

#verbose_logBoolean

Get verbose logging status

Returns:

  • (Boolean)

    Verbose logging status



154
155
156
# File 'lib/cli_class_tool/utils.rb', line 154

def verbose_log()
    @verbose_log
end

#verbose_log=(val) ⇒ Object

Set verbose logging

Parameters:

  • val (Boolean)

    True to enable verbose logging



147
148
149
# File 'lib/cli_class_tool/utils.rb', line 147

def verbose_log=(val)
    @verbose_log = val
end