impromptu 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.document +5 -0
  2. data/.gitignore +21 -0
  3. data/LICENSE +34 -0
  4. data/README.rdoc +76 -0
  5. data/Rakefile +53 -0
  6. data/VERSION +1 -0
  7. data/impromptu.gemspec +106 -0
  8. data/lib/impromptu/autoload.rb +33 -0
  9. data/lib/impromptu/component.rb +124 -0
  10. data/lib/impromptu/component_set.rb +10 -0
  11. data/lib/impromptu/file.rb +220 -0
  12. data/lib/impromptu/folder.rb +151 -0
  13. data/lib/impromptu/impromptu.rb +81 -0
  14. data/lib/impromptu/ordered_set.rb +49 -0
  15. data/lib/impromptu/resource.rb +195 -0
  16. data/lib/impromptu/symbol.rb +43 -0
  17. data/lib/impromptu.rb +12 -0
  18. data/test/framework/copies/extra_klass2.rb +7 -0
  19. data/test/framework/copies/new_klass.rb +10 -0
  20. data/test/framework/copies/new_unseen.rb +7 -0
  21. data/test/framework/copies/original_klass.rb +10 -0
  22. data/test/framework/ext/extensions/blog.rb +6 -0
  23. data/test/framework/ext/extensions.rb +4 -0
  24. data/test/framework/lib/group/klass2.rb +4 -0
  25. data/test/framework/lib/klass.rb +10 -0
  26. data/test/framework/other/also.rb +8 -0
  27. data/test/framework/other/ignore.rb +2 -0
  28. data/test/framework/other/load.rb +2 -0
  29. data/test/framework/other/two.rb +14 -0
  30. data/test/framework/private/klass.rb +10 -0
  31. data/test/framework/test.components +23 -0
  32. data/test/helper.rb +10 -0
  33. data/test/test_autoload.rb +32 -0
  34. data/test/test_component.rb +133 -0
  35. data/test/test_component_set.rb +20 -0
  36. data/test/test_folder.rb +4 -0
  37. data/test/test_impromptu.rb +43 -0
  38. data/test/test_integration.rb +312 -0
  39. data/test/test_ordered_set.rb +93 -0
  40. data/test/test_resource.rb +186 -0
  41. data/test/test_symbol.rb +99 -0
  42. metadata +139 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
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