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 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