awesome_loader 0.1.0 → 1.0.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.
- 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
|