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
# File 'lib/cli_class_tool/utils.rb', line 6

def self.extended(base)
    return if base == nil

    lower_mod = base.name.split('::')[-1]
    superclass_name = "#{base.name}::#{lower_mod}Error"

    if ! Object.const_defined?(superclass_name)
        raise("Could not find a base error class named #{superclass_name}")
    end

    CLIClassTool.define_run_error(base, Object.const_get(superclass_name))
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



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

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:



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

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

#checkDirectConstructor(theClass) ⇒ Object

Validate that the constructor was only called through loadClass



180
181
182
183
184
185
186
187
188
189
# File 'lib/cli_class_tool/utils.rb', line 180

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



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

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)



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

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

    self._runOnClass(action, nil) {|kClass|
        begin
            # 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)

            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



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

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



156
157
158
159
160
161
162
163
164
165
# File 'lib/cli_class_tool/utils.rb', line 156

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



168
169
170
171
172
173
174
175
176
177
# File 'lib/cli_class_tool/utils.rb', line 168

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



196
197
198
199
200
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
# File 'lib/cli_class_tool/utils.rb', line 196

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



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

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



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

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



149
150
151
# File 'lib/cli_class_tool/utils.rb', line 149

def verbose_log()
    @verbose_log
end

#verbose_log=(val) ⇒ Object

Set verbose logging

Parameters:

  • val (Boolean)

    True to enable verbose logging



142
143
144
# File 'lib/cli_class_tool/utils.rb', line 142

def verbose_log=(val)
    @verbose_log = val
end