Class: Cisco::CmdRef

Inherits:
Object
  • Object
show all
Defined in:
lib/cisco_node_utils/command_reference.rb

Overview

Control a reference for an attribute.

Direct Known Subclasses

UnsupportedCmdRef

Constant Summary collapse

KEYS =
%w(default_value default_only
config_set config_set_append
config_get config_get_token config_get_token_append
auto_default multiple kind
test_config_get test_config_get_regex test_config_result)
KINDS =
%w(boolean int string)

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(feature, name, values, file) ⇒ CmdRef

Construct a CmdRef describing the given (feature, name) pair. Param “values” is a hash with keys as described in KEYS. Param “file” is for debugging purposes only.



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/cisco_node_utils/command_reference.rb', line 43

def initialize(feature, name, values, file)
  fail ArgumentError, "'#{values}' is not a hash." unless values.is_a? Hash

  @feature = feature
  @name = name
  @hash = {}
  @auto_default = true
  @default_only = false
  @multiple = false
  @kind = nil

  values.each do |key, value|
    unless KEYS.include?(key)
      fail "Unrecognized key #{key} for #{feature}, #{name} in #{file}"
    end
    if key == 'config_get_token' || key == 'config_set'
      # For simplicity, these are ALWAYS arrays
      value = [value] unless value.is_a?(Array)
      define_getter(key, value)
      # We intentionally do this *after* the define_getter() call
      @hash[key] = preprocess_value(value)
    elsif key == 'auto_default'
      @auto_default = value ? true : false
    elsif key == 'default_only'
      @default_only = true
      # default_value overrides default_only
      @hash['default_value'] ||= preprocess_value(value)
    elsif key == 'multiple'
      @multiple = boolean_default_true(value)
    elsif key == 'kind'
      fail "Unknown 'kind': '#{value}'" unless KINDS.include?(value)
      @kind = value.to_sym
    else
      # default_value overrides default_only
      @default_only = false if key == 'default_value'
      @hash[key] = preprocess_value(value)
    end
  end

  if @default_only # rubocop:disable Style/GuardClause
    %w(config_get_token config_set).each do |key|
      instance_eval "undef #{key}" if @hash.key?(key)
    end
    @hash.delete_if { |key, _| key != 'default_value' }
  end
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_name, *args, &block) ⇒ Object



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/cisco_node_utils/command_reference.rb', line 195

def method_missing(method_name, *args, &block)
  if KEYS.include?(method_name.to_s)
    # ref.foo -> return @hash[foo] or fail IndexError
    method_name = method_name.to_s
    unless @hash.include?(method_name)
      if @default_only
        fail UnsupportedError.new(@feature, @name, method_name)
      end
      fail IndexError, "No #{method_name} defined for #{@feature}, #{@name}"
    end
    # puts("get #{method_name}: '#{@hash[method_name]}'")
    @hash[method_name]
  elsif method_name.to_s[-1] == '?' && \
        KEYS.include?(method_name.to_s[0..-2])
    # ref.foo? -> return true if @hash[foo], else false
    method_name = method_name.to_s[0..-2]
    @hash.include?(method_name)
  else
    super(method_name, *args, &block)
  end
end

Instance Attribute Details

#auto_defaultObject (readonly) Also known as: auto_default?

Returns the value of attribute auto_default.



23
24
25
# File 'lib/cisco_node_utils/command_reference.rb', line 23

def auto_default
  @auto_default
end

#default_onlyObject (readonly) Also known as: default_only?

Returns the value of attribute default_only.



23
24
25
# File 'lib/cisco_node_utils/command_reference.rb', line 23

def default_only
  @default_only
end

#featureObject (readonly)

Returns the value of attribute feature.



22
23
24
# File 'lib/cisco_node_utils/command_reference.rb', line 22

def feature
  @feature
end

#hashObject (readonly)

Returns the value of attribute hash.



22
23
24
# File 'lib/cisco_node_utils/command_reference.rb', line 22

def hash
  @hash
end

#kindObject (readonly)

Returns the value of attribute kind.



23
24
25
# File 'lib/cisco_node_utils/command_reference.rb', line 23

def kind
  @kind
end

#multipleObject (readonly) Also known as: multiple?

Returns the value of attribute multiple.



23
24
25
# File 'lib/cisco_node_utils/command_reference.rb', line 23

def multiple
  @multiple
end

#nameObject (readonly)

Returns the value of attribute name.



22
23
24
# File 'lib/cisco_node_utils/command_reference.rb', line 22

def name
  @name
end

Class Method Details

.keysObject



34
35
36
# File 'lib/cisco_node_utils/command_reference.rb', line 34

def self.keys
  KEYS
end

Instance Method Details

#boolean_default_true(value) ⇒ Object

Property with an implicit value of ‘true’ if no value is given



91
92
93
# File 'lib/cisco_node_utils/command_reference.rb', line 91

def boolean_default_true(value)
  value.nil? || value
end

#convert_to_constant(value) ⇒ Object



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/cisco_node_utils/command_reference.rb', line 169

def convert_to_constant(value)
  # NOTE: This method is now deprecated and should not be used for future
  #       development.
  #
  # If value is a string and it is empty OR the first letter is lower case
  # then leave value untouched.
  # If value is a string and the first letter is uppercase this indicates
  # that it could be a constant in Ruby, so attempt to convert it
  # to a Constant.
  if value.is_a?(String) && !value.empty?
    if value[0].chr == value[0].chr.upcase
      begin
        value = Object.const_get(value) if Object.const_defined?(value)
      rescue NameError
        debug("'#{value}' is not a constant")
      end
    end
  end
  value
end

#define_getter(key, value) ⇒ Object

Create a getter method for the given key. This getter method will automatically handle wildcard arguments.



97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/cisco_node_utils/command_reference.rb', line 97

def define_getter(key, value)
  return unless value.is_a?(Array)
  if value.any? { |item| item.is_a?(String) && /<\S+>/ =~ item }
    # Key-value substitution
    define_singleton_method(key.to_sym, key_substitutor(key, value))
  elsif value.any? { |item| item.is_a?(String) && /%/ =~ item }
    # printf-style substitution
    define_singleton_method(key.to_sym, printf_substitutor(key, value))
  else
    # simple static token(s)
    value = preprocess_value(value)
    define_singleton_method key.to_sym, -> { value }
  end
end

#key_substitutor(config_key, value) ⇒ Object

curried function to define a getter method body that performs key-value substitution



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/cisco_node_utils/command_reference.rb', line 114

def key_substitutor(config_key, value)
  lambda do |**args|
    result = []
    value.each do |line|
      replace = line.scan(/<(\S+)>/).flatten.map(&:to_sym)
      replace.each do |item|
        line = line.sub("<#{item}>", args[item].to_s) if args.key?(item)
      end
      result.push(line) unless /<\S+>/.match(line)
    end
    if result.empty?
      fail ArgumentError,
           "Arguments given to #{config_key} yield empty result"
    end
    preprocess_value(result)
  end
end

#preprocess_value(value) ⇒ Object

Helper method. Converts a regexp-like string (or array thereof) into a proper Regexp object (or array thereof)



152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/cisco_node_utils/command_reference.rb', line 152

def preprocess_value(value)
  if value.is_a?(Array)
    # Recurse!
    return value.map { |item| preprocess_value(item) }
  elsif value.is_a?(String)
    # Some 'Strings' in YAML are actually intended to be regexps
    if value[0] == '/' && value[-1] == '/'
      # '/foo/' => %r{foo}
      return Regexp.new(value[1..-2])
    elsif value[0] == '/' && value[-2..-1] == '/i'
      # '/foo/i' => %r{foo}i
      return Regexp.new(value[1..-3], Regexp::IGNORECASE)
    end
  end
  value
end

#printf_substitutor(config_key, value) ⇒ Object

curried function to define a getter method body that performs printf-style substitution



134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/cisco_node_utils/command_reference.rb', line 134

def printf_substitutor(config_key, value)
  arg_c = value.join.scan(/%/).length
  lambda do |*args|
    unless args.length == arg_c
      fail ArgumentError,
           "Given #{args.length} args, but #{config_key} requires #{arg_c}"
    end
    # Fill in the parameters
    result = value.map do |line|
      sprintf(line, *args.shift(line.scan(/%/).length))
    end
    preprocess_value(result)
  end
end

#test_config_result(value) ⇒ Object



190
191
192
193
# File 'lib/cisco_node_utils/command_reference.rb', line 190

def test_config_result(value)
  result = @hash['test_config_result'][value]
  convert_to_constant(result)
end

#to_sObject

Print useful debugging information about the object.



218
219
220
221
222
223
# File 'lib/cisco_node_utils/command_reference.rb', line 218

def to_s
  str = ''
  str << "Command: #{@feature} #{@name}\n"
  @hash.each { |key, value| str << "  #{key}: #{value}\n" }
  str
end