Module: CLIClassTool::Utils

Defined in:
lib/cli_class_tool/utils.rb

Overview

Generic utilities for CLI class-based actions

Instance Method Summary collapse

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



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/cli_class_tool/utils.rb', line 50

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:



20
21
22
# File 'lib/cli_class_tool/utils.rb', line 20

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

#checkDirectConstructor(theClass) ⇒ Object

Validate that the constructor was only called through loadClass



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

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



88
89
90
91
92
# File 'lib/cli_class_tool/utils.rb', line 88

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)



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/cli_class_tool/utils.rb', line 100

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



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/cli_class_tool/utils.rb', line 28

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



146
147
148
149
150
151
152
153
154
155
# File 'lib/cli_class_tool/utils.rb', line 146

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



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

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



186
187
188
189
190
191
192
193
194
195
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
# File 'lib/cli_class_tool/utils.rb', line 186

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

    # 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



79
80
81
82
83
# File 'lib/cli_class_tool/utils.rb', line 79

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



10
11
12
13
14
# File 'lib/cli_class_tool/utils.rb', line 10

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



139
140
141
# File 'lib/cli_class_tool/utils.rb', line 139

def verbose_log()
    @verbose_log
end

#verbose_log=(val) ⇒ Object

Set verbose logging

Parameters:

  • val (Boolean)

    True to enable verbose logging



132
133
134
# File 'lib/cli_class_tool/utils.rb', line 132

def verbose_log=(val)
    @verbose_log = val
end