tennpipes-base 3.6.6

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