impromptu 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,10 @@
1
+ 1.1.0 - Added support for resources which extend stdlib classes and modules
2
+ - Added the ability to define exceptional files in a folder. Previously
3
+ folders were either implicitly loaded (all files were loaded), or you
4
+ could manually define which files to load. Now you can mix the two
5
+ styles and have some files automatically loaded, and others with
6
+ custom definitions (useful for files with exceptional names)
7
+
8
+ 1.0.1 - Fix bug where nested resources were not autoloaded
9
+
10
+ 1.0.0 - Initial Release
data/README.rdoc CHANGED
@@ -45,7 +45,12 @@ Because the 'private' component shares the same namespace as the 'framework' com
45
45
 
46
46
  In both these components no file provides the Framework module. In this case, Impromptu will automatically create a blank module. If after an update a file appears which implements the namespace, the blank module will be removed, and the new implementation loaded. Removing this file will cause the blank module to be created again.
47
47
 
48
- Just as a resource may be implemented by multiple files, a file may implement multiple resources. Because Impromptu doesn't scan files before loading them you must manually tell Impromptu which resources a file provides if it provides more than one, or if the name of the resource cannot be inferred from the name of the file. Folder definitions may take a block (as with the 'other' folder from the 'other' component). Calling file within the block informs Impromptu of a file to load, and specifying the :provides option allows you to tell Impromptu the names of the resources implemented in the file.
48
+ Just as a resource may be implemented by multiple files, a file may implement multiple resources. Because Impromptu doesn't scan files before loading them you must manually tell Impromptu which resources a file provides if it provides more than one, or if the name of the resource cannot be inferred from the name of the file. Folder definitions may take a block (as with the 'other' folder from the 'other' component). Calling file within the block informs Impromptu of a file to load, and specifying the :provides option allows you to tell Impromptu the names of the resources implemented in the file. You can set the implicitly_loaded option to false if you would like to restrict the set of files loaded for a folder to the set you define within the passed block.
49
+
50
+ == Extending the Standard Library
51
+ Impromptu deals with resources that implement extensions to the standard library a little differently. When a resource is declared, if the implementation of the resource already exists, it assumed the resource extends a standard Ruby class or module. For instance, a resource extending String falls in to this category.
52
+
53
+ These components are automatically loaded at the start of your program since it's impossible for Impromptu to detect when you use them for the first time. If the folder containing their source files is reloadable, any changes to the files will cause the files to be re-required without undefining the module or class. Normally Impromptu undefines a resource before re-requiring the files which define it to ensure the definition doesn't become 'stale'. This isn't possible with preexisting resources, so a side effect of reloading these files is that methods or constants you remove from your extension will remain defined. You will need to restart your program to ensure a clean definition.
49
54
 
50
55
  == Defining Components
51
56
  Before any resources from a component can be loaded, you must define the components of your system in a block provided to define_components. There are two options: you can either define components directly in this block, or you can call the 'parse_file' method to load a file which defines the components instead.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.1
1
+ 1.1.0
data/impromptu.gemspec CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{impromptu}
8
- s.version = "1.0.1"
8
+ s.version = "1.1.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Will Cannings"]
@@ -18,6 +18,7 @@ Gem::Specification.new do |s|
18
18
  ]
19
19
  s.files = [
20
20
  ".document",
21
+ "CHANGELOG",
21
22
  "LICENSE",
22
23
  "README.rdoc",
23
24
  "Rakefile",
@@ -46,6 +47,10 @@ Gem::Specification.new do |s|
46
47
  "test/framework/other/load.rb",
47
48
  "test/framework/other/two.rb",
48
49
  "test/framework/private/klass.rb",
50
+ "test/framework/private/other.rb",
51
+ "test/framework/stdlib/string.rb",
52
+ "test/framework/stdlib/timeout.rb",
53
+ "test/framework/stdlib/timeout/error.rb",
49
54
  "test/framework/test.components",
50
55
  "test/helper.rb",
51
56
  "test/test_autoload.rb",
@@ -56,6 +61,7 @@ Gem::Specification.new do |s|
56
61
  "test/test_integration.rb",
57
62
  "test/test_ordered_set.rb",
58
63
  "test/test_resource.rb",
64
+ "test/test_stdlib.rb",
59
65
  "test/test_symbol.rb"
60
66
  ]
61
67
  s.homepage = %q{http://github.com/willcannings/impromptu}
@@ -76,6 +82,10 @@ Gem::Specification.new do |s|
76
82
  "test/framework/other/load.rb",
77
83
  "test/framework/other/two.rb",
78
84
  "test/framework/private/klass.rb",
85
+ "test/framework/private/other.rb",
86
+ "test/framework/stdlib/string.rb",
87
+ "test/framework/stdlib/timeout.rb",
88
+ "test/framework/stdlib/timeout/error.rb",
79
89
  "test/helper.rb",
80
90
  "test/test_autoload.rb",
81
91
  "test/test_component.rb",
@@ -85,6 +95,7 @@ Gem::Specification.new do |s|
85
95
  "test/test_integration.rb",
86
96
  "test/test_ordered_set.rb",
87
97
  "test/test_resource.rb",
98
+ "test/test_stdlib.rb",
88
99
  "test/test_symbol.rb"
89
100
  ]
90
101
 
@@ -1,7 +1,7 @@
1
1
  module Impromptu
2
2
  class Folder
3
3
  attr_accessor :folder, :files, :component, :namespace
4
- DEFAULT_OPTIONS = {nested_namespaces: true, reloadable: true}
4
+ DEFAULT_OPTIONS = {nested_namespaces: true, reloadable: true, implicitly_loaded: true}
5
5
  SOURCE_EXTENSIONS = %w{rb so bundle}
6
6
 
7
7
  # Register a new folder containing source files for a
@@ -17,13 +17,15 @@ module Impromptu
17
17
  # will be reloaded every time Impromptu.update is
18
18
  # called, and any modified files will be reloaded,
19
19
  # removed files unloaded, and new files tracked.
20
+ # * implicitly_loaded: true by default. When true, reloads
21
+ # and the initial load of this folder will scan for source
22
+ # files and automatically infer resource definitions.
20
23
  def initialize(path, component, options={}, block)
21
24
  @folder = path.realpath
22
25
  @component = component
23
26
  @options = DEFAULT_OPTIONS.merge(options)
24
27
  @block = block
25
28
  @files = OrderedSet.new
26
- @implicitly_load_all_files = true
27
29
  end
28
30
 
29
31
  # Override eql? so two folders with the same path will be
@@ -44,12 +46,11 @@ module Impromptu
44
46
  # Otherwise the folder is scanned to produce an initial set of files
45
47
  # and resources provided by this folder.
46
48
  def load
47
- if @block.nil?
48
- self.reload_file_set
49
- else
49
+ unless @block.nil?
50
50
  instance_eval &@block
51
51
  @block = nil # prevent the block from being run twice
52
52
  end
53
+ self.reload_file_set
53
54
  end
54
55
 
55
56
  # True if the folder uses nested namespaces
@@ -62,6 +63,12 @@ module Impromptu
62
63
  @options[:reloadable]
63
64
  end
64
65
 
66
+ # True if the folder is implicitly loaded (we scan the folder for
67
+ # source files and automatically infer resources)
68
+ def implicitly_loaded?
69
+ @options[:implicitly_loaded]
70
+ end
71
+
65
72
  # Return the 'base' folder for a file contained within this
66
73
  # folder. For instance, a folder with nested namespaces would
67
74
  # return the path to a file from the root folder. Without
@@ -78,17 +85,16 @@ module Impromptu
78
85
  end
79
86
  end
80
87
 
81
- # Explicitly include a file from this folder. If you use this
82
- # method, only files included by this method will be loaded.
83
- # If you do not use this method, all files within this folder
84
- # will be accessible. For this reason, you probably want to
85
- # separate out files which need to be defined this way into a
86
- # folder separate to the majority of your source files. Options
87
- # may be:
88
+ # Explicitly include a file from this folder. Combined with the
89
+ # implicitly_loaded option set to false, this method allows you
90
+ # to manually define a set of files to load from a folder. If
91
+ # implicitly_loaded is true, this method can be used to provide
92
+ # definitions of exceptional files (for example files which define
93
+ # multiple resources, or files with exceptional names).
94
+ # Options may be:
88
95
  # * provides (required): an array of symbols, or a symbol
89
- # inidicating the name of the resource(s) provided by the file
96
+ # indicating the name of the resource(s) provided by the file
90
97
  def file(name, options={})
91
- @implicitly_load_all_files = false
92
98
  file = Impromptu::File.new(@folder.join(*name).realpath, self, options[:provides])
93
99
  @files.push(file).add_resource_definition
94
100
  end
@@ -99,7 +105,7 @@ module Impromptu
99
105
  # and any previously unseen files loaded, existing files
100
106
  # reloaded, and removed files unloaded.
101
107
  def reload
102
- reload_file_set if @implicitly_load_all_files
108
+ reload_file_set if implicitly_loaded?
103
109
  @files.each {|file| file.reload_if_modified}
104
110
  end
105
111
 
@@ -110,12 +116,12 @@ module Impromptu
110
116
  # as well), and any new files insert their resources in to
111
117
  # the known resources tree.
112
118
  def reload_file_set
113
- return unless @implicitly_load_all_files
119
+ return unless implicitly_loaded?
114
120
  old_file_set = @files.to_a
115
121
  new_file_set = []
116
122
  changes = false
117
123
 
118
- # find all current files and add them if necessary
124
+ # find all source files and add unseen files to the files list
119
125
  @folder.find do |path|
120
126
  next unless source_file?(path)
121
127
  file = Impromptu::File.new(path.realpath, self)
@@ -31,9 +31,7 @@ module Impromptu
31
31
  def self.reset
32
32
  # unload all resources
33
33
  unless @root_resource.nil?
34
- @root_resource.children.each_value do |resource|
35
- resource.unload
36
- end
34
+ @root_resource.unload
37
35
  end
38
36
 
39
37
  # reset lists to nil
@@ -57,6 +55,11 @@ module Impromptu
57
55
  components.each do |component|
58
56
  component.freeze
59
57
  end
58
+
59
+ # preload any resources which extend existing standard library
60
+ # modules or classes. we can't catch uses of these resources
61
+ # using const_missing, so we need to load them now.
62
+ @root_resource.load_if_extending_stdlib
60
63
  end
61
64
 
62
65
  # Open and run a file defining components. The folder containing
@@ -14,6 +14,7 @@ module Impromptu
14
14
  @children = {}
15
15
  @reference = nil
16
16
  @namespace = false
17
+ @dont_undef = self.loaded? # existing constants, such as 'String', should never be unloaded
17
18
  @implicitly_defined = true
18
19
  end
19
20
 
@@ -60,9 +61,11 @@ module Impromptu
60
61
  # unloaded. This allows the resource to be garbage collected.
61
62
  def unload
62
63
  return unless loaded?
63
- @children.each_value {|child| child.unload}
64
- @parent.reference.send(:remove_const, @base_symbol)
65
- @reference = nil
64
+ @children.each_value(&:unload)
65
+ unless @dont_undef
66
+ @parent.reference.send(:remove_const, @base_symbol)
67
+ @reference = nil
68
+ end
66
69
  end
67
70
 
68
71
  # Start tracking a file which implements this resource. If the
@@ -192,5 +195,14 @@ module Impromptu
192
195
  return false unless @parent && @parent.loaded? && @parent.reference
193
196
  parent.reference.constants.include?(@base_symbol)
194
197
  end
198
+
199
+ # Loads this resource if it is an extension of an existing class
200
+ # or module (such as an object in the standard library). Should
201
+ # only be called on app startup by Impromptu itself. Recurses to
202
+ # this resource's children as well.
203
+ def load_if_extending_stdlib
204
+ reload if loaded? && !self.root?
205
+ @children.each_value(&:load_if_extending_stdlib)
206
+ end
195
207
  end
196
208
  end
@@ -0,0 +1,4 @@
1
+ module Framework
2
+ module Another
3
+ end
4
+ end
@@ -0,0 +1,5 @@
1
+ class String
2
+ def extra_method
3
+ true
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ module Timeout
2
+ class Error
3
+ def self.ext_two
4
+ true
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Timeout
2
+ def self.ext_one
3
+ true
4
+ end
5
+ end
@@ -10,14 +10,16 @@ component 'framework.extensions' do
10
10
  end
11
11
 
12
12
  component 'other' do
13
- folder 'other' do
13
+ folder 'other', implicitly_loaded: false do
14
14
  file 'load.rb'
15
- file 'also.rb', :provides => :OtherName
16
- file 'two.rb', :provides => [:ModOne, :ModTwo, :OtherName]
15
+ file 'also.rb', provides: :OtherName
16
+ file 'two.rb', provides: [:ModOne, :ModTwo, :OtherName]
17
17
  end
18
18
  end
19
19
 
20
20
  component 'private' do
21
21
  namespace :Framework
22
- folder 'private'
22
+ folder 'private' do
23
+ file 'other.rb', provides: :Another
24
+ end
23
25
  end
@@ -21,6 +21,7 @@ class TestIntegration < Test::Unit::TestCase
21
21
  assert_equal 1, Impromptu.components['framework'].folders.size
22
22
  assert_equal 1, Impromptu.components['framework.extensions'].folders.size
23
23
  assert_equal 1, Impromptu.components['other'].folders.size
24
+ assert_equal 1, Impromptu.components['private'].folders.size
24
25
  end
25
26
 
26
27
  should "03 have a single require in the framework component" do
@@ -37,7 +38,7 @@ class TestIntegration < Test::Unit::TestCase
37
38
  assert_equal 2, Impromptu.components['framework'].folders.first.files.size
38
39
  assert_equal 2, Impromptu.components['framework.extensions'].folders.first.files.size
39
40
  assert_equal 3, Impromptu.components['other'].folders.first.files.size
40
- assert_equal 1, Impromptu.components['private'].folders.first.files.size
41
+ assert_equal 2, Impromptu.components['private'].folders.first.files.size
41
42
  end
42
43
 
43
44
  should "06 load definitions for 9 resources" do
@@ -50,6 +51,7 @@ class TestIntegration < Test::Unit::TestCase
50
51
  assert Impromptu.root_resource.child?(:OtherName)
51
52
  assert Impromptu.root_resource.child?(:ModOne)
52
53
  assert Impromptu.root_resource.child?(:ModTwo)
54
+ assert Impromptu.root_resource.child(:Framework).child?(:Another)
53
55
  end
54
56
 
55
57
  should "07 correctly mark namespace resources" do
@@ -62,6 +64,7 @@ class TestIntegration < Test::Unit::TestCase
62
64
  assert_equal false, Impromptu.root_resource.child(:OtherName).namespace?
63
65
  assert_equal false, Impromptu.root_resource.child(:ModOne).namespace?
64
66
  assert_equal false, Impromptu.root_resource.child(:ModTwo).namespace?
67
+ assert_equal false, Impromptu.root_resource.child(:Framework).child(:Another).namespace?
65
68
  end
66
69
 
67
70
  should "08 keep all resources unloaded to start with" do
@@ -74,6 +77,7 @@ class TestIntegration < Test::Unit::TestCase
74
77
  assert_equal false, Impromptu.root_resource.child(:'OtherName').loaded?
75
78
  assert_equal false, Impromptu.root_resource.child(:'ModOne').loaded?
76
79
  assert_equal false, Impromptu.root_resource.child(:'ModTwo').loaded?
80
+ assert_equal false, Impromptu.root_resource.child(:Framework).child(:'Another').loaded?
77
81
  end
78
82
 
79
83
  should "09 have all resources specified by the correct number of files" do
@@ -87,6 +91,7 @@ class TestIntegration < Test::Unit::TestCase
87
91
  assert_equal 2, Impromptu.root_resource.child(:'OtherName').files.size
88
92
  assert_equal 1, Impromptu.root_resource.child(:'ModOne').files.size
89
93
  assert_equal 1, Impromptu.root_resource.child(:'ModTwo').files.size
94
+ assert_equal 1, Impromptu.root_resource.child(:Framework).child(:'Another').files.size
90
95
  assert_equal true, Impromptu.root_resource.child(:'Framework').implicitly_defined?
91
96
  end
92
97
 
@@ -148,6 +153,13 @@ class TestIntegration < Test::Unit::TestCase
148
153
  assert_nothing_raised do
149
154
  ModTwo
150
155
  end
156
+
157
+ # private
158
+ Impromptu.root_resource.child(:Framework).child(:Another).reload
159
+ assert_equal true, Impromptu.root_resource.child(:Framework).child(:Another).loaded?
160
+ assert_nothing_raised do
161
+ Framework::Another
162
+ end
151
163
  end
152
164
 
153
165
  should "12 load multiple files for a resource when required" do
@@ -253,6 +265,22 @@ class TestIntegration < Test::Unit::TestCase
253
265
  end
254
266
  end
255
267
 
268
+ context "and adding files to an explicitly defined folder" do
269
+ setup do
270
+ FileUtils.mv 'test/framework/copies/new_unseen.rb', 'test/framework/other/unseen.rb'
271
+ end
272
+
273
+ teardown do
274
+ FileUtils.mv 'test/framework/other/unseen.rb', 'test/framework/copies/new_unseen.rb'
275
+ end
276
+
277
+ should "not make add a file definition to the folder" do
278
+ assert_equal 3, Impromptu.components['other'].folders.first.files.size
279
+ Impromptu.update
280
+ assert_equal 3, Impromptu.components['other'].folders.first.files.size
281
+ end
282
+ end
283
+
256
284
 
257
285
  # ----------------------------------------
258
286
  # Removing files
@@ -0,0 +1,34 @@
1
+ require 'helper'
2
+
3
+ class TestStdlib < Test::Unit::TestCase
4
+ context "A component with resources extending the standard library" do
5
+ setup do
6
+ Impromptu.reset
7
+ Impromptu.define_components do
8
+ component 'stdlib' do
9
+ folder 'test/framework/stdlib'
10
+ end
11
+ end
12
+ end
13
+
14
+ should "have a single component, and an extension for String" do
15
+ assert_equal 1, Impromptu.components.size
16
+ assert_not_nil Impromptu.root_resource.child(:String)
17
+ end
18
+
19
+ should "automatically load the String resource" do
20
+ assert_respond_to "string", :extra_method
21
+ assert "string".extra_method
22
+ end
23
+
24
+ should "automatically load nested resources" do
25
+ # timeout
26
+ assert_respond_to Timeout, :ext_one
27
+ assert Timeout.ext_one
28
+
29
+ # timeout::error
30
+ assert_respond_to Timeout::Error, :ext_two
31
+ assert Timeout::Error.ext_two
32
+ end
33
+ end
34
+ end
metadata CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
4
4
  prerelease: false
5
5
  segments:
6
6
  - 1
7
- - 0
8
7
  - 1
9
- version: 1.0.1
8
+ - 0
9
+ version: 1.1.0
10
10
  platform: ruby
11
11
  authors:
12
12
  - Will Cannings
@@ -41,6 +41,7 @@ extra_rdoc_files:
41
41
  - README.rdoc
42
42
  files:
43
43
  - .document
44
+ - CHANGELOG
44
45
  - LICENSE
45
46
  - README.rdoc
46
47
  - Rakefile
@@ -69,6 +70,10 @@ files:
69
70
  - test/framework/other/load.rb
70
71
  - test/framework/other/two.rb
71
72
  - test/framework/private/klass.rb
73
+ - test/framework/private/other.rb
74
+ - test/framework/stdlib/string.rb
75
+ - test/framework/stdlib/timeout.rb
76
+ - test/framework/stdlib/timeout/error.rb
72
77
  - test/framework/test.components
73
78
  - test/helper.rb
74
79
  - test/test_autoload.rb
@@ -79,6 +84,7 @@ files:
79
84
  - test/test_integration.rb
80
85
  - test/test_ordered_set.rb
81
86
  - test/test_resource.rb
87
+ - test/test_stdlib.rb
82
88
  - test/test_symbol.rb
83
89
  has_rdoc: true
84
90
  homepage: http://github.com/willcannings/impromptu
@@ -126,6 +132,10 @@ test_files:
126
132
  - test/framework/other/load.rb
127
133
  - test/framework/other/two.rb
128
134
  - test/framework/private/klass.rb
135
+ - test/framework/private/other.rb
136
+ - test/framework/stdlib/string.rb
137
+ - test/framework/stdlib/timeout.rb
138
+ - test/framework/stdlib/timeout/error.rb
129
139
  - test/helper.rb
130
140
  - test/test_autoload.rb
131
141
  - test/test_component.rb
@@ -135,4 +145,5 @@ test_files:
135
145
  - test/test_integration.rb
136
146
  - test/test_ordered_set.rb
137
147
  - test/test_resource.rb
148
+ - test/test_stdlib.rb
138
149
  - test/test_symbol.rb