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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ff578e319ae7955c039a49a12e1a7dd41f4af2db
4
- data.tar.gz: 1044693107960f3dbfd2b1f78f3247316ae01502
3
+ metadata.gz: 362e0c1826dd0bcde66b8e466799b0b37743f2f2
4
+ data.tar.gz: 0d981dffe720657efa76c056b37b4d4f09ad8ae3
5
5
  SHA512:
6
- metadata.gz: 116775922f484ed6eca46c5d5655f72bae1c5234e6b039c1e4f69734c195f0faffa1ccd4759538dc9c3a476fb41a4d607c8862dc0874c8cd529c255b7670b425
7
- data.tar.gz: 71ed372da0aaeba0719423c971d9399027adf980ffb5472dc63611c8cf16797fb05e1af2557601e04b1e1398b0caff2a1262a2963dcca4f8d955f43118c88771
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
- Let's say your (non-Rails) app has a very Rails-like layout. `app/models`, `app/helpers`, etc. And those might have some subdirectories that correspond to module names. This is all you have to do:
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 often considered best practice to load everything
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
@@ -1,3 +1,4 @@
1
1
  require 'awesome_loader/version'
2
+ require 'awesome_loader/module_builder'
2
3
  require 'awesome_loader/autoloader'
3
4
  require 'awesome_loader/utils'
@@ -2,7 +2,7 @@ require 'set'
2
2
  require 'pathname'
3
3
 
4
4
  #
5
- # A module that holds all the awesomeness.
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 root [String] Path to root of the application (default Dir.cwd)
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:, root: Dir.cwd, eager_load: false, &block)
23
- autoloader = Autoloader.new(root: root, root_depth: root_depth, eager_load: eager_load)
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.finialize!
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
- # finialize!
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 [Array<String>] all defined files, ready to be eager_loaded
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 root [String] Path to root of the application (default Dir.cwd)
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:, root: Dir.cwd, eager_load: false)
61
- @root = Pathname.new(root.to_s)
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 array [Array<String>] A glob pattern as an array.
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(array, root_depth: default_root_depth)
76
- files = Dir.glob File.join *array
77
- root_regex = Regexp.new "^([^/]+/){%d}" % root_depth
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 finialize!
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
@@ -4,7 +4,9 @@ module AwesomeLoader
4
4
  #
5
5
  module Utils
6
6
  # Regex to match "snake name" paths
7
- SNAKE = /_([a-z])/
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
@@ -1,4 +1,4 @@
1
1
  module AwesomeLoader
2
2
  # Library version
3
- VERSION = '0.1.0'.freeze
3
+ VERSION = '1.0.0'.freeze
4
4
  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: 0.1.0
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-07 00:00:00.000000000 Z
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.6.8
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