auto_reloader 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ba4a09d3953bfde28a2af58974520277a6586f66
4
+ data.tar.gz: fbf78f768bb8ea66ec1ca341f74ecae6fe0c207c
5
+ SHA512:
6
+ metadata.gz: c307052c4138e40de2855ace66cb2c5369771ce2aadaa8366cb4bf146dde3e8fc59733fd37a79b2a72f47a0692485d68d62681816e46245f992fd41c7a3694d6
7
+ data.tar.gz: 40fd961e6b85db733047235bef5099fe5d9a2e18492845495c94e98307d000c9047ed564532b552b617db7b55bb2698f6d31d6d91c03a3a94d3036f212cfa562
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --order default
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.3.1
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in auto_reloader.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Rodrigo Rosenfeld Rosas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,129 @@
1
+ # AutoReloader
2
+
3
+ AutoReloader is a lightweight code reloader intended to be used specially in development mode of server applications.
4
+
5
+ It will override `require` and `require_relative` when activated.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'auto_reloader'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install auto_reloader
22
+
23
+ ## Usage
24
+
25
+ AutoReloader will try to add auto-reloading code support transparently by unloading all files
26
+ belonging to the list of reloadable paths and the constants defined by them. This is not always
27
+ possible to handle transparently, so please read the Known Caveats to properly understand what
28
+ you should do to avoid them.
29
+
30
+ Here's how it would be used in a Rack application.
31
+
32
+ app.rb:
33
+
34
+ ```ruby
35
+ App = ->{['200', {'Content-Type' => 'text/plain'}, ['Sample output']]}
36
+ ```
37
+
38
+ config.ru:
39
+
40
+ ```ruby
41
+ if ENV['RACK_ENV'] != 'development'
42
+ require_relative 'app'
43
+ run App
44
+ else
45
+ require 'auto_reloader'
46
+ # won't reload before 1s elapsed since last reload by default. It can be overridden
47
+ # in the reload! call below
48
+ AutoReloader.activate reloadable_paths: [__dir__], delay: 1
49
+ run ->(env) {
50
+ AutoReloader.reload! do
51
+ require_relative 'app'
52
+ App.call env
53
+ end
54
+ }
55
+ end
56
+ ```
57
+
58
+ Just change "Sample output" to something else and reload the page.
59
+
60
+ By default reloading will only happen if one of the reloadable file was changed since it was
61
+ required. This can be overriden by providing the `onchange: false` option to either `activate`
62
+ or `reload!`.
63
+
64
+ ## Known Caveats
65
+
66
+ In order to work transparently AutoReloader will override `require` and `require_relative` when
67
+ activated and track changes to the top-level constants after each require. Top-level constants
68
+ defined by reloadable files are removed upon `reload!` or `unload!`. So, if your application
69
+ does something crazy like this:
70
+
71
+ json-extension.rb:
72
+
73
+ ```ruby
74
+ class JSON
75
+ class MyExtension
76
+ # crazy stuff: don't do that
77
+ end
78
+ end
79
+ ```
80
+
81
+ If you require 'json-extension' before requiring 'json', supposing it's reloadable, `unload!`
82
+ and `reload!` would remove the JSON constant because AutoReloader will think it was defined
83
+ by 'json-extension'. If you require 'json' before this file, then JSON won't be removed but
84
+ neither will JSON::MyExtension.
85
+
86
+ As a general rule, any reloadable file should not change the behavior of code in non
87
+ reloadable files.
88
+
89
+ ## Implementation description
90
+
91
+ AutoReloader doesn't try to reload only the changed files. If any of the reloadable files change
92
+ then all reloadable files are unloaded and the constants they defined are removed. Reloadable
93
+ files are those living in one of the `reloadable_paths` entries. The more paths it has to search
94
+ the bigger will be the overhead to `require` and `require_relative`.
95
+
96
+ Currently this implementation does not use an evented watcher to detect changes to files but
97
+ it may be considered in future versions. Currently it traverses each loaded reloadable file and
98
+ check whether it was changed.
99
+
100
+ ## AutoReloadable does not support automatic autoload
101
+
102
+ AutoReloadable does not provide automatic autoload features like ActiveSupport::Dependencies
103
+ by design and won't support it ever, although such feature could be implemented as an extension
104
+ or as a separate gem. Personally I don't find it a good practice and I think all dependencies
105
+ should be declared explicitly by all files depending on them even if it's not necessary because
106
+ it was already required by another file.
107
+
108
+ ## Development
109
+
110
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec`
111
+ to run the tests. You can also run `bin/console` for an interactive prompt that will allow
112
+ you to experiment.
113
+
114
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a
115
+ new version, update the version number in `version.rb`, and then run `bundle exec rake release`,
116
+ which will create a git tag for the version, push git commits and tags, and push the `.gem`
117
+ file to [rubygems.org](https://rubygems.org).
118
+
119
+ ## Contributing
120
+
121
+ Bug reports and pull requests are welcome
122
+ [on GitHub](https://github.com/rosenfeld/auto_reloader).
123
+
124
+
125
+ ## License
126
+
127
+ The gem is available as open source under the terms of the
128
+ [MIT License](http://opensource.org/licenses/MIT).
129
+
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,22 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'auto_reloader/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'auto_reloader'
8
+ spec.version = AutoReloader::VERSION
9
+ spec.authors = ['Rodrigo Rosenfeld Rosas']
10
+ spec.email = ['rr.rosas@gmail.com']
11
+
12
+ spec.summary = %q{A transparent code reloader.}
13
+ spec.homepage = 'https://github.com/rosenfeld/auto_reloader'
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
17
+ spec.require_paths = ['lib']
18
+
19
+ spec.add_development_dependency 'bundler', '~> 1.11'
20
+ spec.add_development_dependency 'rake', '~> 10.0'
21
+ spec.add_development_dependency 'rspec', '~> 3.0'
22
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "auto_reloader"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,161 @@
1
+ require 'auto_reloader/version'
2
+ require 'singleton'
3
+ require 'monitor'
4
+ require 'thread' # for Mutex
5
+ require 'set'
6
+ require 'time' unless defined?(Process::CLOCK_MONOTONIC)
7
+
8
+ class AutoReloader
9
+ include Singleton
10
+
11
+ attr_reader :reloadable_paths
12
+
13
+ module RequireOverride
14
+ def require(path)
15
+ AutoReloader.instance.require(path) { super }
16
+ end
17
+
18
+ def require_relative(path)
19
+ fullpath = File.join File.dirname(caller.first), path
20
+ AutoReloader.instance.require_relative path, fullpath
21
+ end
22
+ end
23
+
24
+ def self.activate(*args)
25
+ instance.activate *args
26
+ end
27
+
28
+ def initialize
29
+ @activate_lock = Mutex.new
30
+ end
31
+
32
+ ActivatedMoreThanOnce = Class.new RuntimeError
33
+ def activate(reloadable_paths: [], onchange: true, delay: nil)
34
+ @activate_lock.synchronize do
35
+ raise ActivatedMoreThanOnce, "Can only activate Autoreloader once" if @reloadable_paths
36
+ @delay = delay
37
+ @onchange = onchange
38
+ self.reloadable_paths = reloadable_paths
39
+ Object.include RequireOverride
40
+ @require_lock = Monitor.new # monitor is like Mutex, but reentrant
41
+ @reload_lock = Mutex.new
42
+ @top_level_consts_stack = []
43
+ @unload_constants = Set.new
44
+ @unload_files = Set.new
45
+ @last_reloaded = clock_time
46
+ end
47
+ end
48
+
49
+ def self.reloadable_paths=(paths)
50
+ instance.reloadable_paths = paths
51
+ end
52
+
53
+ def reloadable_paths=(paths)
54
+ @reloadable_paths = paths.map{|rp| File.expand_path(rp).freeze }.freeze
55
+ end
56
+
57
+ def require(path, &block)
58
+ was_required = false
59
+ @require_lock.synchronize do
60
+ @top_level_consts_stack << Set.new
61
+ old_consts = Object.constants
62
+ prev_consts = new_top_level_constants = nil
63
+ begin
64
+ was_required = yield
65
+ ensure
66
+ prev_consts = @top_level_consts_stack.pop
67
+ if was_required
68
+ new_top_level_constants = Object.constants - old_consts - prev_consts.to_a
69
+ @top_level_consts_stack.each{|c| c.merge new_top_level_constants }
70
+ end
71
+ end
72
+ return false unless was_required # was required already, do nothing
73
+ full_loaded_path = $LOADED_FEATURES.last
74
+ reloadable = reloadable?(full_loaded_path, path)
75
+ if reloadable
76
+ @reload_lock.synchronize do
77
+ @unload_constants.merge new_top_level_constants
78
+ @unload_files << full_loaded_path
79
+ end
80
+ end
81
+ end
82
+ was_required
83
+ end
84
+
85
+ def require_relative(path, fullpath)
86
+ Object.require fullpath
87
+ end
88
+
89
+ def self.reload!(*args)
90
+ if block_given?
91
+ instance.reload!(*args){ yield }
92
+ else
93
+ instance.reload!(*args)
94
+ end
95
+ end
96
+
97
+ InvalidUsage = Class.new RuntimeError
98
+ def reload!(delay: @default_delay, onchange: @default_onchange)
99
+ if onchange && !block_given?
100
+ raise InvalidUsage, 'A block must be provided to reload! when onchange is true (the default)'
101
+ end
102
+
103
+ unload! unless ignore_reload?(delay, onchange)
104
+
105
+ result = nil
106
+ if block_given?
107
+ result = yield
108
+ find_mtime
109
+ end
110
+ @last_reloaded = clock_time if delay
111
+ result
112
+ end
113
+
114
+ def self.unload!
115
+ instance.unload!
116
+ end
117
+
118
+ def unload!
119
+ @reload_lock.synchronize do
120
+ @unload_files.each{|f| $LOADED_FEATURES.delete f }
121
+ @unload_constants.each{|c| Object.send :remove_const, c }
122
+ @unload_files = Set.new
123
+ @unload_constants = Set.new
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ if defined?(Process::CLOCK_MONOTONIC)
130
+ def clock_time
131
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
132
+ end
133
+ else
134
+ def clock_time
135
+ Time.now.to_f
136
+ end
137
+ end
138
+
139
+ def reloadable?(fullpath, path)
140
+ @reloadable_paths.any?{|rp| fullpath.start_with? rp}
141
+ end
142
+
143
+ def ignore_reload?(delay, onchange)
144
+ (delay && (clock_time - @last_reloaded < delay)) || (onchange && !changed?)
145
+ end
146
+
147
+ def changed?
148
+ return true unless @last_mtime_by_path
149
+ @reload_lock.synchronize do
150
+ return @unload_files.any?{|f| @last_mtime_by_path[f] != File.mtime(f) }
151
+ end
152
+ end
153
+
154
+ def find_mtime
155
+ @reload_lock.synchronize do
156
+ @last_mtime_by_path = {}
157
+ @unload_files.each{|f| @last_mtime_by_path[f] = File.mtime f }
158
+ end
159
+ @last_mtime_by_path
160
+ end
161
+ end
@@ -0,0 +1,3 @@
1
+ class AutoReloader
2
+ VERSION = '0.1.0'
3
+ end
metadata ADDED
@@ -0,0 +1,98 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: auto_reloader
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Rodrigo Rosenfeld Rosas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-07-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.11'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.11'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.0'
55
+ description:
56
+ email:
57
+ - rr.rosas@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - ".gitignore"
63
+ - ".rspec"
64
+ - ".travis.yml"
65
+ - Gemfile
66
+ - LICENSE.txt
67
+ - README.md
68
+ - Rakefile
69
+ - auto_reloader.gemspec
70
+ - bin/console
71
+ - bin/setup
72
+ - lib/auto_reloader.rb
73
+ - lib/auto_reloader/version.rb
74
+ homepage: https://github.com/rosenfeld/auto_reloader
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - ">="
90
+ - !ruby/object:Gem::Version
91
+ version: '0'
92
+ requirements: []
93
+ rubyforge_project:
94
+ rubygems_version: 2.5.1
95
+ signing_key:
96
+ specification_version: 4
97
+ summary: A transparent code reloader.
98
+ test_files: []