grape-reload 0.0.2 → 0.0.3
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 +2 -0
- data/Rakefile +1 -1
- data/lib/core_ext/string/colorize.rb +62 -0
- data/lib/grape/reload.rb +3 -1
- data/lib/grape/reload/dependency_map.rb +9 -1
- data/lib/grape/reload/grape_api.rb +6 -1
- data/lib/grape/reload/rack_builder.rb +4 -3
- data/lib/grape/reload/storage.rb +1 -1
- data/lib/grape/reload/version.rb +1 -1
- data/lib/grape/reload/watcher.rb +1 -3
- data/lib/ripper/extract_constants.rb +6 -15
- data/spec/fixtures/app2/mounts/mount.rb +3 -1
- data/spec/fixtures/app2/test2.rb +2 -5
- data/spec/grape/reload/dependency_map_spec.rb +24 -4
- data/spec/grape/reload/watcher_spec.rb +15 -0
- data/spec/ripper/extract_constants_spec.rb +11 -3
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d23fb6507e6ffdadf978ae7cafa32ad6a49b7b09
|
4
|
+
data.tar.gz: 4cdb689e50c6f760afb8ea2fe660b3c4d3971cea
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ee472baaa8f3a405e193940073a8ea0734501179432b18f33e6d625e58728d06cf884e318430581f720c6d2598d7ea5811b089a1fc5e650a62fef5d79fa5e846
|
7
|
+
data.tar.gz: e6d6ce030ee2f4f6aff852817276cb3597fb852cbf0c178505a069b622aa5cbf662049450e20e356cb66555288817d1be149d5b618da21fe586a87a647172b60
|
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[](https://travis-ci.org/AlexYankee/grape-reload)
|
2
|
+
[](http://badge.fury.io/rb/grape-reload)
|
1
3
|
# Grape::Reload
|
2
4
|
|
3
5
|
Expiremental approach for providing reloading of Grape-based rack applications in dev environment.
|
data/Rakefile
CHANGED
@@ -4,7 +4,7 @@ require 'bundler/gem_tasks'
|
|
4
4
|
# Default directory to look in is `/specs`
|
5
5
|
# Run with `rake spec`
|
6
6
|
RSpec::Core::RakeTask.new(:spec) do |task|
|
7
|
-
task.rspec_opts = ['--color', '--format', '
|
7
|
+
task.rspec_opts = ['--color', '--format', 'progress']
|
8
8
|
end
|
9
9
|
|
10
10
|
task :default => :spec
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# Ruby color support for Windows
|
2
|
+
# If you want colorize on Windows with Ruby 1.9, please use ansicon:
|
3
|
+
# https://github.com/adoxa/ansicon
|
4
|
+
# Other ways, add `gem 'win32console'` to your Gemfile.
|
5
|
+
if RUBY_PLATFORM =~ /mswin|mingw/ && RUBY_VERSION < '2.0' && ENV['ANSICON'].nil?
|
6
|
+
begin
|
7
|
+
require 'win32console'
|
8
|
+
rescue LoadError
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
##
|
13
|
+
# Add colors
|
14
|
+
#
|
15
|
+
class String
|
16
|
+
# colorize(:red)
|
17
|
+
def colorize(args)
|
18
|
+
case args
|
19
|
+
when Symbol
|
20
|
+
Colorizer.send(args, self)
|
21
|
+
when Hash
|
22
|
+
Colorizer.send(args[:color], self, args[:mode])
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Used to colorize strings for the shell
|
27
|
+
class Colorizer
|
28
|
+
# Returns colors integer mapping
|
29
|
+
def self.colors
|
30
|
+
@_colors ||= {
|
31
|
+
:default => 9,
|
32
|
+
:black => 30,
|
33
|
+
:red => 31,
|
34
|
+
:green => 32,
|
35
|
+
:yellow => 33,
|
36
|
+
:blue => 34,
|
37
|
+
:magenta => 35,
|
38
|
+
:cyan => 36,
|
39
|
+
:white => 37
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns modes integer mapping
|
44
|
+
def self.modes
|
45
|
+
@_modes ||= {
|
46
|
+
:default => 0,
|
47
|
+
:bold => 1
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Defines class level color methods
|
52
|
+
# i.e Colorizer.red("hello")
|
53
|
+
class << self
|
54
|
+
Colorizer.colors.each do |color, value|
|
55
|
+
define_method(color) do |target, mode_name = :default|
|
56
|
+
mode = modes[mode_name] || modes[:default]
|
57
|
+
"\e[#{mode};#{value}m" << target << "\e[0m"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
data/lib/grape/reload.rb
CHANGED
@@ -2,6 +2,11 @@ require_relative '../../ripper/extract_constants'
|
|
2
2
|
|
3
3
|
module Grape
|
4
4
|
module Reload
|
5
|
+
class UnresolvedDependenciesError < RuntimeError
|
6
|
+
def message; 'One or more unresolved dependencies found' end
|
7
|
+
end
|
8
|
+
|
9
|
+
|
5
10
|
class DependencyMap
|
6
11
|
extend Forwardable
|
7
12
|
include TSort
|
@@ -32,11 +37,12 @@ module Grape
|
|
32
37
|
|
33
38
|
def dependent_classes(loaded_file)
|
34
39
|
classes = []
|
40
|
+
sorted = sorted_files
|
35
41
|
cycle_classes = ->(file, visited_files = []){
|
36
42
|
return if visited_files.include?(file)
|
37
43
|
visited_files ||= []
|
38
44
|
visited_files << file
|
39
|
-
classes |= map[file][:declared]
|
45
|
+
classes |= map[file][:declared]
|
40
46
|
map[file][:declared].map{|klass|
|
41
47
|
file_class = map.each_pair
|
42
48
|
.sort{|a1, a2|
|
@@ -110,6 +116,8 @@ module Grape
|
|
110
116
|
unresolved_classes.each_pair do |klass, filenames|
|
111
117
|
filenames.each {|filename| Grape::RackBuilder.logger.error("Unresolved const reference #{klass} from: #{filename}".colorize(:red)) }
|
112
118
|
end
|
119
|
+
|
120
|
+
raise UnresolvedDependenciesError if unresolved_classes.any?
|
113
121
|
end
|
114
122
|
end
|
115
123
|
|
@@ -17,7 +17,7 @@ CLASS
|
|
17
17
|
end
|
18
18
|
|
19
19
|
def initialize(app)
|
20
|
-
@app_klass = app
|
20
|
+
@app_klass = app
|
21
21
|
end
|
22
22
|
|
23
23
|
def call(*args)
|
@@ -48,12 +48,17 @@ METHOD
|
|
48
48
|
def reinit!
|
49
49
|
declaration = class_declaration.dup
|
50
50
|
@class_decl = []
|
51
|
+
endpoints.each { |e| e.options[:app].reinit! if e.options[:app] }
|
51
52
|
reset!
|
52
53
|
declaration.each {|decl|
|
53
54
|
send(decl[0],*deep_reconstantize.call(decl[1]),&decl[2])
|
54
55
|
}
|
55
56
|
change!
|
56
57
|
end
|
58
|
+
|
59
|
+
def recursive_!
|
60
|
+
|
61
|
+
end
|
57
62
|
private
|
58
63
|
def class_declaration
|
59
64
|
@class_decl ||= []
|
@@ -12,7 +12,7 @@ module Grape
|
|
12
12
|
class << self
|
13
13
|
[:error, :debug, :exception, :info, :devel].each do |level|
|
14
14
|
define_method(level){|*args|
|
15
|
-
|
15
|
+
# Silence all reloader output by default with stub
|
16
16
|
}
|
17
17
|
end
|
18
18
|
end
|
@@ -75,10 +75,11 @@ module Grape
|
|
75
75
|
if environment == 'development'
|
76
76
|
r = Rack::Builder.new
|
77
77
|
r.use Grape::ReloadMiddleware[reload_threshold]
|
78
|
-
r.run m.app_class
|
78
|
+
r.run m.app_class
|
79
79
|
map(m.mount_root) { run r }
|
80
80
|
else
|
81
|
-
|
81
|
+
app_klass = m.app_class.constantize
|
82
|
+
map(m.mount_root) { run app_klass }
|
82
83
|
end
|
83
84
|
end
|
84
85
|
end
|
data/lib/grape/reload/storage.rb
CHANGED
data/lib/grape/reload/version.rb
CHANGED
data/lib/grape/reload/watcher.rb
CHANGED
@@ -14,12 +14,10 @@ module Grape
|
|
14
14
|
def logger; Grape::RackBuilder.logger end
|
15
15
|
|
16
16
|
def safe_load(file, options={})
|
17
|
-
began_at = Time.now
|
18
17
|
return unless options[:force] || file_changed?(file)
|
19
|
-
# return require(file) if feature_excluded?(file)
|
20
18
|
|
21
19
|
Storage.prepare(file) # might call #safe_load recursively
|
22
|
-
logger.
|
20
|
+
logger.debug((file_new?(file) ? "loading" : "reloading") + "#{file}" )
|
23
21
|
begin
|
24
22
|
with_silence{ require(file) }
|
25
23
|
Storage.commit(file)
|
@@ -102,23 +102,13 @@ class TraversingResult
|
|
102
102
|
!variants.find{|v| result[:declared].include?(v) }.nil?
|
103
103
|
}
|
104
104
|
|
105
|
-
used = self.used.reject {|variants|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
if namespace
|
110
|
-
ns_variants = [namespace+'::']
|
111
|
-
full_namespace[0..-2].reverse.each{|ns| ns_variants << ns + '::' + ns_variants.last}
|
112
|
-
used.each do |variants|
|
113
|
-
# variants = variants.reject{ |v|
|
114
|
-
# !ns_variants.find{|ns_part| v.start_with?(ns_part) }.nil?
|
115
|
-
# }
|
116
|
-
result[:used] = result[:used] << variants
|
117
|
-
end
|
118
|
-
else
|
119
|
-
result[:used] = result[:used].concat(used)
|
105
|
+
used = self.used.reject {|variants| !variants.find{|v| result[:declared].include?(v) }.nil? }
|
106
|
+
if namespace.nil?
|
107
|
+
used = used.map {|variants| variants.map{|v| (v.start_with?('::') ? '' : (namespace || '') + '::') + v }}
|
120
108
|
end
|
121
109
|
|
110
|
+
result[:used] = result[:used].concat(used)
|
111
|
+
|
122
112
|
result
|
123
113
|
end
|
124
114
|
end
|
@@ -299,6 +289,7 @@ class ASTModule < ASTEntity
|
|
299
289
|
}
|
300
290
|
end
|
301
291
|
def collect_constants(result, context)
|
292
|
+
result.declare_const(@module_name)
|
302
293
|
result = result.nest(@module_name)
|
303
294
|
context.module << @module_name
|
304
295
|
super(result, context)
|
data/spec/fixtures/app2/test2.rb
CHANGED
@@ -1,12 +1,9 @@
|
|
1
1
|
module Test
|
2
2
|
class App2 < Grape::API
|
3
3
|
format :txt
|
4
|
-
mount Test::Mount2 => '/mounted'
|
5
|
-
# mount Test::Mount10 => '/mounted2'
|
6
|
-
mount Test::LibMount2 => '/lib_mounted'
|
7
|
-
#changed: mount Test::LibMount2 => '/lib_mounted'
|
4
|
+
mount Test::Mount2 => '/mounted' #changed: mount Test::Mount2 => '/mounted2'
|
8
5
|
get :test do
|
9
|
-
'test2 response
|
6
|
+
'test2 response changed'
|
10
7
|
end
|
11
8
|
end
|
12
9
|
end
|
@@ -10,11 +10,27 @@ describe Grape::Reload::DependencyMap do
|
|
10
10
|
},
|
11
11
|
'file2' => {
|
12
12
|
declared: ['::Class2'],
|
13
|
-
used: ['::Class1','::Class3'],
|
13
|
+
used: [['::Class1'],['::Class3']],
|
14
14
|
},
|
15
15
|
'file3' => {
|
16
16
|
declared: ['::Class3'],
|
17
|
-
used: ['::
|
17
|
+
used: [['::Class1']],
|
18
|
+
},
|
19
|
+
}
|
20
|
+
}
|
21
|
+
let!(:wrong_class_map) {
|
22
|
+
{
|
23
|
+
'file1' => {
|
24
|
+
declared: ['::Class1'],
|
25
|
+
used: [],
|
26
|
+
},
|
27
|
+
'file2' => {
|
28
|
+
declared: ['::Class2'],
|
29
|
+
used: [['::Class1'],['::Class3']],
|
30
|
+
},
|
31
|
+
'file3' => {
|
32
|
+
declared: ['::Class3'],
|
33
|
+
used: [['::Class5']],
|
18
34
|
},
|
19
35
|
}
|
20
36
|
}
|
@@ -22,9 +38,13 @@ describe Grape::Reload::DependencyMap do
|
|
22
38
|
|
23
39
|
it 'resolves dependent classes properly' do
|
24
40
|
allow(dm).to receive(:map).and_return(file_class_map)
|
25
|
-
|
26
|
-
# allow(map).to receive(:map).and_return(file_class_map)
|
41
|
+
dm.resolve_dependencies!
|
27
42
|
|
28
43
|
expect(dm.dependent_classes('file1')).to include('::Class2','::Class3')
|
29
44
|
end
|
45
|
+
|
46
|
+
it "raises error if dependencies can't be resolved" do
|
47
|
+
allow(dm).to receive(:map).and_return(wrong_class_map)
|
48
|
+
expect { dm.resolve_dependencies! }.to raise_error(Grape::Reload::UnresolvedDependenciesError)
|
49
|
+
end
|
30
50
|
end
|
@@ -6,6 +6,7 @@ describe Grape::Reload::Watcher do
|
|
6
6
|
before(:example) do
|
7
7
|
@app =
|
8
8
|
Grape::RackBuilder.setup do
|
9
|
+
add_source_path File.expand_path('**.rb', APP_ROOT)
|
9
10
|
add_source_path File.expand_path('**/*.rb', APP_ROOT)
|
10
11
|
environment 'development'
|
11
12
|
reload_threshold 0
|
@@ -42,6 +43,20 @@ describe Grape::Reload::Watcher do
|
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
46
|
+
it 'remounts class on different root' do
|
47
|
+
get '/test2/mounted/test'
|
48
|
+
expect(last_response).to succeed
|
49
|
+
expect(last_response.body).to eq('test')
|
50
|
+
|
51
|
+
with_changed_fixture 'app2/test2.rb' do
|
52
|
+
get '/test2/mounted/test'
|
53
|
+
expect(last_response).to_not succeed
|
54
|
+
|
55
|
+
get '/test2/mounted2/test'
|
56
|
+
expect(last_response).to succeed
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
45
60
|
it 'reloads library file and reinits all affected APIs' do
|
46
61
|
with_changed_fixture 'app1/test1.rb' do
|
47
62
|
get '/test1/lib_mounted/lib_string'
|
@@ -53,6 +53,13 @@ CODE
|
|
53
53
|
'test2 response'
|
54
54
|
end
|
55
55
|
end
|
56
|
+
module EmptyModule
|
57
|
+
end
|
58
|
+
end
|
59
|
+
class WithoutModule
|
60
|
+
def use_top_level
|
61
|
+
TopLevel.new
|
62
|
+
end
|
56
63
|
end
|
57
64
|
CODE
|
58
65
|
}
|
@@ -83,15 +90,16 @@ CODE
|
|
83
90
|
it 'extract consts from code2 correctly' do
|
84
91
|
consts = Ripper.extract_constants(code2)
|
85
92
|
expect(consts[:declared].flatten).to include(
|
86
|
-
'::Test::App2'
|
93
|
+
'::Test::App2',
|
94
|
+
'::Test::EmptyModule'
|
87
95
|
)
|
88
96
|
|
89
97
|
expect(consts[:used].flatten).to include(
|
90
98
|
'::Test::Mount2',
|
91
99
|
'::Test::Mount10',
|
92
100
|
'::Test::SomeAnotherEntity',
|
93
|
-
'::SomeClass'
|
101
|
+
'::SomeClass',
|
102
|
+
'::TopLevel'
|
94
103
|
)
|
95
|
-
|
96
104
|
end
|
97
105
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grape-reload
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- AMar4enko
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-09-
|
11
|
+
date: 2014-09-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: grape
|
@@ -208,6 +208,7 @@ files:
|
|
208
208
|
- Rakefile
|
209
209
|
- grape-reload.gemspec
|
210
210
|
- lib/core_ext/object_space.rb
|
211
|
+
- lib/core_ext/string/colorize.rb
|
211
212
|
- lib/grape/reload.rb
|
212
213
|
- lib/grape/reload/dependency_map.rb
|
213
214
|
- lib/grape/reload/grape_api.rb
|