easyload 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .yardoc
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+ source "http://rubygems.org"
3
+
4
+ # Specify your gem's dependencies in Easyload.gemspec
5
+ gemspec
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
@@ -0,0 +1,6 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'bundler'
3
+ require 'rspec/core/rake_task'
4
+
5
+ Bundler::GemHelper.install_tasks
6
+ RSpec::Core::RakeTask.new
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
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Easyload
3
+ VERSION = '0.1.0'
4
+ end
@@ -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
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ module Example
3
+
4
+ end
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ class Thing
3
+
4
+ end
@@ -0,0 +1,7 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ ::SpecRoot.load_count += 1
4
+
5
+ module LoadCounter
6
+ ABOUT = "load counter: #{::SpecRoot.load_count}"
7
+ end
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ class Nested
3
+
4
+ end
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ class Leaf
3
+ ABOUT = 'A leaf?'
4
+ end
@@ -0,0 +1,4 @@
1
+ # -*- encoding: utf-8 -*-
2
+ class Node
3
+ ABOUT = 'A node?'
4
+ end
@@ -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