tennpipes-base 3.6.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.rdoc +294 -0
  4. data/Rakefile +1 -0
  5. data/bin/tennpipes +8 -0
  6. data/lib/tennpipes-base.rb +196 -0
  7. data/lib/tennpipes-base/application.rb +175 -0
  8. data/lib/tennpipes-base/application/application_setup.rb +202 -0
  9. data/lib/tennpipes-base/application/authenticity_token.rb +25 -0
  10. data/lib/tennpipes-base/application/flash.rb +229 -0
  11. data/lib/tennpipes-base/application/params_protection.rb +129 -0
  12. data/lib/tennpipes-base/application/routing.rb +1002 -0
  13. data/lib/tennpipes-base/application/show_exceptions.rb +50 -0
  14. data/lib/tennpipes-base/caller.rb +53 -0
  15. data/lib/tennpipes-base/cli/adapter.rb +33 -0
  16. data/lib/tennpipes-base/cli/base.rb +105 -0
  17. data/lib/tennpipes-base/cli/console.rb +20 -0
  18. data/lib/tennpipes-base/cli/launcher.rb +103 -0
  19. data/lib/tennpipes-base/cli/rake.rb +50 -0
  20. data/lib/tennpipes-base/cli/rake_tasks.rb +72 -0
  21. data/lib/tennpipes-base/command.rb +38 -0
  22. data/lib/tennpipes-base/ext/sinatra.rb +29 -0
  23. data/lib/tennpipes-base/filter.rb +52 -0
  24. data/lib/tennpipes-base/images/404.png +0 -0
  25. data/lib/tennpipes-base/images/500.png +0 -0
  26. data/lib/tennpipes-base/loader.rb +202 -0
  27. data/lib/tennpipes-base/logger.rb +492 -0
  28. data/lib/tennpipes-base/module.rb +58 -0
  29. data/lib/tennpipes-base/mounter.rb +308 -0
  30. data/lib/tennpipes-base/path_router.rb +119 -0
  31. data/lib/tennpipes-base/path_router/compiler.rb +110 -0
  32. data/lib/tennpipes-base/path_router/error_handler.rb +8 -0
  33. data/lib/tennpipes-base/path_router/matcher.rb +123 -0
  34. data/lib/tennpipes-base/path_router/route.rb +169 -0
  35. data/lib/tennpipes-base/reloader.rb +309 -0
  36. data/lib/tennpipes-base/reloader/rack.rb +26 -0
  37. data/lib/tennpipes-base/reloader/storage.rb +55 -0
  38. data/lib/tennpipes-base/router.rb +98 -0
  39. data/lib/tennpipes-base/server.rb +119 -0
  40. data/lib/tennpipes-base/tasks.rb +21 -0
  41. data/lib/tennpipes-base/version.rb +20 -0
  42. data/lib/tennpipes-base/version.rb~ +20 -0
  43. data/test/fixtures/app_gem/Gemfile +4 -0
  44. data/test/fixtures/app_gem/app/app.rb +3 -0
  45. data/test/fixtures/app_gem/app_gem.gemspec +17 -0
  46. data/test/fixtures/app_gem/lib/app_gem.rb +7 -0
  47. data/test/fixtures/app_gem/lib/app_gem/version.rb +3 -0
  48. data/test/fixtures/apps/complex.rb +32 -0
  49. data/test/fixtures/apps/demo_app.rb +7 -0
  50. data/test/fixtures/apps/demo_demo.rb +7 -0
  51. data/test/fixtures/apps/demo_project/api/app.rb +7 -0
  52. data/test/fixtures/apps/demo_project/api/lib/api_lib.rb +3 -0
  53. data/test/fixtures/apps/demo_project/app.rb +7 -0
  54. data/test/fixtures/apps/external_apps/fake_lib.rb +1 -0
  55. data/test/fixtures/apps/external_apps/fake_root.rb +2 -0
  56. data/test/fixtures/apps/helpers/class_methods_helpers.rb +4 -0
  57. data/test/fixtures/apps/helpers/instance_methods_helpers.rb +4 -0
  58. data/test/fixtures/apps/helpers/support.rb +1 -0
  59. data/test/fixtures/apps/helpers/system_helpers.rb +8 -0
  60. data/test/fixtures/apps/kiq.rb +3 -0
  61. data/test/fixtures/apps/lib/myklass.rb +2 -0
  62. data/test/fixtures/apps/lib/myklass/mysubklass.rb +4 -0
  63. data/test/fixtures/apps/models/child.rb +2 -0
  64. data/test/fixtures/apps/models/parent.rb +5 -0
  65. data/test/fixtures/apps/mountable_apps/rack_apps.rb +15 -0
  66. data/test/fixtures/apps/mountable_apps/static.html +1 -0
  67. data/test/fixtures/apps/precompiled_app.rb +19 -0
  68. data/test/fixtures/apps/simple.rb +32 -0
  69. data/test/fixtures/apps/static.rb +10 -0
  70. data/test/fixtures/apps/system.rb +13 -0
  71. data/test/fixtures/apps/system_class_methods_demo.rb +7 -0
  72. data/test/fixtures/apps/system_instance_methods_demo.rb +7 -0
  73. data/test/fixtures/dependencies/a.rb +9 -0
  74. data/test/fixtures/dependencies/b.rb +4 -0
  75. data/test/fixtures/dependencies/c.rb +1 -0
  76. data/test/fixtures/dependencies/circular/e.rb +13 -0
  77. data/test/fixtures/dependencies/circular/f.rb +2 -0
  78. data/test/fixtures/dependencies/circular/g.rb +2 -0
  79. data/test/fixtures/dependencies/d.rb +4 -0
  80. data/test/fixtures/reloadable_apps/external/app/app.rb +6 -0
  81. data/test/fixtures/reloadable_apps/external/app/controllers/base.rb +6 -0
  82. data/test/fixtures/reloadable_apps/main/app.rb +10 -0
  83. data/test/helper.rb +30 -0
  84. data/test/test_application.rb +185 -0
  85. data/test/test_core.rb +93 -0
  86. data/test/test_csrf_protection.rb +208 -0
  87. data/test/test_dependencies.rb +57 -0
  88. data/test/test_filters.rb +389 -0
  89. data/test/test_flash.rb +168 -0
  90. data/test/test_locale.rb +21 -0
  91. data/test/test_logger.rb +295 -0
  92. data/test/test_mounter.rb +302 -0
  93. data/test/test_params_protection.rb +195 -0
  94. data/test/test_reloader_complex.rb +74 -0
  95. data/test/test_reloader_external.rb +21 -0
  96. data/test/test_reloader_simple.rb +101 -0
  97. data/test/test_reloader_system.rb +113 -0
  98. data/test/test_restful_routing.rb +33 -0
  99. data/test/test_router.rb +281 -0
  100. data/test/test_routing.rb +2328 -0
  101. metadata +301 -0
@@ -0,0 +1,110 @@
1
+ module Tennpipes
2
+ module PathRouter
3
+ #
4
+ # High performance engine for finding all routes which are matched with pattern
5
+ #
6
+ class Compiler
7
+ # All regexps generated by recursive compiler
8
+ attr_reader :regexps
9
+
10
+ ##
11
+ # Constructs an instance of Tennpipes::PathRouter::Compiler
12
+ #
13
+ def initialize(routes)
14
+ @routes = routes
15
+ end
16
+
17
+ ##
18
+ # Compiles all routes into regexps.
19
+ #
20
+ def compile!
21
+ return if compiled?
22
+ @regexps = @routes.map.with_index do |route, index|
23
+ route.index = index
24
+ /(?<_#{index}>#{route.matcher.to_regexp})/
25
+ end
26
+ @regexps = recursive_compile(@regexps)
27
+ @compiled = true
28
+ end
29
+
30
+ ##
31
+ # Returns true if all routes has been compiled.
32
+ #
33
+ def compiled?
34
+ !!@compiled
35
+ end
36
+
37
+ ##
38
+ # Finds routes by using request or env.
39
+ #
40
+ def find_by(request_or_env)
41
+ request = request_or_env.is_a?(Hash) ? Sinatra::Request.new(request_or_env) : request_or_env
42
+ pattern = encode_default_external(request.path_info)
43
+ verb = request.request_method
44
+ rotation { |offset| match?(offset, pattern) }.select { |route| route.verb == verb }
45
+ end
46
+
47
+ ##
48
+ # Calls routes by using request.
49
+ #
50
+ def call_by_request(request)
51
+ rotation do |offset|
52
+ pattern = encode_default_external(request.path_info)
53
+ if route = match?(offset, pattern)
54
+ params = route.params_for(pattern, request.params)
55
+ yield(route, params) if route.verb == request.request_method
56
+ route
57
+ end
58
+ end
59
+ end
60
+
61
+ ##
62
+ # Finds routes by using PATH_INFO.
63
+ #
64
+ def find_by_pattern(pattern)
65
+ pattern = pattern.encode(Encoding.default_external)
66
+ rotation { |offset| match?(offset, pattern) }
67
+ end
68
+
69
+ private
70
+
71
+ ##
72
+ # Returns a instance of PathRouter::Route if path is matched with current regexp
73
+ #
74
+ def match?(offset, path)
75
+ current_regexp = @regexps[offset]
76
+ return unless current_regexp === path || current_regexp === path[0..-2]
77
+ @routes[offset..-1].detect{ |route| Regexp.last_match["_#{route.index}"] }
78
+ end
79
+
80
+ ##
81
+ # Runs through all regexps to find routes.
82
+ #
83
+ def rotation(offset = 0)
84
+ compile! unless compiled?
85
+ loop.with_object([]) do |_, candidacies|
86
+ return candidacies unless route = yield(offset)
87
+ candidacies << route
88
+ offset = route.index.next
89
+ end
90
+ end
91
+
92
+ ##
93
+ # Compiles routes into regexp recursively.
94
+ #
95
+ def recursive_compile(regexps, paths = [])
96
+ return paths if regexps.length.zero?
97
+ paths << Regexp.union(regexps)
98
+ regexps.shift
99
+ recursive_compile(regexps, paths)
100
+ end
101
+
102
+ ##
103
+ # Encode string with Encoding.default_external
104
+ #
105
+ def encode_default_external(string)
106
+ string.encode(Encoding.default_external)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,8 @@
1
+ module Tennpipes
2
+ module PathRouter
3
+ ##
4
+ # @see PathRouter::Router#path
5
+ #
6
+ InvalidRouteException = Class.new(ArgumentError)
7
+ end
8
+ end
@@ -0,0 +1,123 @@
1
+ require 'mustermann/sinatra'
2
+
3
+ module Tennpipes
4
+ module PathRouter
5
+ class Matcher
6
+ # To count group of regexp
7
+ GROUP_REGEXP = %r{\((?!\?:|\?!|\?<=|\?<!|\?=).+?\)}.freeze
8
+
9
+ ##
10
+ # Constructs an instance of PathRouter::Matcher.
11
+ #
12
+ def initialize(path, options = {})
13
+ @path = path.is_a?(String) && path.empty? ? "/" : path
14
+ @capture = options[:capture]
15
+ @default_values = options[:default_values]
16
+ end
17
+
18
+ ##
19
+ # Matches a pattern with the route matcher.
20
+ #
21
+ def match(pattern)
22
+ if match_data = handler.match(pattern)
23
+ match_data
24
+ elsif mustermann? && pattern != "/" && pattern.end_with?("/")
25
+ handler.match(pattern[0..-2])
26
+ end
27
+ end
28
+
29
+ ##
30
+ # Returns a regexp from handler.
31
+ #
32
+ def to_regexp
33
+ mustermann? ? handler.to_regexp : handler
34
+ end
35
+
36
+ ##
37
+ # Expands the path by using parameters.
38
+ #
39
+ def expand(params)
40
+ params = params.merge(@default_values) if @default_values.is_a?(Hash)
41
+ expanded_path = handler.expand(:append, params)
42
+ expanded_path
43
+ end
44
+
45
+ ##
46
+ # Returns true if handler is an instance of Mustermann.
47
+ #
48
+ def mustermann?
49
+ handler.instance_of?(Mustermann::Sinatra)
50
+ end
51
+
52
+ ##
53
+ # Builds a parameters, and returns them.
54
+ #
55
+ def params_for(pattern, others)
56
+ data = match(pattern)
57
+ params = indifferent_hash
58
+ if data.names.empty?
59
+ params.merge!(:captures => data.captures) unless data.captures.empty?
60
+ else
61
+ if mustermann?
62
+ new_params = handler.params(pattern, :captures => data)
63
+ params.merge!(new_params) if new_params
64
+ elsif data
65
+ params.merge!(Hash[names.zip(data.captures)])
66
+ end
67
+ params.merge!(others){ |_, old, new| old || new }
68
+ end
69
+ params
70
+ end
71
+
72
+ ##
73
+ # Returns the handler which is an instance of Mustermann or Regexp.
74
+ #
75
+ def handler
76
+ @handler ||=
77
+ case @path
78
+ when String
79
+ Mustermann.new(@path, :capture => @capture)
80
+ when Regexp
81
+ /^(?:#{@path})$/
82
+ else
83
+ @path
84
+ end
85
+ end
86
+
87
+ ##
88
+ # Converts the handler into string.
89
+ #
90
+ def to_s
91
+ handler.to_s
92
+ end
93
+
94
+ ##
95
+ # Returns names of the handler.
96
+ # @see Regexp#names
97
+ #
98
+ def names
99
+ handler.names
100
+ end
101
+
102
+ ##
103
+ # Returns captures parameter length.
104
+ #
105
+ def capture_length
106
+ if mustermann?
107
+ handler.named_captures.inject(0) { |count, (_, capture)| count += capture.length }
108
+ else
109
+ handler.inspect.scan(GROUP_REGEXP).length
110
+ end
111
+ end
112
+
113
+ private
114
+
115
+ ##
116
+ # Creates a hash with indifferent access.
117
+ #
118
+ def indifferent_hash
119
+ Hash.new{ |hash, key| hash[key.to_s] if key.instance_of?(Symbol) }
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,169 @@
1
+ module Tennpipes
2
+ module PathRouter
3
+ class Route
4
+ ##
5
+ # The accessors are useful to access from PathRouter::Router
6
+ #
7
+ attr_accessor :name, :capture, :order, :options, :index
8
+
9
+ ##
10
+ # A reader for compile option
11
+ #
12
+ attr_reader :verb, :block
13
+
14
+ ##
15
+ # The router will be treated in this class
16
+ #
17
+ attr_writer :router
18
+
19
+ ##
20
+ # The accessors will be used in other classes
21
+ #
22
+ attr_accessor :action, :cache, :cache_key, :cache_expires,
23
+ :parent, :use_layout, :controller, :user_agent, :path_for_generation, :default_values
24
+
25
+ ##
26
+ # Constructs an instance of PathRouter::Route.
27
+ #
28
+ def initialize(path, verb, options = {}, &block)
29
+ @path = path
30
+ @verb = verb
31
+ @capture = {}
32
+ @order = 0
33
+ @block = block if block_given?
34
+ merge_with_options!(options)
35
+ end
36
+
37
+ ##
38
+ # Calls the route block with arguments.
39
+ #
40
+ def call(app, *args)
41
+ @block.call(app, *args)
42
+ end
43
+
44
+ ##
45
+ # Returns the route's verb as an array.
46
+ #
47
+ def request_methods
48
+ [verb.to_s.upcase]
49
+ end
50
+
51
+ ##
52
+ # Returns the original path.
53
+ #
54
+ def original_path
55
+ @path
56
+ end
57
+
58
+ SIGNIFICANT_VARIABLES_REGEX = /(^|[^\\])[:\*]([a-zA-Z0-9_]+)/.freeze
59
+
60
+ ##
61
+ # Returns signficant variable names.
62
+ #
63
+ def significant_variable_names
64
+ @significant_variable_names ||=
65
+ if @path.is_a?(String)
66
+ @path.scan(SIGNIFICANT_VARIABLES_REGEX).map{ |p| p.last.to_sym }
67
+ elsif @path.is_a?(Regexp) and @path.respond_to?(:named_captures)
68
+ @path.named_captures.keys.map(&:to_sym)
69
+ else
70
+ []
71
+ end
72
+ end
73
+
74
+ ##
75
+ # Returns an instance of PathRouter::Matcher that is associated with the route.
76
+ #
77
+ def matcher
78
+ @matcher ||= Matcher.new(@path, :capture => @capture, :default_values => default_values)
79
+ end
80
+
81
+ ##
82
+ # @see PathRouter::Matcher#match
83
+ #
84
+ def match(pattern)
85
+ matcher.match(pattern)
86
+ end
87
+
88
+ ##
89
+ # Associates a block with the route, and increments current order of the router.
90
+ #
91
+ def to(&block)
92
+ @block = block if block_given?
93
+ @order = @router.current_order
94
+ @router.increment_order
95
+ end
96
+
97
+ ##
98
+ # Expands the path by using parameters.
99
+ # @see PathRouter::Matcher#expand
100
+ #
101
+ def path(*args)
102
+ return @path if args.empty?
103
+ params = args[0].dup
104
+ params.delete(:captures)
105
+ matcher.expand(params) if matcher.mustermann?
106
+ end
107
+
108
+ ##
109
+ # Returns parameters which is created by the matcher.
110
+ #
111
+ def params_for(pattern, others = {})
112
+ matcher.params_for(pattern, others)
113
+ end
114
+
115
+ ##
116
+ # Returns before_filters as an array.
117
+ #
118
+ def before_filters(&block)
119
+ @_before_filters ||= []
120
+ @_before_filters << block if block_given?
121
+ @_before_filters
122
+ end
123
+
124
+ ##
125
+ # Returns after_filters as an array.
126
+ #
127
+ def after_filters(&block)
128
+ @_after_filters ||= []
129
+ @_after_filters << block if block_given?
130
+ @_after_filters
131
+ end
132
+
133
+ ##
134
+ # Returns custom_conditions as an array.
135
+ #
136
+ def custom_conditions(&block)
137
+ @_custom_conditions ||= []
138
+ @_custom_conditions << block if block_given?
139
+ @_custom_conditions
140
+ end
141
+
142
+ ##
143
+ # Returns block parameter length.
144
+ #
145
+ def block_parameter_length
146
+ matcher.capture_length
147
+ end
148
+
149
+ private
150
+
151
+ ##
152
+ # Set value to accessor if option name has been defined as an accessora.
153
+ #
154
+ def merge_with_options!(options)
155
+ @options ||= {}
156
+ options.each_pair do |key, value|
157
+ accessor?(key) ? __send__("#{key}=", value) : (@options[key] = value)
158
+ end
159
+ end
160
+
161
+ ##
162
+ # Returns true if name has been defined as an accessor.
163
+ #
164
+ def accessor?(key)
165
+ respond_to?("#{key}=") && respond_to?(key)
166
+ end
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,309 @@
1
+ require 'pathname'
2
+ require 'tennpipes-base/reloader/rack'
3
+ require 'tennpipes-base/reloader/storage'
4
+
5
+ module Tennpipes
6
+ ##
7
+ # High performance source code reloader middleware
8
+ #
9
+ module Reloader
10
+ ##
11
+ # This reloader is suited for use in a many environments because each file
12
+ # will only be checked once and only one system call to stat(2) is made.
13
+ #
14
+ # Please note that this will not reload files in the background, and does so
15
+ # only when explicitly invoked.
16
+ #
17
+ extend self
18
+
19
+ # The modification times for every file in a project.
20
+ MTIMES = {}
21
+
22
+ ##
23
+ # Specified folders can be excluded from the code reload detection process.
24
+ # Default excluded directories at Tennpipes.root are: test, spec, features, tmp, config, db and public
25
+ #
26
+ def exclude
27
+ @_exclude ||= Set.new %w(test spec tmp features config public db).map{ |path| Tennpipes.root(path) }
28
+ end
29
+
30
+ ##
31
+ # Specified constants can be excluded from the code unloading process.
32
+ #
33
+ def exclude_constants
34
+ @_exclude_constants ||= Set.new
35
+ end
36
+
37
+ ##
38
+ # Specified constants can be configured to be reloaded on every request.
39
+ # Default included constants are: [none]
40
+ #
41
+ def include_constants
42
+ @_include_constants ||= Set.new
43
+ end
44
+
45
+ ##
46
+ # Reload apps and files with changes detected.
47
+ #
48
+ def reload!
49
+ rotation do |file|
50
+ next unless file_changed?(file)
51
+ reload_special(file) || reload_regular(file)
52
+ end
53
+ end
54
+
55
+ ##
56
+ # Remove files and classes loaded with stat
57
+ #
58
+ def clear!
59
+ MTIMES.clear
60
+ Storage.clear!
61
+ end
62
+
63
+ ##
64
+ # Returns true if any file changes are detected.
65
+ #
66
+ def changed?
67
+ rotation do |file|
68
+ break true if file_changed?(file)
69
+ end
70
+ end
71
+
72
+ ##
73
+ # We lock dependencies sets to prevent reloading of protected constants
74
+ #
75
+ def lock!
76
+ klasses = ObjectSpace.classes do |klass|
77
+ klass._orig_klass_name.split('::').first
78
+ end
79
+ klasses |= Tennpipes.mounted_apps.map(&:app_class)
80
+ exclude_constants.merge(klasses)
81
+ end
82
+
83
+ ##
84
+ # A safe Kernel::require which issues the necessary hooks depending on results
85
+ #
86
+ def safe_load(file, options={})
87
+ began_at = Time.now
88
+ file = figure_path(file)
89
+ return unless options[:force] || file_changed?(file)
90
+ return require(file) if feature_excluded?(file)
91
+
92
+ Storage.prepare(file) # might call #safe_load recursively
93
+ logger.devel(file_new?(file) ? :loading : :reload, began_at, file)
94
+ begin
95
+ with_silence{ require(file) }
96
+ Storage.commit(file)
97
+ update_modification_time(file)
98
+ rescue Exception => exception
99
+ unless options[:cyclic]
100
+ logger.exception exception, :short
101
+ logger.error "Failed to load #{file}; removing partially defined constants"
102
+ end
103
+ Storage.rollback(file)
104
+ raise
105
+ end
106
+ end
107
+
108
+ ##
109
+ # Removes the specified class and constant.
110
+ #
111
+ def remove_constant(const)
112
+ return if constant_excluded?(const)
113
+ base, _, object = const.to_s.rpartition('::')
114
+ base = base.empty? ? Object : base.constantize
115
+ base.send :remove_const, object
116
+ logger.devel "Removed constant #{const} from #{base}"
117
+ rescue NameError
118
+ end
119
+
120
+ ##
121
+ # Remove a feature from $LOADED_FEATURES so it can be required again.
122
+ #
123
+ def remove_feature(file)
124
+ $LOADED_FEATURES.delete(file) unless feature_excluded?(file)
125
+ end
126
+
127
+ ##
128
+ # Returns the list of special tracked files for Reloader.
129
+ #
130
+ def special_files
131
+ @special_files ||= Set.new
132
+ end
133
+
134
+ ##
135
+ # Sets the list of special tracked files for Reloader.
136
+ #
137
+ def special_files=(files)
138
+ @special_files = Set.new(files)
139
+ end
140
+
141
+ private
142
+
143
+ ##
144
+ # Returns absolute path of the file.
145
+ #
146
+ def figure_path(file)
147
+ return file if Pathname.new(file).absolute?
148
+ $LOAD_PATH.each do |path|
149
+ found = File.join(path, file)
150
+ return File.expand_path(found) if File.file?(found)
151
+ end
152
+ file
153
+ end
154
+
155
+ ##
156
+ # Reloads the file if it's special. For now it's only I18n locale files.
157
+ #
158
+ def reload_special(file)
159
+ return unless special_files.any?{ |special_file| File.identical?(special_file, file) }
160
+ if defined?(I18n)
161
+ began_at = Time.now
162
+ I18n.reload!
163
+ update_modification_time(file)
164
+ logger.devel :reload, began_at, file
165
+ end
166
+ true
167
+ end
168
+
169
+ ##
170
+ # Reloads ruby file and applications dependent on it.
171
+ #
172
+ def reload_regular(file)
173
+ apps = mounted_apps_of(file)
174
+ if apps.empty?
175
+ reloadable_apps.each do |app|
176
+ app.app_obj.reload! if app.app_obj.dependencies.include?(file)
177
+ end
178
+ safe_load(file)
179
+ else
180
+ apps.each { |app| app.app_obj.reload! }
181
+ update_modification_time(file)
182
+ end
183
+ end
184
+
185
+ ###
186
+ # Macro for mtime update.
187
+ #
188
+ def update_modification_time(file)
189
+ MTIMES[file] = File.mtime(file)
190
+ end
191
+
192
+ ###
193
+ # Returns true if the file is new or it's modification time changed.
194
+ #
195
+ def file_changed?(file)
196
+ file_new?(file) || File.mtime(file) > MTIMES[file]
197
+ end
198
+
199
+ ###
200
+ # Returns true if the file is new.
201
+ #
202
+ def file_new?(file)
203
+ MTIMES[file].nil?
204
+ end
205
+
206
+ ##
207
+ # Searches Ruby files in your +Tennpipes.load_paths+ , Tennpipes::Application.load_paths
208
+ # and monitors them for any changes.
209
+ #
210
+ def rotation
211
+ files_for_rotation.each do |file|
212
+ file = File.expand_path(file)
213
+ next if Reloader.exclude.any? { |base| file.start_with?(base) } || !File.file?(file)
214
+ yield file
215
+ end
216
+ nil
217
+ end
218
+
219
+ ##
220
+ # Creates an array of paths for use in #rotation.
221
+ #
222
+ def files_for_rotation
223
+ files = Set.new
224
+ files += Dir.glob("#{Tennpipes.root}/{lib,models,shared}/**/*.rb")
225
+ reloadable_apps.each do |app|
226
+ files << app.app_file
227
+ files += Dir.glob(app.app_obj.prerequisites)
228
+ files += app.app_obj.dependencies
229
+ end
230
+ files + special_files
231
+ end
232
+
233
+ ##
234
+ # Tells if a feature should be excluded from Reloader tracking.
235
+ #
236
+ def feature_excluded?(file)
237
+ !file.start_with?(Tennpipes.root) || exclude.any?{ |excluded_path| file.start_with?(excluded_path) }
238
+ end
239
+
240
+ ##
241
+ # Tells if a constant should be excluded from Reloader routines.
242
+ #
243
+ def constant_excluded?(const)
244
+ external_constant?(const) || (exclude_constants - include_constants).any?{ |excluded_constant| const._orig_klass_name.start_with?(excluded_constant) }
245
+ end
246
+
247
+ ##
248
+ # Tells if a constant is defined only outside of Tennpipes project path.
249
+ # If a constant has any methods defined inside of the project path it's
250
+ # considered internal and will be included in further testing.
251
+ #
252
+ def external_constant?(const)
253
+ sources = object_sources(const)
254
+ begin
255
+ if sample = ObjectSpace.each_object(const).first
256
+ sources += object_sources(sample)
257
+ end
258
+ rescue RuntimeError => error # JRuby 1.7.12 fails to ObjectSpace.each_object
259
+ raise unless RUBY_PLATFORM =='java' && error.message.start_with?("ObjectSpace is disabled")
260
+ end
261
+ !sources.any?{ |source| source.start_with?(Tennpipes.root) }
262
+ end
263
+
264
+ ##
265
+ # Gets all the sources in which target's class or instance methods are defined.
266
+ #
267
+ # Note: Method#source_location is for Ruby 1.9.3+ only.
268
+ #
269
+ def object_sources(target)
270
+ sources = Set.new
271
+ target.methods.each do |method_name|
272
+ next unless method_name.kind_of?(Symbol)
273
+ method_object = target.method(method_name)
274
+ if method_object.owner == (target.class == Class ? target.singleton_class : target.class)
275
+ sources << method_object.source_location.first
276
+ end
277
+ end
278
+ sources
279
+ end
280
+
281
+ ##
282
+ # Return the mounted_apps providing the app location.
283
+ # Can be an array because in one app.rb we can define multiple Tennpipes::Application.
284
+ #
285
+ def mounted_apps_of(file)
286
+ Tennpipes.mounted_apps.select { |app| File.identical?(file, app.app_file) }
287
+ end
288
+
289
+ ##
290
+ # Return the apps that allow reloading.
291
+ #
292
+ def reloadable_apps
293
+ Tennpipes.mounted_apps.select do |app|
294
+ next unless app.app_file.start_with?(Tennpipes.root)
295
+ app.app_obj.respond_to?(:reload) && app.app_obj.reload?
296
+ end
297
+ end
298
+
299
+ ##
300
+ # Disables output, yields block, switches output back.
301
+ #
302
+ def with_silence
303
+ verbosity_level, $-v = $-v, nil
304
+ yield
305
+ ensure
306
+ $-v = verbosity_level
307
+ end
308
+ end
309
+ end