Class: Tap::Env::Constant

Inherits:
Object
  • Object
show all
Defined in:
lib/tap/env/constant.rb

Overview

A Constant serves as a placeholder for an actual constant, sort of like autoload. Use the constantize method to retrieve the actual constant; if it doesn’t exist, constantize requires require_path and tries again.

Object.const_defined?(:Net)                      # => false
$".grep(/net\/http.rb$/).empty?                  # => true

http = Constant.new('Net::HTTP', 'net/http.rb')
http.constantize                                 # => Net::HTTP
$".grep(/net\/http.rb$/).empty?                  # => false

Unloading

Constant also supports constant unloading. Unloading can be useful in various development modes, but may cause code to behave unpredictably. When a Constant unloads, the constant value is removed from the nesting constant and the require paths are removed from $“. This allows a require statement to re-require, and in theory, reload the constant.

# [simple.rb]
# class Simple
# end

const = Constant.new('Simple', 'simple')
const.constantize                                # => Simple
Object.const_defined?(:Simple)                   # => true

const.unload                                     # => Simple
Object.const_defined?(:Simple)                   # => false

const.constantize                                # => Simple
Object.const_defined?(:Simple)                   # => true

Unloading and reloading works best for scripts that have no side effects; ie scripts that do not require other files and only define the specified class or module.

Rationale for that last statement

Scripts that require other files will not re-require the other files because unload doesn’t remove the other files from $“. Likewise scripts that define other constants effectively overwrite the existing constant; that may or may not be a big deal, but it can cause warnings. Moreover, if a script actually DOES something (like create a file), that something will be repeated when it gets re-required.

Constant Summary collapse

CONST_REGEXP =

Matches a valid constant. After the match:

$1:: The unqualified constant (ex 'Const' for '::Const')
/\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(const_name, *require_paths) ⇒ Constant

Initializes a new Constant with the specified constant name, and require_paths. Raises an error if const_name is not valid.



157
158
159
160
161
# File 'lib/tap/env/constant.rb', line 157

def initialize(const_name, *require_paths)
  @types = {}
  @const_name = normalize(const_name)
  @require_paths = require_paths
end

Instance Attribute Details

#const_nameObject (readonly)

The full constant name



146
147
148
# File 'lib/tap/env/constant.rb', line 146

def const_name
  @const_name
end

#require_pathsObject (readonly)

An array of paths that will be required when the constantize is called and the constant does not exist. Require paths are required in order.



150
151
152
# File 'lib/tap/env/constant.rb', line 150

def require_paths
  @require_paths
end

#typesObject (readonly)

A hash of (type, summary) pairs used to classify self.



153
154
155
# File 'lib/tap/env/constant.rb', line 153

def types
  @types
end

Class Method Details

.cast(obj) ⇒ Object



113
114
115
116
117
118
119
120
# File 'lib/tap/env/constant.rb', line 113

def cast(obj)
  case obj
  when String   then new(obj)
  when Module   then new(obj.to_s)
  when Constant then obj
  else raise ArgumentError, "not a constant or constant name: #{obj.inspect}"
  end
end

.constantize(const_name, const = Object) ⇒ Object

Constantize tries to look up the specified constant under const. A block may be given to manually look up missing constants; the last existing const and any non-existant constant names are yielded to the block, which is expected to return the desired constant. For instance in the example ‘Non::Existant’ is essentially mapping to ConstName.

module ConstName; end

Constant.constantize('ConstName')                     # => ConstName
Constant.constantize('Non::Existant') { ConstName }   # => ConstName

Raises a NameError for invalid/missing constants.



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
# File 'lib/tap/env/constant.rb', line 69

def constantize(const_name, const=Object) # :yields: const, missing_const_names
  unless CONST_REGEXP =~ const_name
    raise NameError, "#{const_name.inspect} is not a valid constant name!"
  end

  constants = $1.split(/::/)
  while !constants.empty?
    unless const_is_defined?(const, constants[0])
      if block_given? 
        return yield(const, constants)
      else
        raise NameError.new("uninitialized constant #{const_name}", constants[0]) 
      end
    end
    const = const.const_get(constants.shift)
  end
  const
end

.scan(dir, pattern = "**/*.rb") ⇒ Object

Scans the directory and pattern for constants.



89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/tap/env/constant.rb', line 89

def scan(dir, pattern="**/*.rb")
  constants = {}
  
  root = Root.new(dir)
  root.glob(pattern).each do |path|
    Lazydoc::Document.scan(File.read(path)) do |const_name, type, summary|
      require_path = root.relative_path(path)
      
      if const_name.empty?
        extname = File.extname(path)
        const_name = require_path.chomp(extname).camelize
      end
      
      constant = (constants[const_name] ||= new(const_name))
      constant.register_as(type, summary)
      constant.require_paths << require_path
    end
  end

  constants = constants.values
  constants.each {|constant| constant.require_paths.uniq! }
  constants
end

Instance Method Details

#<=>(another) ⇒ Object

Peforms comparison of the const_name of self vs another.



224
225
226
# File 'lib/tap/env/constant.rb', line 224

def <=>(another)
  const_name <=> another.const_name
end

#==(another) ⇒ Object

True if another is a Constant with the same const_name, require_path, and comment as self.



217
218
219
220
221
# File 'lib/tap/env/constant.rb', line 217

def ==(another)
  another.kind_of?(Constant) && 
  another.const_name == self.const_name &&
  another.require_paths == self.require_paths
end

#basenameObject

Returns the basename of path.

Constant.new("Const::Name").basename       # => 'name'


179
180
181
# File 'lib/tap/env/constant.rb', line 179

def basename
  @basename ||= File.basename(path)
end

#constantize(autorequire = true) ⇒ Object

Looks up and returns the constant indicated by const_name. If the constant cannot be found, constantize requires the require_paths in order and tries again.

Raises a NameError if the constant cannot be found.



244
245
246
247
248
249
250
251
252
253
254
# File 'lib/tap/env/constant.rb', line 244

def constantize(autorequire=true)
  Constant.constantize(const_name) do
    break unless autorequire
    
    require_paths.each do |require_path|
      require require_path
    end
    
    Constant.constantize(const_name)
  end
end

#dirnameObject

Returns the path, minus the basename of path.

Constant.new("Const::Name").dirname        # => '/const'


187
188
189
# File 'lib/tap/env/constant.rb', line 187

def dirname
  @dirname ||= File.dirname(path)
end

#inspectObject

Returns a string like:

"#<Tap::Env::Constant:object_id Const::Name (require_path)>"


299
300
301
# File 'lib/tap/env/constant.rb', line 299

def inspect
  "#<#{self.class}:#{object_id} #{const_name} #{require_paths.inspect}>"
end

#nameObject

Returns the name of the constant, minus nesting.

Constant.new("Const::Name").name           # => 'Name'


195
196
197
# File 'lib/tap/env/constant.rb', line 195

def name
  @name ||= (const_name =~ /.*::(.*)\z/ ? $1 : const_name)
end

#nestingObject

Returns the nesting constant of const_name.

Constant.new("Const::Name").nesting        # => 'Const'


203
204
205
# File 'lib/tap/env/constant.rb', line 203

def nesting
  @nesting ||= (const_name =~ /(.*)::.*\z/ ? $1 : '')
end

#nesting_depthObject

Returns the number of constants in nesting.

Constant.new("Const::Name").nesting_depth  # => 1


211
212
213
# File 'lib/tap/env/constant.rb', line 211

def nesting_depth
  @nesting_depth ||= nesting.split(/::/).length
end

#pathObject

Returns the underscored const_name.

Constant.new("Const::Name").path           # => '/const/name'


171
172
173
# File 'lib/tap/env/constant.rb', line 171

def path
  @path ||= "/#{relative_path}"
end

#path_match?(head, tail = nil) ⇒ Boolean

Returns:

  • (Boolean)


283
284
285
# File 'lib/tap/env/constant.rb', line 283

def path_match?(head, tail=nil)
  (head.nil? || head.empty? || head_match(head)) && (tail.nil? || tail.empty? || tail_match(tail))
end

#register_as(type, summary = nil, override = false) ⇒ Object

Registers the type and summary with self. Raises an error if self is already registerd as the type and override is false.



230
231
232
233
234
235
236
237
# File 'lib/tap/env/constant.rb', line 230

def register_as(type, summary=nil, override=false)
  if types.include?(type) && types[type] != summary && !override
    raise "already registered as a #{type.inspect} (#{const_name})"
  end
  
  types[type] = summary
  self
end

#relative_pathObject



163
164
165
# File 'lib/tap/env/constant.rb', line 163

def relative_path
  @relative_path ||= const_name.underscore
end

#to_sObject

Returns const_name



304
305
306
# File 'lib/tap/env/constant.rb', line 304

def to_s
  const_name
end

#type_match?(type) ⇒ Boolean

Returns:

  • (Boolean)


287
288
289
290
291
292
293
# File 'lib/tap/env/constant.rb', line 287

def type_match?(type)
  case type
  when nil   then true
  when Array then type.any? {|t| types.has_key?(t) } 
  else types.has_key?(type)
  end
end

#unload(unrequire = true) ⇒ Object

Undefines the constant indicated by const_name. The nesting constants are not removed. If specified, the require_paths will be removed from $“.

When removing require_path, unload will add ‘.rb’ to the require_path if require_path has no extension (this echos the behavior of require). Other extension names like ‘.so’, ‘.dll’, etc. are not tried and will not be removed.

Does nothing if const_name doesn’t exist. Returns the unloaded constant. Obviously, this method should be used with caution.



266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/tap/env/constant.rb', line 266

def unload(unrequire=true)
  const = nesting.empty? ? Object : Constant.constantize(nesting) { Object }

  if const.const_defined?(name)
    require_paths.each do |require_path|
      require_path = File.extname(require_path).empty? ? "#{require_path}.rb" : require_path
      regexp = /#{require_path}$/
      
      $".delete_if {|path| path =~ regexp }
    end if unrequire
  
    return const.send(:remove_const, name)
  end

  nil
end