impromptu 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +21 -0
- data/LICENSE +34 -0
- data/README.rdoc +76 -0
- data/Rakefile +53 -0
- data/VERSION +1 -0
- data/impromptu.gemspec +106 -0
- data/lib/impromptu/autoload.rb +33 -0
- data/lib/impromptu/component.rb +124 -0
- data/lib/impromptu/component_set.rb +10 -0
- data/lib/impromptu/file.rb +220 -0
- data/lib/impromptu/folder.rb +151 -0
- data/lib/impromptu/impromptu.rb +81 -0
- data/lib/impromptu/ordered_set.rb +49 -0
- data/lib/impromptu/resource.rb +195 -0
- data/lib/impromptu/symbol.rb +43 -0
- data/lib/impromptu.rb +12 -0
- data/test/framework/copies/extra_klass2.rb +7 -0
- data/test/framework/copies/new_klass.rb +10 -0
- data/test/framework/copies/new_unseen.rb +7 -0
- data/test/framework/copies/original_klass.rb +10 -0
- data/test/framework/ext/extensions/blog.rb +6 -0
- data/test/framework/ext/extensions.rb +4 -0
- data/test/framework/lib/group/klass2.rb +4 -0
- data/test/framework/lib/klass.rb +10 -0
- data/test/framework/other/also.rb +8 -0
- data/test/framework/other/ignore.rb +2 -0
- data/test/framework/other/load.rb +2 -0
- data/test/framework/other/two.rb +14 -0
- data/test/framework/private/klass.rb +10 -0
- data/test/framework/test.components +23 -0
- data/test/helper.rb +10 -0
- data/test/test_autoload.rb +32 -0
- data/test/test_component.rb +133 -0
- data/test/test_component_set.rb +20 -0
- data/test/test_folder.rb +4 -0
- data/test/test_impromptu.rb +43 -0
- data/test/test_integration.rb +312 -0
- data/test/test_ordered_set.rb +93 -0
- data/test/test_resource.rb +186 -0
- data/test/test_symbol.rb +99 -0
- metadata +139 -0
data/.document
ADDED
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
Public Domain, so feel free to do whatever you like. If you prefer a long legal definition, the Creative Commons public domain description is below. In this, I (William Cannings) am the Owner, and Impromptu is the Work.
|
2
|
+
|
3
|
+
== Creative Commons Legal Code ==
|
4
|
+
|
5
|
+
CC0 1.0 Universal
|
6
|
+
|
7
|
+
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED HEREUNDER.
|
8
|
+
Statement of Purpose
|
9
|
+
|
10
|
+
The laws of most jurisdictions throughout the world automatically confer exclusive Copyright and Related Rights (defined below) upon the creator and subsequent owner(s) (each and all, an "owner") of an original work of authorship and/or a database (each, a "Work").
|
11
|
+
|
12
|
+
Certain owners wish to permanently relinquish those rights to a Work for the purpose of contributing to a commons of creative, cultural and scientific works ("Commons") that the public can reliably and without fear of later claims of infringement build upon, modify, incorporate in other works, reuse and redistribute as freely as possible in any form whatsoever and for any purposes, including without limitation commercial purposes. These owners may contribute to the Commons to promote the ideal of a free culture and the further production of creative, cultural and scientific works, or to gain reputation or greater distribution for their Work in part through the use and efforts of others.
|
13
|
+
|
14
|
+
For these and/or other purposes and motivations, and without any expectation of additional consideration or compensation, the person associating CC0 with a Work (the "Affirmer"), to the extent that he or she is an owner of Copyright and Related Rights in the Work, voluntarily elects to apply CC0 to the Work and publicly distribute the Work under its terms, with knowledge of his or her Copyright and Related Rights in the Work and the meaning and intended legal effect of CC0 on those rights.
|
15
|
+
|
16
|
+
1. Copyright and Related Rights. A Work made available under CC0 may be protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following:
|
17
|
+
|
18
|
+
the right to reproduce, adapt, distribute, perform, display, communicate, and translate a Work;
|
19
|
+
moral rights retained by the original author(s) and/or performer(s);
|
20
|
+
publicity and privacy rights pertaining to a person's image or likeness depicted in a Work;
|
21
|
+
rights protecting against unfair competition in regards to a Work, subject to the limitations in paragraph 4(a), below;
|
22
|
+
rights protecting the extraction, dissemination, use and reuse of data in a Work;
|
23
|
+
database rights (such as those arising under Directive 96/9/EC of the European Parliament and of the Council of 11 March 1996 on the legal protection of databases, and under any national implementation thereof, including any amended or successor version of such directive); and
|
24
|
+
other similar, equivalent or corresponding rights throughout the world based on applicable law or treaty, and any national implementations thereof.
|
25
|
+
2. Waiver. To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and unconditionally waives, abandons, and surrenders all of Affirmer's Copyright and Related Rights and associated claims and causes of action, whether now known or unknown (including existing as well as future claims and causes of action), in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each member of the public at large and to the detriment of Affirmer's heirs and successors, fully intending that such Waiver shall not be subject to revocation, rescission, cancellation, termination, or any other legal or equitable action to disrupt the quiet enjoyment of the Work by the public as contemplated by Affirmer's express Statement of Purpose.
|
26
|
+
|
27
|
+
3. Public License Fallback. Should any part of the Waiver for any reason be judged legally invalid or ineffective under applicable law, then the Waiver shall be preserved to the maximum extent permitted taking into account Affirmer's express Statement of Purpose. In addition, to the extent the Waiver is so judged Affirmer hereby grants to each affected person a royalty-free, non transferable, non sublicensable, non exclusive, irrevocable and unconditional license to exercise Affirmer's Copyright and Related Rights in the Work (i) in all territories worldwide, (ii) for the maximum duration provided by applicable law or treaty (including future time extensions), (iii) in any current or future medium and for any number of copies, and (iv) for any purpose whatsoever, including without limitation commercial, advertising or promotional purposes (the "License"). The License shall be deemed effective as of the date CC0 was applied by Affirmer to the Work. Should any part of the License for any reason be judged legally invalid or ineffective under applicable law, such partial invalidity or ineffectiveness shall not invalidate the remainder of the License, and in such case Affirmer hereby affirms that he or she will not (i) exercise any of his or her remaining Copyright and Related Rights in the Work or (ii) assert any associated claims and causes of action with respect to the Work, in either case contrary to Affirmer's express Statement of Purpose.
|
28
|
+
|
29
|
+
4. Limitations and Disclaimers.
|
30
|
+
|
31
|
+
No trademark or patent rights held by Affirmer are waived, abandoned, surrendered, licensed or otherwise affected by this document.
|
32
|
+
Affirmer offers the Work as-is and makes no representations or warranties of any kind concerning the Work, express, implied, statutory or otherwise, including without limitation warranties of title, merchantability, fitness for a particular purpose, non infringement, or the absence of latent or other defects, accuracy, or the present or absence of errors, whether or not discoverable, all to the greatest extent permissible under applicable law.
|
33
|
+
Affirmer disclaims responsibility for clearing rights of other persons that may apply to the Work or any use thereof, including without limitation any person's Copyright and Related Rights in the Work. Further, Affirmer disclaims responsibility for obtaining any necessary consents, permissions or other rights required for any use of the Work.
|
34
|
+
Affirmer understands and acknowledges that Creative Commons is not a party to this document and has no duty or obligation with respect to this CC0 or use of the Work.
|
data/README.rdoc
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
= Impromptu
|
2
|
+
A lazy/auto-loading component manager for Ruby.
|
3
|
+
|
4
|
+
== Motivation
|
5
|
+
Large libraries such as web frameworks define a lot of code that may never be used in a production running app. It would be nice if you could pick and choose which parts of a library are actually loaded and used within an application, and automatically reload files when they are changed. It would be even nicer if you didn't have to manually configure which components to include at all.
|
6
|
+
|
7
|
+
== Overview
|
8
|
+
Impromptu allows you to define components of a system, the gem/external requirements of a component, and the files and folders which implement the resources provided. +const_missing+ is used to load modules and classes the first time they are used, along with any gems/requirements the component as a whole has. By doing this, there's no need to define which components of a library are actually used; the fewest components possible are loaded simply as a result of the code you write.
|
9
|
+
|
10
|
+
A simple component DSL is used to define components:
|
11
|
+
|
12
|
+
component 'framework' do
|
13
|
+
namespace :Framework
|
14
|
+
requires 'rack'
|
15
|
+
folder 'lib', nested_namespaces: false
|
16
|
+
end
|
17
|
+
|
18
|
+
component 'private' do
|
19
|
+
namespace :Framework
|
20
|
+
folder 'private'
|
21
|
+
end
|
22
|
+
|
23
|
+
component 'other' do
|
24
|
+
folder 'other' do
|
25
|
+
file 'load.rb'
|
26
|
+
file 'mods.rb', :provides => [:ModOne, :ModTwo]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
Components may have a namespace, may have requirements, and must have at least one folder containing source files that implement the resources provided by the component. Imagine the 'lib' folder used by the 'framework' component contained the files:
|
32
|
+
|
33
|
+
[lib]
|
34
|
+
\ klass.rb
|
35
|
+
\ [group]
|
36
|
+
\ klass2.rb
|
37
|
+
|
38
|
+
These files would provide the resources Framework::Klass, and Framework::Klass2. If the 'nested_namespaces' flag was true, klass2.rb would instead provide Framework::Group::Klass2. When either of these resources are requested anywhere, Impromptu first loads the requirements of the component (in this case rack) before loading any files implementing the requested resource. Sometimes, a resource is defined by more than one file. Imagine the 'private' folder contained:
|
39
|
+
|
40
|
+
[private]
|
41
|
+
\ klass.rb
|
42
|
+
\ klass2.rb
|
43
|
+
|
44
|
+
Because the 'private' component shares the same namespace as the 'framework' component, these two files would also provide Framework::Klass and Framework::Klass2. Referencing Klass or Klass2 would load the file from 'lib' first before the file from 'private' (since the 'private' component appears after the 'framework' component).
|
45
|
+
|
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
|
+
|
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.
|
49
|
+
|
50
|
+
== Defining Components
|
51
|
+
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.
|
52
|
+
|
53
|
+
Impromptu.define_components do
|
54
|
+
# direct
|
55
|
+
component do
|
56
|
+
...
|
57
|
+
end
|
58
|
+
|
59
|
+
# from a file
|
60
|
+
parse_file 'framework/framework.components'
|
61
|
+
end
|
62
|
+
|
63
|
+
The present working directory, or the directory containing a file being parsed, is used as the current 'base' directory. Folder references are assumed to be relative to this directory, unless they are an absolute path. Once the set of components is defined it cannot be changed. The list of folders implementing a component also cannot be changed, although the files within a folder may be changed and reloaded. These restrictions may be lifted in a later release.
|
64
|
+
|
65
|
+
== Reloading Components
|
66
|
+
Impromptu supports the reloading of resource implementation files, including adding new files to a folder and removing existing files. By default folders are assumed to be reloadable, though setting the reloadable option to false prevents folders being scanned during an update:
|
67
|
+
|
68
|
+
folder 'lib', :reloadable => false
|
69
|
+
|
70
|
+
To force a reload of already loaded resources, and an update of the resource tree, call Impromptu.update. Only resources which have their files changed and have previously been loaded will be reloaded. Any new files will either insert their resources in to the resource tree or add themselves to the list of files implementing an existing resource. Removing a file may simply remove it from the list of files implementing a resource, or it may unload and remove the resource entirely (if it was the only or last file implementing a resource). Any folders which were defined using a block and subsequent calls to 'file' will not be scanned for new or missing files since their file list is explicitly defined. Only files with a reloadable extension (currently 'rb') will be reloaded if they are changed - object files such as bundles and shared objects can only be loaded once.
|
71
|
+
|
72
|
+
== Known Issues
|
73
|
+
Reloading resources is currently not guarded so if you have one thread reloading a resource, and another accessing it, the second thread may get a stale reference.
|
74
|
+
|
75
|
+
== Thanks
|
76
|
+
To Matt (http://twitter.com/mattrobs) for the name
|
data/Rakefile
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "impromptu"
|
8
|
+
gem.summary = "Component and dependency manager for Ruby"
|
9
|
+
gem.description = "Component and dependency manager for Ruby"
|
10
|
+
gem.email = "me@willcannings.com"
|
11
|
+
gem.homepage = "http://github.com/willcannings/impromptu"
|
12
|
+
gem.authors = ["Will Cannings"]
|
13
|
+
gem.add_development_dependency "shoulda"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'rake/testtask'
|
22
|
+
Rake::TestTask.new(:test) do |test|
|
23
|
+
test.libs << 'lib' << 'test'
|
24
|
+
test.pattern = 'test/**/test_*.rb'
|
25
|
+
test.verbose = true
|
26
|
+
end
|
27
|
+
|
28
|
+
begin
|
29
|
+
require 'rcov/rcovtask'
|
30
|
+
Rcov::RcovTask.new do |test|
|
31
|
+
test.libs << 'test'
|
32
|
+
test.pattern = 'test/**/test_*.rb'
|
33
|
+
test.verbose = true
|
34
|
+
end
|
35
|
+
rescue LoadError
|
36
|
+
task :rcov do
|
37
|
+
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
task :test => :check_dependencies
|
42
|
+
|
43
|
+
task :default => :test
|
44
|
+
|
45
|
+
require 'rake/rdoctask'
|
46
|
+
Rake::RDocTask.new do |rdoc|
|
47
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
48
|
+
|
49
|
+
rdoc.rdoc_dir = 'rdoc'
|
50
|
+
rdoc.title = "Impromptu #{version}"
|
51
|
+
rdoc.rdoc_files.include('README*')
|
52
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
53
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
data/impromptu.gemspec
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{impromptu}
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Will Cannings"]
|
12
|
+
s.date = %q{2010-09-30}
|
13
|
+
s.description = %q{Component and dependency manager for Ruby}
|
14
|
+
s.email = %q{me@willcannings.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README.rdoc",
|
24
|
+
"Rakefile",
|
25
|
+
"VERSION",
|
26
|
+
"impromptu.gemspec",
|
27
|
+
"lib/impromptu.rb",
|
28
|
+
"lib/impromptu/autoload.rb",
|
29
|
+
"lib/impromptu/component.rb",
|
30
|
+
"lib/impromptu/component_set.rb",
|
31
|
+
"lib/impromptu/file.rb",
|
32
|
+
"lib/impromptu/folder.rb",
|
33
|
+
"lib/impromptu/impromptu.rb",
|
34
|
+
"lib/impromptu/ordered_set.rb",
|
35
|
+
"lib/impromptu/resource.rb",
|
36
|
+
"lib/impromptu/symbol.rb",
|
37
|
+
"test/framework/copies/extra_klass2.rb",
|
38
|
+
"test/framework/copies/new_klass.rb",
|
39
|
+
"test/framework/copies/new_unseen.rb",
|
40
|
+
"test/framework/copies/original_klass.rb",
|
41
|
+
"test/framework/ext/extensions.rb",
|
42
|
+
"test/framework/ext/extensions/blog.rb",
|
43
|
+
"test/framework/lib/group/klass2.rb",
|
44
|
+
"test/framework/lib/klass.rb",
|
45
|
+
"test/framework/other/also.rb",
|
46
|
+
"test/framework/other/ignore.rb",
|
47
|
+
"test/framework/other/load.rb",
|
48
|
+
"test/framework/other/two.rb",
|
49
|
+
"test/framework/private/klass.rb",
|
50
|
+
"test/framework/test.components",
|
51
|
+
"test/helper.rb",
|
52
|
+
"test/test_autoload.rb",
|
53
|
+
"test/test_component.rb",
|
54
|
+
"test/test_component_set.rb",
|
55
|
+
"test/test_folder.rb",
|
56
|
+
"test/test_impromptu.rb",
|
57
|
+
"test/test_integration.rb",
|
58
|
+
"test/test_ordered_set.rb",
|
59
|
+
"test/test_resource.rb",
|
60
|
+
"test/test_symbol.rb"
|
61
|
+
]
|
62
|
+
s.homepage = %q{http://github.com/willcannings/impromptu}
|
63
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
64
|
+
s.require_paths = ["lib"]
|
65
|
+
s.rubygems_version = %q{1.3.7}
|
66
|
+
s.summary = %q{Component and dependency manager for Ruby}
|
67
|
+
s.test_files = [
|
68
|
+
"test/framework/copies/extra_klass2.rb",
|
69
|
+
"test/framework/copies/new_klass.rb",
|
70
|
+
"test/framework/copies/new_unseen.rb",
|
71
|
+
"test/framework/copies/original_klass.rb",
|
72
|
+
"test/framework/ext/extensions/blog.rb",
|
73
|
+
"test/framework/ext/extensions.rb",
|
74
|
+
"test/framework/lib/group/klass2.rb",
|
75
|
+
"test/framework/lib/klass.rb",
|
76
|
+
"test/framework/other/also.rb",
|
77
|
+
"test/framework/other/ignore.rb",
|
78
|
+
"test/framework/other/load.rb",
|
79
|
+
"test/framework/other/two.rb",
|
80
|
+
"test/framework/private/klass.rb",
|
81
|
+
"test/helper.rb",
|
82
|
+
"test/test_autoload.rb",
|
83
|
+
"test/test_component.rb",
|
84
|
+
"test/test_component_set.rb",
|
85
|
+
"test/test_folder.rb",
|
86
|
+
"test/test_impromptu.rb",
|
87
|
+
"test/test_integration.rb",
|
88
|
+
"test/test_ordered_set.rb",
|
89
|
+
"test/test_resource.rb",
|
90
|
+
"test/test_symbol.rb"
|
91
|
+
]
|
92
|
+
|
93
|
+
if s.respond_to? :specification_version then
|
94
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
95
|
+
s.specification_version = 3
|
96
|
+
|
97
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
98
|
+
s.add_development_dependency(%q<shoulda>, [">= 0"])
|
99
|
+
else
|
100
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
101
|
+
end
|
102
|
+
else
|
103
|
+
s.add_dependency(%q<shoulda>, [">= 0"])
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Impromptu
|
2
|
+
module Autoload
|
3
|
+
# Impromptu implements the autoloading behaviour using this module
|
4
|
+
# and this method. Object is extended with this module so we can
|
5
|
+
# catch references to objects which don't exist. It first tries
|
6
|
+
# to determine if we know about the resource corresponding to name
|
7
|
+
# and if so loads and returns a reference to it. Otherwise, the usual
|
8
|
+
# NameError exception will be raised by Object. We also test to make
|
9
|
+
# sure a resource isn't already loaded before returning a reference
|
10
|
+
# to it. If it is, then something very screwy has gone on and Ruby
|
11
|
+
# cannot locate an already loaded resource.
|
12
|
+
def const_missing(symbol)
|
13
|
+
# walk the resource tree and get a reference to the
|
14
|
+
# resource or nil if we're not tracking it
|
15
|
+
resource = Impromptu.root_resource.child(symbol)
|
16
|
+
|
17
|
+
# if we don't know about the symbol, send the method to
|
18
|
+
# Object which will raise an exception
|
19
|
+
super(symbol) if resource.nil?
|
20
|
+
|
21
|
+
# ensure the resource hasn't already been loaded
|
22
|
+
raise "Illegal condition: const_missing called after a resource has been loaded" if resource.loaded?
|
23
|
+
|
24
|
+
# load the resource and return a reference to it. this
|
25
|
+
# assumes that the source files will correctly define
|
26
|
+
# the resource. otherwise nil will be returned.
|
27
|
+
resource.reload
|
28
|
+
resource.reference
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
Object.extend(Impromptu::Autoload)
|
@@ -0,0 +1,124 @@
|
|
1
|
+
module Impromptu
|
2
|
+
class Component
|
3
|
+
attr_accessor :base_path, :name, :requirements, :folders, :frozen
|
4
|
+
attr_writer :namespace
|
5
|
+
|
6
|
+
# Create a new component. base_path is the 'current working
|
7
|
+
# directory' of the component, and all folder paths will be
|
8
|
+
# joined with this path to create an absolute path. Name is
|
9
|
+
# the name of the component and is currently only used as a
|
10
|
+
# reference. Names must be unique amongst all components, so
|
11
|
+
# to avoid clashes a namespacing scheme should be used.
|
12
|
+
def initialize(base_path, name)
|
13
|
+
@base_path = base_path || Pathname.new('.').realpath
|
14
|
+
@name = name
|
15
|
+
@requirements = OrderedSet.new
|
16
|
+
@folders = OrderedSet.new
|
17
|
+
@namespace = nil
|
18
|
+
@frozen = false
|
19
|
+
@dependencies_loaded = false
|
20
|
+
end
|
21
|
+
|
22
|
+
# Override eql? so two components with the same name will be
|
23
|
+
# considered equal by ordered set.
|
24
|
+
def eql?(other)
|
25
|
+
other.name == @name
|
26
|
+
end
|
27
|
+
|
28
|
+
# Override hash so two components with the same name will result
|
29
|
+
# in the same hash value.
|
30
|
+
def hash
|
31
|
+
@name.hash
|
32
|
+
end
|
33
|
+
|
34
|
+
# Add external dependencies (such as gems) to this component. e.g:
|
35
|
+
# requires 'gem_name', 'other_file'. May be called multiple times.
|
36
|
+
def requires(*resources)
|
37
|
+
protect_from_modification
|
38
|
+
@requirements.merge(resources)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Declare a folder implementing this component. All source files
|
42
|
+
# within this folder are assumed to define the resources of this
|
43
|
+
# component. Sub-folders by default provide nested namespaces,
|
44
|
+
# and when used in combination with the namespace method can
|
45
|
+
# produce multi-level namespaces. For example, a root folder 'src'
|
46
|
+
# which contains a sub-folder 'plugins', and a file 'testing.rb'
|
47
|
+
# would provide the resource Plugins::Testing. If namespace was
|
48
|
+
# used to define a root namespace for the component, that namespace
|
49
|
+
# would precede the Plugins namespace. To turn off this behaviour
|
50
|
+
# set the nested_namespaces option to false. e.g:
|
51
|
+
# folder 'src', nested_namespaces: false
|
52
|
+
def folder(path, options={}, &block)
|
53
|
+
protect_from_modification
|
54
|
+
folder = @folders << Folder.new(@base_path.join(*path), self, options, block)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Define a namespace used for all resources provided by this
|
58
|
+
# component. This becomes the root namespace for all top level
|
59
|
+
# folders. e.g if you declare a namespace ':Root', and a single
|
60
|
+
# folder 'src' which contains a file 'klass.rb', klass.rb will
|
61
|
+
# declare the resource Root::Klass. By default, nested folders
|
62
|
+
# will extend the namespace with the name of the folder. For
|
63
|
+
# instance, if the src folder contained another called 'plugins'
|
64
|
+
# which contained a file 'testing.rb', the resource
|
65
|
+
# Root::Plugins::Testing would be defined. Folder declarations
|
66
|
+
# can override this behaviour.
|
67
|
+
def namespace(name=nil)
|
68
|
+
unless name.nil?
|
69
|
+
protect_from_modification
|
70
|
+
@namespace = name.to_sym
|
71
|
+
end
|
72
|
+
@namespace
|
73
|
+
end
|
74
|
+
|
75
|
+
# Load the external dependencies required by this component. If
|
76
|
+
# the require fails, ruby gems is loaded and the require attempted
|
77
|
+
# again. Any failures after this point will cause a LoadError
|
78
|
+
# exception to bubble through your application.
|
79
|
+
def load_external_dependencies
|
80
|
+
return false if @dependencies_loaded
|
81
|
+
@requirements.each do |requirement|
|
82
|
+
begin
|
83
|
+
require requirement
|
84
|
+
rescue LoadError => unavailable
|
85
|
+
begin
|
86
|
+
require 'rubygems'
|
87
|
+
rescue LoadError
|
88
|
+
raise unavailable
|
89
|
+
end
|
90
|
+
require requirement
|
91
|
+
end
|
92
|
+
end
|
93
|
+
@dependencies_loaded = true
|
94
|
+
end
|
95
|
+
|
96
|
+
# Mark a component as 'frozen'. Modification of the component
|
97
|
+
# requirements or list of folders is not allowed after this.
|
98
|
+
def freeze
|
99
|
+
# freeze files
|
100
|
+
@folders.each do |folder|
|
101
|
+
folder.files.each do |file|
|
102
|
+
file.freeze
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# create a blank namespace module if required
|
107
|
+
unless namespace.nil?
|
108
|
+
Impromptu.root_resource.get_or_create_child(namespace).namespace!
|
109
|
+
end
|
110
|
+
|
111
|
+
@frozen = true
|
112
|
+
end
|
113
|
+
|
114
|
+
# True if the component definition has been frozen.
|
115
|
+
def frozen?
|
116
|
+
@frozen
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
def protect_from_modification
|
121
|
+
raise "Modification of component after component has been loaded" if @frozen
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Impromptu
|
2
|
+
class ComponentSet < OrderedSet
|
3
|
+
# Retrieve a component by name. This is used very
|
4
|
+
# rarely (only in component definitions and the
|
5
|
+
# test suite) so a linear search is acceptable.
|
6
|
+
def [](name)
|
7
|
+
@items_list.select {|component| component.name == name}.first
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|