easyload 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +5 -0
- data/Gemfile +5 -0
- data/README.md +64 -0
- data/Rakefile +6 -0
- data/easyload.gemspec +23 -0
- data/lib/easyload.rb +20 -0
- data/lib/easyload/singleton_extensions.rb +72 -0
- data/lib/easyload/version.rb +4 -0
- data/spec/basic_spec.rb +56 -0
- data/spec/deep/example.rb +4 -0
- data/spec/deep/example/thing.rb +4 -0
- data/spec/examples/load_counter.rb +7 -0
- data/spec/examples/nested.rb +4 -0
- data/spec/examples/nested/leaf.rb +4 -0
- data/spec/examples/node.rb +4 -0
- data/spec/naming_spec.rb +23 -0
- metadata +101 -0
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
Easyload
|
2
|
+
========
|
3
|
+
An alternative to autoload that relies on your project's directory structure to determine its
|
4
|
+
module hierarchy, recursively.
|
5
|
+
|
6
|
+
This is an opinionated loading method that attempts to simplify your development process by
|
7
|
+
enforcing that your directory and file names follow convention and are consistent with your
|
8
|
+
project's module hierarchy. Additionally, it allows for painless refactoring and reduces code
|
9
|
+
repetition by removing the need of declaring your module hierarchy in every single source
|
10
|
+
file. The project's directory structure determines that; it's just extra work to keep the two
|
11
|
+
in sync.
|
12
|
+
|
13
|
+
Easyload frowns upon the require statement, and would much rather that your library know how
|
14
|
+
to load itself. Simply reference the constant that you want, and it's there.
|
15
|
+
|
16
|
+
|
17
|
+
General Use
|
18
|
+
-----------
|
19
|
+
To easyload a project, it must define at least one top level module to be used as the easyload
|
20
|
+
root. From that root, and the search path that it defines, all child constants are easyloaded
|
21
|
+
according to your directory structure.
|
22
|
+
|
23
|
+
For example, with a directory structure of:
|
24
|
+
lib/
|
25
|
+
my_easyloaded_module.rb
|
26
|
+
my_easyloaded_module/
|
27
|
+
child_module.rb
|
28
|
+
child_module/
|
29
|
+
leafy.rb
|
30
|
+
node.rb
|
31
|
+
|
32
|
+
`lib/my_easyloaded_module.rb`:
|
33
|
+
module MyEasyloadedModule
|
34
|
+
import Easyload
|
35
|
+
end
|
36
|
+
|
37
|
+
`lib/my_easyloaded_module/child_module.rb`:
|
38
|
+
module ChildModule
|
39
|
+
...
|
40
|
+
end
|
41
|
+
|
42
|
+
`lib/my_easyloaded_module/child_module/leafy.rb`:
|
43
|
+
class Leafy
|
44
|
+
...
|
45
|
+
end
|
46
|
+
|
47
|
+
`lib/my_easyloaded_module/node.rb`:
|
48
|
+
class Node
|
49
|
+
...
|
50
|
+
end
|
51
|
+
|
52
|
+
Would result in the following module/class hierarchy being easyloadable:
|
53
|
+
MyEasyloadedModule
|
54
|
+
MyEasyloadedModule::ChildModule
|
55
|
+
MyEasyloadedModule::ChildModule::Leafy
|
56
|
+
MyEasyloadedModule::Node
|
57
|
+
|
58
|
+
|
59
|
+
Easyload Configuration
|
60
|
+
----------------------
|
61
|
+
When a class or module includes `Easyload`, the
|
62
|
+
`Easyload::SingletonExtensions` module is mixed into that class or module's singleton methods,
|
63
|
+
providing the easyload API. See [`Easyload::SingletonExtensions`](http://rdoc.info/github/nevir/easyload/master/Easyload/SingletonExtensions)
|
64
|
+
for a reference of what configuration can be performed on an easyloaded class or module.
|
data/Rakefile
ADDED
data/easyload.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path('../lib', __FILE__)
|
3
|
+
require 'easyload/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'easyload'
|
7
|
+
s.version = Easyload::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ['Ian MacLeod']
|
10
|
+
s.email = ['ian@nevir.net']
|
11
|
+
s.homepage = ''
|
12
|
+
s.summary = 'A recursive and opinionated alternative to autoload.'
|
13
|
+
s.description = 'An alternative to autoload that relies on your project\'s directory structure to determine its module hierarchy, recursively.'
|
14
|
+
|
15
|
+
s.rubyforge_project = 'easyload'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ['lib']
|
21
|
+
|
22
|
+
s.add_development_dependency('rspec', ['~> 2.5.0'])
|
23
|
+
end
|
data/lib/easyload.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'easyload/singleton_extensions'
|
3
|
+
|
4
|
+
module Easyload
|
5
|
+
autoload :VERSION, 'version'
|
6
|
+
|
7
|
+
# Handle module/class inclusions in a clean manner, and try to guess our easyload root.
|
8
|
+
#
|
9
|
+
# @private
|
10
|
+
def self.included(in_mod)
|
11
|
+
class << in_mod
|
12
|
+
include SingletonExtensions
|
13
|
+
end
|
14
|
+
|
15
|
+
if in_mod.name
|
16
|
+
components = in_mod.name.split('::').map {|n| in_mod.easyload_path_component_for_sym(n)}
|
17
|
+
in_mod.easyload_from(components.join('/'))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Easyload
|
3
|
+
# The singleton methods that are defined for a module that includes {Easyload}.
|
4
|
+
module SingletonExtensions
|
5
|
+
# The root path that easyload should use when loading a child constant for this module.
|
6
|
+
#
|
7
|
+
# Defaults to the path component form of the class/module's name
|
8
|
+
attr_reader :easyload_root
|
9
|
+
|
10
|
+
# Sets easyload_root.
|
11
|
+
def easyload_from(root_path)
|
12
|
+
@easyload_root = root_path.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
# Converts a CamelCased symbol into a path component.
|
16
|
+
#
|
17
|
+
# * A +Single+ symbol becomes +single+.
|
18
|
+
# * +MultiWordSymbols+ become +multi_word_symbols+.
|
19
|
+
# * +ACRONYMS+ are treated like words: +acronyms+.
|
20
|
+
# * +ABCFoo+ is considered to be a mix of acronyms and words: +abc_foo+.
|
21
|
+
def easyload_path_component_for_sym(sym)
|
22
|
+
path = sym.to_s.dup
|
23
|
+
path.gsub!(/([A-Z]+)([A-Z][a-z]+)/, '\1_\2_')
|
24
|
+
path.gsub!(/([A-Z][a-z]+)/, '\1_')
|
25
|
+
path.gsub!(/_+/, '_')
|
26
|
+
path.chomp!('_')
|
27
|
+
path.downcase!
|
28
|
+
end
|
29
|
+
|
30
|
+
# The meat of easyloading happens here.
|
31
|
+
#
|
32
|
+
# @private
|
33
|
+
def const_missing(sym)
|
34
|
+
if not self.instance_variable_defined? :@easyload_root
|
35
|
+
$stderr.puts "You must call easyload_from() before you can easyload #{self}::#{sym}"
|
36
|
+
return super(sym)
|
37
|
+
end
|
38
|
+
|
39
|
+
path_component = self.easyload_path_component_for_sym(sym)
|
40
|
+
easyload_path = File.join(@easyload_root, "#{path_component}.rb")
|
41
|
+
|
42
|
+
# Search for the file to include
|
43
|
+
$LOAD_PATH.each do |load_root|
|
44
|
+
full_load_path = File.join(load_root, easyload_path)
|
45
|
+
if File.exists? full_load_path
|
46
|
+
self.module_eval(File.read(full_load_path))
|
47
|
+
|
48
|
+
# Did we get our target constant?
|
49
|
+
if self.const_defined? sym
|
50
|
+
target_const = self.const_get(sym)
|
51
|
+
class << target_const
|
52
|
+
include SingletonExtensions
|
53
|
+
end
|
54
|
+
|
55
|
+
target_const.easyload_from(File.join(self.easyload_root, path_component))
|
56
|
+
|
57
|
+
return self.const_get(sym)
|
58
|
+
|
59
|
+
# Warn but still break the load process. We don't want to support ambiguous load
|
60
|
+
# paths.
|
61
|
+
else
|
62
|
+
$stderr.puts "Attempted to easyload #{sym} from '#{full_load_path}', but it doesn't appear to exist in that source file."
|
63
|
+
end
|
64
|
+
|
65
|
+
break
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
return super(sym)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/spec/basic_spec.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'easyload'
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
module Examples
|
7
|
+
include Easyload
|
8
|
+
end
|
9
|
+
|
10
|
+
module Deep
|
11
|
+
module Example
|
12
|
+
include Easyload
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module SpecRoot
|
17
|
+
class << self
|
18
|
+
attr_accessor :load_count
|
19
|
+
end
|
20
|
+
self.load_count = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
describe 'A basic easyload module' do
|
24
|
+
it 'should default to the class/module name for the easyload root' do
|
25
|
+
Examples.easyload_root.should == 'examples'
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should be able to load constants one level deep' do
|
29
|
+
Examples::Node::ABOUT.should == 'A node?'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should raise standard NameErrors for unknown constants' do
|
33
|
+
expect {
|
34
|
+
Examples::Missing
|
35
|
+
}.to raise_error(NameError)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'should not load files twice' do
|
39
|
+
SpecRoot.load_count.should == 0
|
40
|
+
|
41
|
+
Examples::LoadCounter::ABOUT.should == 'load counter: 1'
|
42
|
+
SpecRoot.load_count.should == 1
|
43
|
+
|
44
|
+
Examples::LoadCounter::ABOUT.should == 'load counter: 1'
|
45
|
+
SpecRoot.load_count.should == 1
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should be able to load nested modules' do
|
49
|
+
Examples::Nested::Leaf::ABOUT.should == 'A leaf?'
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'should be able to automatically guess a nested module name' do
|
53
|
+
Deep::Example.easyload_root.should == 'deep/example'
|
54
|
+
thing = Deep::Example::Thing.new
|
55
|
+
end
|
56
|
+
end
|
data/spec/naming_spec.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require 'easyload'
|
3
|
+
|
4
|
+
module NamingExamples
|
5
|
+
Easyload
|
6
|
+
end
|
7
|
+
|
8
|
+
describe 'Easyload file names' do
|
9
|
+
it 'should use a single lowercase name for a single word constant' do
|
10
|
+
Examples.easyload_path_component_for_sym(:Single).should == 'single'
|
11
|
+
Examples.easyload_path_component_for_sym(:A).should == 'a'
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'should break CamelCase constants into lowercase words separated by underscores' do
|
15
|
+
Examples.easyload_path_component_for_sym(:TwoWords).should == 'two_words'
|
16
|
+
Examples.easyload_path_component_for_sym(:OneTwoThreeFour).should == 'one_two_three_four'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'should treat strings of adjacent capital letters as acronyms' do
|
20
|
+
Examples.easyload_path_component_for_sym(:ABC).should == 'abc'
|
21
|
+
Examples.easyload_path_component_for_sym(:WTFWord).should == 'wtf_word'
|
22
|
+
end
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: easyload
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Ian MacLeod
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-02-13 00:00:00 -08:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ~>
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 5
|
31
|
+
- 0
|
32
|
+
version: 2.5.0
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
description: An alternative to autoload that relies on your project's directory structure to determine its module hierarchy, recursively.
|
36
|
+
email:
|
37
|
+
- ian@nevir.net
|
38
|
+
executables: []
|
39
|
+
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files: []
|
43
|
+
|
44
|
+
files:
|
45
|
+
- .gitignore
|
46
|
+
- Gemfile
|
47
|
+
- README.md
|
48
|
+
- Rakefile
|
49
|
+
- easyload.gemspec
|
50
|
+
- lib/easyload.rb
|
51
|
+
- lib/easyload/singleton_extensions.rb
|
52
|
+
- lib/easyload/version.rb
|
53
|
+
- spec/basic_spec.rb
|
54
|
+
- spec/deep/example.rb
|
55
|
+
- spec/deep/example/thing.rb
|
56
|
+
- spec/examples/load_counter.rb
|
57
|
+
- spec/examples/nested.rb
|
58
|
+
- spec/examples/nested/leaf.rb
|
59
|
+
- spec/examples/node.rb
|
60
|
+
- spec/naming_spec.rb
|
61
|
+
has_rdoc: true
|
62
|
+
homepage: ""
|
63
|
+
licenses: []
|
64
|
+
|
65
|
+
post_install_message:
|
66
|
+
rdoc_options: []
|
67
|
+
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
none: false
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
segments:
|
84
|
+
- 0
|
85
|
+
version: "0"
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project: easyload
|
89
|
+
rubygems_version: 1.3.7
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: A recursive and opinionated alternative to autoload.
|
93
|
+
test_files:
|
94
|
+
- spec/basic_spec.rb
|
95
|
+
- spec/deep/example.rb
|
96
|
+
- spec/deep/example/thing.rb
|
97
|
+
- spec/examples/load_counter.rb
|
98
|
+
- spec/examples/nested.rb
|
99
|
+
- spec/examples/nested/leaf.rb
|
100
|
+
- spec/examples/node.rb
|
101
|
+
- spec/naming_spec.rb
|