awesome_loader 0.1.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -5
- data/lib/awesome_loader.rb +1 -0
- data/lib/awesome_loader/autoloader.rb +28 -43
- data/lib/awesome_loader/module_builder.rb +72 -0
- data/lib/awesome_loader/utils.rb +13 -1
- data/lib/awesome_loader/version.rb +1 -1
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 362e0c1826dd0bcde66b8e466799b0b37743f2f2
|
4
|
+
data.tar.gz: 0d981dffe720657efa76c056b37b4d4f09ad8ae3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e7d754fce596bd375ed6dec3f11341b10e76e8f7320b5ba6ae3aed577cff50f6ef4614b17358054cd784c48974623af38672c0c288b80c07970d2274b59fe6cb
|
7
|
+
data.tar.gz: 905b71a2043b2334334f1472eab79d2d4bf0709892c7d691568410e815318ac76f805bbce5ad566ae2198b2176a3624a6d6c0b7d846ea276a7dfac77b15b78f1
|
data/README.md
CHANGED
@@ -10,9 +10,17 @@ Just add to your Gemfile.
|
|
10
10
|
|
11
11
|
## Basic Usage
|
12
12
|
|
13
|
-
|
13
|
+
`awesome_loader` assumes that your directories and files are all in "snake case" (my_dir/my_file.rb), and that your Ruby Modules and Classes are all in "camel case" (MyDir::MyFile). Additionally, it assumes that your directory structure matches your Module structure. In fact it will traverse your directory tree and make sure all those Modules are created for you.
|
14
|
+
|
15
|
+
Let's say you have a fairly simple layout, somewhat inspired by Rails.
|
16
|
+
|
17
|
+
* `app/models/user.rb` contains `User`
|
18
|
+
* `app/models/widget.rb` contains `Widget`
|
19
|
+
* `app/models/billing/line_item.rb` contains `Billing::LineItem`
|
20
|
+
* `app/helpers/app_helpers.rb` contains `AppHelpers`
|
21
|
+
|
22
|
+
Given those files and their contents, this is all you have to tell `awesome_loader`. Note the `root_depth: 2` argument. That's saying, "Only start creating modules for dirs after the first 2 levels." That means `app` and `app/*` won't get any modules, but deeper directories, like `app/models/billing`, will.
|
14
23
|
|
15
|
-
# The root_depth argument is saying "Start creating modules for dirs after the first 2 levels"
|
16
24
|
AwesomeLoader.autoload root_depth: 2 do
|
17
25
|
paths %w(app ** *.rb)
|
18
26
|
end
|
@@ -33,11 +41,10 @@ Maybe your app structure is more complicated. That's fine too.
|
|
33
41
|
|
34
42
|
## Eager Loading
|
35
43
|
|
36
|
-
If you're running a threaded server like Puma or Thin, it's
|
37
|
-
up-front, instead of possibly loading something during a request Thread. At least in production.
|
44
|
+
If you're running a threaded server like Puma or Thin, it's usually considered best practice to load everything up-front (at least in production), instead of lettings things load while other threads might be running. The `eager_load` option will ensure that all files are loaded before the block exits.
|
38
45
|
|
39
46
|
AwesomeLoader.autoload root_depth: 2, eager_load: true do
|
40
|
-
|
47
|
+
paths %w(app ** *.rb)
|
41
48
|
end
|
42
49
|
|
43
50
|
## License
|
data/lib/awesome_loader.rb
CHANGED
@@ -2,7 +2,7 @@ require 'set'
|
|
2
2
|
require 'pathname'
|
3
3
|
|
4
4
|
#
|
5
|
-
#
|
5
|
+
# The module that holds the awesomeness.
|
6
6
|
#
|
7
7
|
module AwesomeLoader
|
8
8
|
#
|
@@ -15,15 +15,16 @@ module AwesomeLoader
|
|
15
15
|
# end
|
16
16
|
#
|
17
17
|
# @param root_depth [String] Tells AwesomeLoader to start creating Modules for dirs *after* this level
|
18
|
-
# @param
|
18
|
+
# @param root_path [String] Path to root of the application (default Dir.pwd)
|
19
|
+
# @param root_module [Module] Module to load your modules into (default Object). You'll probably always want to keep the default.
|
19
20
|
# @param eager_load [Boolean] Make sure all files get loaded by the time the block finishes (default false)
|
20
21
|
# @return [AwesomeLoader::Autoloader]
|
21
22
|
#
|
22
|
-
def self.autoload(root_depth:,
|
23
|
-
autoloader = Autoloader.new(
|
23
|
+
def self.autoload(root_depth:, root_path: Dir.pwd, root_module: Object, eager_load: false, &block)
|
24
|
+
autoloader = Autoloader.new(root_depth: root_depth, root_path: root_path, root_module: root_module, eager_load: eager_load)
|
24
25
|
if block
|
25
26
|
autoloader.instance_eval(&block)
|
26
|
-
autoloader.
|
27
|
+
autoloader.finalize!
|
27
28
|
end
|
28
29
|
autoloader
|
29
30
|
end
|
@@ -35,18 +36,20 @@ module AwesomeLoader
|
|
35
36
|
# paths(%w(app models ** *.rb)).
|
36
37
|
# paths(%w(app helpers *.rb)).
|
37
38
|
# paths(%w(app routes ** *.rb), root_depth: 1).
|
38
|
-
#
|
39
|
+
# finalize!
|
39
40
|
#
|
40
41
|
class Autoloader
|
41
42
|
RB_EXT = /\.rb$/
|
42
43
|
|
43
|
-
# @return [Pathname] the application root
|
44
|
-
attr_reader :root
|
45
44
|
# @return [Integer] root depth used for all paths unless otherwise specified
|
46
45
|
attr_reader :default_root_depth
|
46
|
+
# @return [Pathname] the application root
|
47
|
+
attr_reader :root_path
|
48
|
+
# @return [Module] the root ruby Module
|
49
|
+
attr_reader :root_module
|
47
50
|
# @return [Boolean] whether or not to automatically load all files once they're defined
|
48
51
|
attr_reader :eager_load
|
49
|
-
# @return [
|
52
|
+
# @return [Set<String>] all defined files, ready to be eager_loaded
|
50
53
|
attr_reader :all_files
|
51
54
|
private :all_files
|
52
55
|
|
@@ -54,13 +57,14 @@ module AwesomeLoader
|
|
54
57
|
# Initialize a new AwesomeLoader::Autoloader.
|
55
58
|
#
|
56
59
|
# @param root_depth [String] Tells AwesomeLoader to start creating Modules for dirs *after* this level
|
57
|
-
# @param
|
60
|
+
# @param root_path [String] Path to root of the application (default Dir.pwd)
|
61
|
+
# @param root_module [Module] Module to load your modules into (default Object). You'll probably always want to keep the default.
|
58
62
|
# @param eager_load [Boolean] Make sure all files get loaded by the time the block finishes (default false)
|
59
63
|
#
|
60
|
-
def initialize(root_depth:,
|
61
|
-
@
|
64
|
+
def initialize(root_depth:, root_path: Dir.pwd, root_module: Object, eager_load: false)
|
65
|
+
@root_path, @root_module = Pathname.new(root_path.to_s), root_module
|
62
66
|
@default_root_depth, @eager_load = root_depth, eager_load
|
63
|
-
@all_files =
|
67
|
+
@all_files = Set.new
|
64
68
|
end
|
65
69
|
|
66
70
|
#
|
@@ -68,47 +72,28 @@ module AwesomeLoader
|
|
68
72
|
#
|
69
73
|
# autoloader.paths %w(app models ** *.rb)
|
70
74
|
#
|
71
|
-
# @param
|
75
|
+
# @param glob [Array<String>] A glob pattern as an array.
|
72
76
|
# @paths root_depth [Integer] Depth at which to start creating modules for dirs. Defaults to whatever the AwesomeLoader::Autoloader instance was initialized with.
|
73
77
|
# @return [AwesomeLoader::Autoloader] returns self, so you can chain calls
|
74
78
|
#
|
75
|
-
def paths(
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
# Get an array of nested dirs (parent dir always comes before child dir)
|
80
|
-
nested_dirs = files.
|
81
|
-
map { |path| File.dirname(path).sub(root_regex, '') }.uniq.
|
82
|
-
reduce(Set.new) { |dirs, leaf_dir|
|
83
|
-
dirs + leaf_dir.split('/').reduce([]) { |a, dir|
|
84
|
-
a << File.join(*a, dir)
|
85
|
-
}
|
86
|
-
}
|
87
|
-
|
88
|
-
# Create modules for each dir. Set a Hash of dir path => Module.
|
89
|
-
modules = nested_dirs.reduce({'.' => Object}) { |a, dir|
|
90
|
-
parent_module = a.fetch File.dirname dir
|
91
|
-
new_module = parent_module.const_set Utils.camelize(File.basename dir), Module.new
|
92
|
-
a[dir] = new_module
|
93
|
-
a
|
94
|
-
}
|
95
|
-
|
96
|
-
# For each file, look up it's dir module and set autoload on the class/module in the file.
|
97
|
-
files.each do |path|
|
98
|
-
full_path = self.root.join(path)
|
99
|
-
const_name = Utils.camelize File.basename(path).sub(RB_EXT, '')
|
100
|
-
mod = modules.fetch File.dirname path.sub(root_regex, '')
|
101
|
-
mod.autoload const_name, full_path
|
79
|
+
def paths(glob, root_depth: default_root_depth)
|
80
|
+
builder = ModuleBuilder.new(root_depth: root_depth, root_module: root_module)
|
81
|
+
Dir.glob(File.join root_path.to_s, *glob).each do |full_path|
|
82
|
+
next if all_files.include? full_path
|
102
83
|
all_files << full_path if eager_load
|
103
|
-
end
|
104
84
|
|
85
|
+
rel_path = full_path.sub root_path.to_s, ''
|
86
|
+
dir_path, file_name = File.split rel_path
|
87
|
+
const_name = Utils.camelize file_name.sub RB_EXT, ''
|
88
|
+
builder.module(dir_path).autoload const_name, full_path
|
89
|
+
end
|
105
90
|
self
|
106
91
|
end
|
107
92
|
|
108
93
|
#
|
109
94
|
# Perform any final operations or cleanup. If eager_load is true, this is where they're loaded.
|
110
95
|
#
|
111
|
-
def
|
96
|
+
def finalize!
|
112
97
|
all_files.each { |f| require f } if eager_load
|
113
98
|
all_files.clear
|
114
99
|
self
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module AwesomeLoader
|
2
|
+
#
|
3
|
+
# Recursively builds modules out of directory structures.
|
4
|
+
#
|
5
|
+
class ModuleBuilder
|
6
|
+
# @return [Integer] the dir depth at which to start building modules.
|
7
|
+
attr_reader :root_depth
|
8
|
+
# @return [Module] the root ruby Module
|
9
|
+
attr_reader :root_module
|
10
|
+
|
11
|
+
#
|
12
|
+
# Initializes a new builder.
|
13
|
+
#
|
14
|
+
# @param root_depth [Integer] directory depth at which to start building modules.
|
15
|
+
# @param root_module [Module] Module to load the modules into (default Object). You'll probably always want to keep the default.
|
16
|
+
#
|
17
|
+
def initialize(root_depth:, root_module: Object)
|
18
|
+
@root_depth = root_depth
|
19
|
+
@root_module = root_module
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Returns (building if necessary) the Module represented by the dir path. The path should be relative
|
24
|
+
# to your application root/working directory.
|
25
|
+
#
|
26
|
+
# # Since root_depth is 2, the first 2 dirs in any path will be ignored
|
27
|
+
# builder = ModuleBuilder.new(root_depth: 2)
|
28
|
+
#
|
29
|
+
# builder.module('src/models')
|
30
|
+
# => Object
|
31
|
+
#
|
32
|
+
# builder.module('src/features/billing')
|
33
|
+
# => Billing
|
34
|
+
#
|
35
|
+
# builder.module('src/services/billing/foo')
|
36
|
+
# => Billing::Foo
|
37
|
+
#
|
38
|
+
# @param rel_filepath [String] The path, relative to your application root, to the file you want the module for.
|
39
|
+
# @return [Module] The module that should contain the constant in the file.
|
40
|
+
#
|
41
|
+
def module(rel_path)
|
42
|
+
module_names(rel_path).reduce(root_module) { |parent_mod, mod_name|
|
43
|
+
if parent_mod.const_defined? mod_name, false
|
44
|
+
parent_mod.const_get mod_name
|
45
|
+
else
|
46
|
+
parent_mod.const_set mod_name, Module.new
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
#
|
52
|
+
# Returns an array of nested Module names based on the directory structure of the given path.
|
53
|
+
#
|
54
|
+
# builder = ModuleBuilder.new(root_depth: 2)
|
55
|
+
#
|
56
|
+
# # Since root_depth is 2, 'src' and 'models' are ignored and there aren't any modules
|
57
|
+
# builder.nested_dirs('src/models')
|
58
|
+
# => []
|
59
|
+
#
|
60
|
+
# builder.nested_dirs('src/features/billing')
|
61
|
+
# => ['Billing']
|
62
|
+
#
|
63
|
+
# builder.nested_dirs('src/features/billing/foo')
|
64
|
+
# => ['Billing', 'Foo']
|
65
|
+
#
|
66
|
+
def module_names(rel_path)
|
67
|
+
dir_names = Utils.clean_path(rel_path).split '/'
|
68
|
+
return [] if rel_path == '.' or root_depth > dir_names.size
|
69
|
+
dir_names[root_depth..-1].map { |name| Utils.camelize name }
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
data/lib/awesome_loader/utils.rb
CHANGED
@@ -4,7 +4,9 @@ module AwesomeLoader
|
|
4
4
|
#
|
5
5
|
module Utils
|
6
6
|
# Regex to match "snake name" paths
|
7
|
-
SNAKE =
|
7
|
+
SNAKE = %r{_([a-z])}
|
8
|
+
LEADING_SEP = %r{^/}
|
9
|
+
TRAILING_SEP = %r{/$}
|
8
10
|
|
9
11
|
#
|
10
12
|
# Converts a snake_case_name to a CamelCaseName.
|
@@ -15,5 +17,15 @@ module AwesomeLoader
|
|
15
17
|
def self.camelize(name)
|
16
18
|
name.capitalize.gsub(SNAKE) { |match| match[1].capitalize }
|
17
19
|
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Returns the path with any leading or trailing /'s removed.
|
23
|
+
#
|
24
|
+
# @param path [String]
|
25
|
+
# @return [String]
|
26
|
+
#
|
27
|
+
def self.clean_path(path)
|
28
|
+
path.sub(LEADING_SEP, '').sub(TRAILING_SEP, '')
|
29
|
+
end
|
18
30
|
end
|
19
31
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: awesome_loader
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jordan Hollinger
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-04-
|
11
|
+
date: 2017-04-09 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: An awesome wrapper for Ruby's built-in autoload
|
14
14
|
email: jordan.hollinger@gmail.com
|
@@ -20,6 +20,7 @@ files:
|
|
20
20
|
- README.md
|
21
21
|
- lib/awesome_loader.rb
|
22
22
|
- lib/awesome_loader/autoloader.rb
|
23
|
+
- lib/awesome_loader/module_builder.rb
|
23
24
|
- lib/awesome_loader/utils.rb
|
24
25
|
- lib/awesome_loader/version.rb
|
25
26
|
homepage: https://github.com/jhollinger/awesome_loader
|
@@ -42,7 +43,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
42
43
|
version: '0'
|
43
44
|
requirements: []
|
44
45
|
rubyforge_project:
|
45
|
-
rubygems_version: 2.
|
46
|
+
rubygems_version: 2.5.2
|
46
47
|
signing_key:
|
47
48
|
specification_version: 4
|
48
49
|
summary: An awesome way to autoload your Ruby application
|