Class: Capsule

Inherits:
Module show all
Defined in:
lib/capsule.rb

Overview

A module which is an instance of the Capsule class encapsulates in its scope the top-level methods, top-level constants, and instance variables defined in a ruby script file (and its subfiles) loaded by a ruby program. This allows use of script files to define objects that can be loaded into a program in much the same way that objects can be loaded from YAML or Marshal files.

See intro.txt for an overview.

Defined Under Namespace

Classes: MissingFile

Constant Summary collapse

SUFFIXES =

Ruby script extensions to automatically try when searching for a script on the load_path.

['.rb', '.rbs']

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(main_file, options = nil, &block) ⇒ Capsule

Creates new Capsule, and loads main_file in the scope of the script. If a block is given, the script is passed to it before loading from the file, and constants can be defined as inputs to the script.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/capsule.rb', line 59

def initialize(main_file, options=nil, &block)
  options ||= {}

  @main_file       = File.expand_path(main_file)

  @load_path       = options[:load_path] || []
  @loaded_features = options[:loaded_features] || {}

  @extend          = true  # default
  @extend          = options[:extend] if options.key?(:extend)

  ## add script's path to load_path
  ## TODO: should we be doing this?
  @load_path |= [File.dirname(@main_file)]

  ## if @extend (the default) module extends itself
  extend self if @extend

  module_eval(&block) if block

  load_in_module(main_file)
end

Instance Attribute Details

#load_pathObject (readonly)

An array of paths to search for scripts. This has the same semantics as $:, alias $LOAD_PATH, except that it is local to this script. The path of the current script is added automatically.



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

def load_path
  @load_path
end

#loaded_featuresObject (readonly)

A hash that maps filename=>true for each file that has been required locally by the script. This has the same semantics as $", alias $LOADED_FEATURES, except that it is local to this script.



40
41
42
# File 'lib/capsule.rb', line 40

def loaded_features
  @loaded_features
end

#main_fileObject (readonly)

The script file with which the import was instantiated.



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

def main_file
  @main_file
end

Class Method Details

.load(main_file, options = nil, &block) ⇒ Object

As with #new but will search Ruby’s $LOAD_PATH first. This will also try ‘.rb` extensions, like require does.



45
46
47
48
49
50
51
52
53
# File 'lib/capsule.rb', line 45

def self.load(main_file, options=nil, &block)
  file = nil
  $LOAD_PATH.each do |path|
    file = File.join(path, main_file)
    break if file = File.file?(file)
    break if file = Dir.glob(file + '{' + SUFFIXES.join(',') + '}').first
  end
  new(file || main_file, options=nil, &block)
end

Instance Method Details

#include(*mods) ⇒ Object

Checks the class of each mods. If a String, then calls #include_script, otherwise behaves like normal #include.



142
143
144
145
146
147
148
149
150
151
152
# File 'lib/capsule.rb', line 142

def include(*mods)
  mods.reverse_each do |mod|
    case mod
    when String
      include_script(mod)
    else
      super(mod)
      extend self if @extend
    end
  end
end

#include_script(file) ⇒ Object

Create a new Capsule for a script and include it into the current capsule.



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

def include_script(file)
  include self.class.new(file, :load_path=>load_path, :loaded_features=>loaded_features, :extend=>false)
rescue Errno::ENOENT => e
  if /#{file}$/ =~ e.message
    raise MissingFile, e.message
  else
    raise
  end
  extend self if @extend
end

#inspectObject

Give inspection of Capsule with script file name.



183
184
185
# File 'lib/capsule.rb', line 183

def inspect # :nodoc:
  "#<#{self.class}:#{main_file}>"
end

#load(file, wrap = false) ⇒ Object

Loads file into the capsule. Searches relative to the local dir, that is, the dir of the file given in the original call to Capsule.load(file), loads the file, if found, into this Capsule’s scope, and returns true. If the file is not found, falls back to Kernel.load, which searches on $LOAD_PATH, loads the file, if found, into global scope, and returns true. Otherwise, raises LoadError.

The wrap argument is passed to Kernel.load in the fallback case, when the file is not found locally.

Typically called from within the main file to load additional sub files, or from those sub files.



105
106
107
108
109
110
111
112
# File 'lib/capsule.rb', line 105

def load(file, wrap = false)
  file = load_path_lookup(feature)
  return super unless file
  load_in_module(file) #File.join(@dir, file))
  true
rescue MissingFile
  super
end

#load_in_module(file) ⇒ Object

Loads file in this module’s context. Note that _\FILE\_ and _\LINE\_ work correctly in file. Called by #load and #require; not normally called directly.



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

def load_in_module(file)
  module_eval(IO.read(file), File.expand_path(file))
rescue Errno::ENOENT => e
  if /#{file}$/ =~ e.message
    raise MissingFile, e.message
  else
    raise
  end
end

#load_path_lookup(feature) ⇒ Object

Lookup feature in load path.



84
85
86
87
88
89
# File 'lib/capsule.rb', line 84

def load_path_lookup(feature)
  paths = File.join('{' + @load_path.join(',') + '}', feature + '{' + SUFFIXES + '}')
  files = Dir.glob(paths)
  match = files.find{ |f| ! @loaded_features.include?(f) }
  return match
end

#require(feature) ⇒ Object

Analogous to Kernel#require. First tries the local dir, then falls back to Kernel#require. Will load a given feature only once.

Note that extensions (*.so, *.dll) can be required in the global scope, as usual, but not in the local scope. (This is not much of a limitation in practice–you wouldn’t want to load an extension more than once.) This implementation falls back to Kernel#require when the argument is an extension or is not found locally.

– TODO: Should this be using #include_script instead? ++



127
128
129
130
131
132
133
134
135
136
137
# File 'lib/capsule.rb', line 127

def require(feature)
  file = load_path_lookup(feature)
  return super unless file
  begin
    @loaded_features[file] = true
    load_in_module(file)
  rescue MissingFile
    @loaded_features[file] = false
    super
  end
end