capsule 1.0.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
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