capsule 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/COPYING.rdoc ADDED
@@ -0,0 +1,24 @@
1
+ = COPYRIGHT NOTICES
2
+
3
+ == Capsule
4
+
5
+ Copyright (C)2007 Thomas Sawyer
6
+
7
+ Unless otherwise agreed upon by the copyright holder this software is
8
+ made available under the terms of the GNU Affero General Public License
9
+ (see AGPL3.txt.)
10
+
11
+ (http://rubyworks.github.com/capsule)
12
+
13
+
14
+ == Script
15
+
16
+ Capsule is a derivation of Joel VanderWerf's Script library.
17
+
18
+ Usable under the Ruby license.
19
+
20
+ Copyright (C)2004 Joel VanderWerf.
21
+
22
+ Questions to mailto:vjoel@users.sourceforge.net.
23
+
24
+ (http://redshift.sourceforge.net/script/)
data/HISTORY.rdoc ADDED
@@ -0,0 +1,27 @@
1
+ = RELEASE HISTORY
2
+
3
+ == 1.1.0 / 2011-05-12
4
+
5
+ This release makes two small enhancements to the Capsule class.
6
+ It will automatically try `.rb` extension of script names that
7
+ lack an extension and it now provides an `:extend` option which
8
+ can be set to `false` to deactive a capsules sef extension
9
+ (`extend self`).
10
+
11
+ Changes:
12
+
13
+ * Search for scripts with common ruby extensions.
14
+ * Add :extend option to control self extension.
15
+
16
+
17
+ == 1.0.0 / 2009-07-01
18
+
19
+ This is the initial stand-alone release of Capsule,
20
+ spun-off from Ruby Facets.
21
+
22
+ Changes:
23
+
24
+ * 1 Major Enhancement
25
+
26
+ * Happy Birthday!
27
+
data/README.rdoc ADDED
@@ -0,0 +1,154 @@
1
+ = Capsule
2
+
3
+
4
+ == Description
5
+
6
+ Capsule is a subclass of Module. A module which is an instance of the Capsule
7
+ class encapsulates in its scope the top-level methods, top-level constants, and
8
+ instance variables defined in a Ruby script (and its dependent files)
9
+ loaded by a Ruby program. This allows use of script files to define objects that
10
+ can be loaded into a program in much the same way that objects can be loaded
11
+ from YAML or Marshaled files. There is also an autoimport method which functions
12
+ like Ruby's autoload but based on is Capsule.load.
13
+
14
+
15
+ == Resources
16
+
17
+ * home: http://rubyworks.github.com/capsule
18
+ * work: http://github.com/rubyworks/capsule
19
+ * mail: http://groups.google.com/group/rubyworks-mailinglist
20
+
21
+
22
+ == Synopsis
23
+
24
+ To encapsulate a script in a Capsule:
25
+
26
+ myscript = Capsule.new('myscript.rb')
27
+
28
+ If the script is in Ruby's $LOAD_PATH, then you can use +Capsule.load+.
29
+
30
+ myscript = Capsule.load('myscript.rb')
31
+
32
+ Here is an example:
33
+
34
+ # myscript.rb
35
+
36
+ VALUE = [1,2,3]
37
+
38
+ def run
39
+ puts "#{self} is running."
40
+ end
41
+
42
+ And the encapsulating program:
43
+
44
+ # program.rb:
45
+
46
+ require 'capsule'
47
+
48
+ myscript = Capsule.load("myscript.rb")
49
+
50
+ p myscript::VALUE
51
+
52
+ myscript.run
53
+
54
+ Running `program.rb` will result in:
55
+
56
+ $ ruby program.rb
57
+ [1, 2, 3]
58
+ #<Capsule:myscript.rb> is running.
59
+
60
+
61
+ == Usage
62
+
63
+ Capsule modules are instantiated with <tt>Capsule.new(main_file)</tt> or the alias
64
+ <tt>Capsule.load(main_file)</tt>. All the top-level constants and top-level
65
+ methods that are defined in the +main_file+ and its dependent local files (see
66
+ below) are scoped in the same Capsule module, and are thereby available to the
67
+ calling program.
68
+
69
+ The +main_file+ can load or require other files with +load+ and +require+, as
70
+ usual. These methods, in the Capsule context, add some behavior to the +Kernel+
71
+ +load+ and +require+ methods: <tt>Capsule#load</tt> and <tt>Capsule#require</tt>
72
+ first search for files relative to the +main_file+'s dir. Files loaded in this
73
+ way ("dependent local files") are treated like the script file itself: top-level
74
+ definitions are added to the script module that is returned by +load+ or
75
+ +require+.
76
+
77
+ Both <tt>Capsule#load</tt> and <tt>Capsule#require</tt> fall back to the Kernel
78
+ versions if the file is not found locally. Hence, other ruby libraries can be
79
+ loaded and required as usual, assuming their names do not conflict with local
80
+ file names. Definitions from those files go into the usual scope (typically
81
+ global). The normal ruby +load+ and +require+ behavior can be forced by calling
82
+ <tt>Kernel.load</tt> and <tt>Kernel.require</tt>.
83
+
84
+ A Capsule immitates the way the top-level ruby context works, so a ruby file that
85
+ was originally intended to be run from the top level, defining top-level
86
+ constants and top-level methods, can also be run as a Capsule, and its top-level
87
+ constants and top-level methods are wrapped in the script's scope. The
88
+ difference between this behavior and simply wrapping the loaded definitions in
89
+ an _anonymous_ module using <tt>Kernel.load(main_file, true)</tt> is that the
90
+ top-level methods and top-level constants defined in the script are accessible
91
+ using the Capsule instance.
92
+
93
+ The top-level definitions of a Capsule can be accessed after it has been
94
+ loaded, as follows:
95
+
96
+ <tt>script.meth</tt>
97
+
98
+ - Call a method defined using <tt>def meth</tt> or <tt>def self.meth</tt> in
99
+ the script file.
100
+
101
+ <tt>script::K</tt>
102
+
103
+ - Access a class, module, or constant defined using <tt>K = val</tt> in the
104
+ script file.
105
+
106
+ An "input" can be passed to the script before loading. Simply call Capsule.new
107
+ (or Capsule.load) with a block. The block is passed a single argument, the
108
+ Capsule module, and executed before the files are loaded into the Capsule's
109
+ scope. Setting a constant in this block makes the constant available to the
110
+ script during loading. For example:
111
+
112
+ script = Capsule.load("my-script.rb") { |capsule| capsule::INPUT = 3 }
113
+
114
+ Note that all methods defined in the script file are both instance methods of
115
+ the module and methods of the module instance (the effect of
116
+ <tt>Module#module_function</tt>). So <tt>include</tt>-ing a Capsule module in a
117
+ class will give instances of the class all the methods and constants defined in
118
+ the script, and they will reference the instance's instance variables,
119
+ rather than the Capsule module's instance variables.
120
+
121
+ The Capsule class was inspired by Nobu Nokada's suggestion in
122
+ http://ruby-talk.org/62727, in a thread (started in http://ruby-talk.org/62660)
123
+ about how to use ruby script files as specifications of objects.
124
+
125
+
126
+ == Installation
127
+
128
+ To install with RubyGems simply open a console and type:
129
+
130
+ gem install capsule
131
+
132
+ Local installation requires Setup.rb (gem install setup),
133
+ then download the tarball package and type:
134
+
135
+ tar -xvzf capsule-1.0.0.tgz
136
+ cd capsule-1.0.0
137
+ sudo setup.rb all
138
+
139
+ Windows users use 'ruby setup.rb all'.
140
+
141
+
142
+ == Legal
143
+
144
+ (AGPL 3 License)
145
+
146
+ Copyright (c) 2007 Thomas Sawyer
147
+ Copyright (c) 2004 Joel VanderWerf
148
+
149
+ Capsule is based on Joel VanderWerf's Script library.
150
+
151
+ Unless otherwise agreed upon by the copyright holder, this program is
152
+ ditributed under the terms of the GNU Affero General Public License v3.
153
+
154
+ See COPYING.rdoc file for details.
data/lib/capsule.rb CHANGED
@@ -1,8 +1,6 @@
1
1
  #require 'rbconfig'
2
+ require 'capsule/autoimport'
2
3
 
3
- # A Capsule is subclass of Module. It encapsulates an extenal script
4
- # as a funcitons module.
5
- #
6
4
  # A module which is an instance of the Capsule class encapsulates in its scope
7
5
  # the top-level methods, top-level constants, and instance variables defined in
8
6
  # a ruby script file (and its subfiles) loaded by a ruby program. This allows
@@ -15,7 +13,13 @@ class Capsule < Module
15
13
 
16
14
  #DLEXT = Config::CONFIG['DLEXT']
17
15
 
18
- # The script file with which the Import was instantiated.
16
+ # Ruby script extensions to automatically try when searching for
17
+ # a script on the load_path.
18
+
19
+ SUFFIXES = ['.rb', '.rbs'] #, '.rbw', '.so', '.bundle', '.dll', '.sl', '.jar']
20
+
21
+ # The script file with which the import was instantiated.
22
+
19
23
  attr_reader :main_file
20
24
 
21
25
  # The directory in which main_file is located, and relative to which
@@ -23,29 +27,29 @@ class Capsule < Module
23
27
  #attr_reader :dir
24
28
 
25
29
  # An array of paths to search for scripts. This has the same
26
- # semantics as <tt>$:</tt>, alias <tt>$LOAD_PATH</tt>, excpet
30
+ # semantics as <tt>$:</tt>, alias <tt>$LOAD_PATH</tt>, except
27
31
  # that it is local to this script. The path of the current
28
- # script is added automatically (equivalent to '.')
32
+ # script is added automatically.
33
+
29
34
  attr_reader :load_path
30
35
 
31
36
  # A hash that maps <tt>filename=>true</tt> for each file that has been
32
37
  # required locally by the script. This has the same semantics as <tt>$"</tt>,
33
38
  # alias <tt>$LOADED_FEATURES</tt>, except that it is local to this script.
39
+
34
40
  attr_reader :loaded_features
35
41
 
36
- class << self
37
- # As with #new but will search Ruby's $LOAD_PATH first.
38
- #--
39
- # Will also try .rb, .so, .dll, et al extensions, like require does.
40
- #++
41
- def load(main_file, options=nil, &block)
42
- file = nil
43
- $LOAD_PATH.each do |path|
44
- break if file = File.file?(File.join(path, main_file))
45
- #break if file = Dir.glob(File.join(path, main_file)+'{,.rb,.'+DLEXT+'}')[0]
46
- end
47
- new(file || main_file, options=nil, &block)
42
+ # As with #new but will search Ruby's $LOAD_PATH first.
43
+ # This will also try `.rb` extensions, like require does.
44
+
45
+ def self.load(main_file, options=nil, &block)
46
+ file = nil
47
+ $LOAD_PATH.each do |path|
48
+ file = File.join(path, main_file)
49
+ break if file = File.file?(file)
50
+ break if file = Dir.glob(file + '{' + SUFFIXES.join(',') + '}').first
48
51
  end
52
+ new(file || main_file, options=nil, &block)
49
53
  end
50
54
 
51
55
  # Creates new Capsule, and loads _main_file_ in the scope of the script. If a
@@ -53,25 +57,24 @@ class Capsule < Module
53
57
  # constants can be defined as inputs to the script.
54
58
 
55
59
  def initialize(main_file, options=nil, &block)
56
- extend self
57
-
58
60
  options ||= {}
59
61
 
60
62
  @main_file = File.expand_path(main_file)
63
+
61
64
  @load_path = options[:load_path] || []
62
- #@load_path |= [File.dirname(@main_file)] # before or after?
63
65
  @loaded_features = options[:loaded_features] || {}
64
66
 
65
- # TODO In order to load/require at the instance level.
66
- # This needs to be in a separate namespace however
67
- # b/c it can interfere with what is expected.
68
- #[ :require, :load ].each{ |meth|
69
- # m = method(meth)
70
- # define_method(meth) do |*args| m.call(*args) end
71
- #}
67
+ @extend = true # default
68
+ @extend = options[:extend] if options.key?(:extend)
69
+
70
+ ## add script's path to load_path
71
+ ## TODO: should we be doing this?
72
+ @load_path |= [File.dirname(@main_file)]
73
+
74
+ ## if @extend (the default) module extends itself
75
+ extend self if @extend
72
76
 
73
77
  module_eval(&block) if block
74
- extend self
75
78
 
76
79
  load_in_module(main_file)
77
80
  end
@@ -79,7 +82,7 @@ class Capsule < Module
79
82
  # Lookup feature in load path.
80
83
 
81
84
  def load_path_lookup(feature)
82
- paths = File.join('{' + @load_path.join(',') + '}', feature + '{,.rb,.rbs}')
85
+ paths = File.join('{' + @load_path.join(',') + '}', feature + '{' + SUFFIXES + '}')
83
86
  files = Dir.glob(paths)
84
87
  match = files.find{ |f| ! @loaded_features.include?(f) }
85
88
  return match
@@ -98,13 +101,11 @@ class Capsule < Module
98
101
  #
99
102
  # Typically called from within the main file to load additional sub files, or
100
103
  # from those sub files.
101
- #
102
- #--
103
- # TODO Need to add load_path lookup.
104
- #++
105
104
 
106
105
  def load(file, wrap = false)
107
- load_in_module(File.join(@dir, file))
106
+ file = load_path_lookup(feature)
107
+ return super unless file
108
+ load_in_module(file) #File.join(@dir, file))
108
109
  true
109
110
  rescue MissingFile
110
111
  super
@@ -120,8 +121,7 @@ class Capsule < Module
120
121
  # extension or is not found locally.
121
122
  #
122
123
  #--
123
- # This was using load_in_module rather than include_script. Maybe is still should
124
- # and one should have to call include_script instead? Think about this.
124
+ # TODO: Should this be using #include_script instead?
125
125
  #++
126
126
 
127
127
  def require(feature)
@@ -136,25 +136,40 @@ class Capsule < Module
136
136
  end
137
137
  end
138
138
 
139
- # Raised by #load_in_module, caught by #load and #require.
140
- class MissingFile < LoadError; end
139
+ # Checks the class of each +mods+. If a String, then calls #include_script,
140
+ # otherwise behaves like normal #include.
141
141
 
142
- # Loads _file_ in this module's context.Thomas Sawyer Note that <tt>\_\_FILE\_\_</tt> and
143
- # <tt>\_\_LINE\_\_</tt> work correctly in _file_.
144
- # Called by #load and #require; not normally called directly.
142
+ def include(*mods)
143
+ mods.reverse_each do |mod|
144
+ case mod
145
+ when String
146
+ include_script(mod)
147
+ else
148
+ super(mod)
149
+ extend self if @extend
150
+ end
151
+ end
152
+ end
145
153
 
146
- def load_in_module(file)
147
- module_eval(IO.read(file), File.expand_path(file))
154
+ # Create a new Capsule for a script and include it into the current capsule.
155
+
156
+ def include_script(file)
157
+ include self.class.new(file, :load_path=>load_path, :loaded_features=>loaded_features, :extend=>false)
148
158
  rescue Errno::ENOENT => e
149
159
  if /#{file}$/ =~ e.message
150
160
  raise MissingFile, e.message
151
161
  else
152
162
  raise
153
163
  end
164
+ extend self if @extend
154
165
  end
155
166
 
156
- def include_script(file)
157
- include self.class.new(file, :load_path=>load_path, :loaded_features=>loaded_features)
167
+ # Loads _file_ in this module's context. Note that <tt>\_\_FILE\_\_</tt> and
168
+ # <tt>\_\_LINE\_\_</tt> work correctly in _file_.
169
+ # Called by #load and #require; not normally called directly.
170
+
171
+ def load_in_module(file)
172
+ module_eval(IO.read(file), File.expand_path(file))
158
173
  rescue Errno::ENOENT => e
159
174
  if /#{file}$/ =~ e.message
160
175
  raise MissingFile, e.message
@@ -163,59 +178,15 @@ class Capsule < Module
163
178
  end
164
179
  end
165
180
 
166
- #
167
- def include(*mods)
168
- super
169
- extend self
170
- end
181
+ # Give inspection of Capsule with script file name.
171
182
 
172
- def to_s # :nodoc:
183
+ def inspect # :nodoc:
173
184
  "#<#{self.class}:#{main_file}>"
174
185
  end
175
186
 
176
- end
177
-
178
- # TODO Is autoimport bets name for this?
179
-
180
- class Module
181
-
182
- const_missing_definition_for_autoimport = lambda do
183
- #$autoimport_activated = true
184
- alias const_missing_before_autoimport const_missing
185
-
186
- def const_missing(sym) # :nodoc:
187
- filename = @autoimport && @autoimport[sym]
188
- if filename
189
- mod = Import.load(filename)
190
- const_set sym, mod
191
- else
192
- const_missing_before_autoimport(sym)
193
- end
194
- end
195
- end
187
+ # Raised by #load_in_module, caught by #load and #require.
196
188
 
197
- # When the constant named by symbol +mod+ is referenced, loads the script
198
- # in filename using Capsule.load and defines the constant to be equal to the
199
- # resulting Capsule module.
200
- #
201
- # Use like Module#autoload--however, the underlying opertation is #load rather
202
- # than #require, because scripts, unlike libraries, can be loaded more than
203
- # once. See examples/autoscript-example.rb
189
+ class MissingFile < ::LoadError; end
204
190
 
205
- define_method(:autoimport) do |mod, file|
206
- if @autoimport.empty? #unless $autoimport_activated
207
- const_missing_definition_for_autoimport.call
208
- end
209
- (@autoimport ||= {})[mod] = file
210
- end
211
191
  end
212
192
 
213
-
214
- module Kernel
215
-
216
- # Calls Object.autoimport
217
- def autoimport(mod, file)
218
- Object.autoimport(mod, file)
219
- end
220
-
221
- end