ultra-lite-hub 0.0.1

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 (162) hide show
  1. checksums.yaml +7 -0
  2. data/roda-3.105.0/MIT-LICENSE +21 -0
  3. data/roda-3.105.0/lib/roda/cache.rb +42 -0
  4. data/roda-3.105.0/lib/roda/plugins/Integer_matcher_max.rb +55 -0
  5. data/roda-3.105.0/lib/roda/plugins/_after_hook.rb +11 -0
  6. data/roda-3.105.0/lib/roda/plugins/_base64.rb +34 -0
  7. data/roda-3.105.0/lib/roda/plugins/_before_hook.rb +41 -0
  8. data/roda-3.105.0/lib/roda/plugins/_optimized_matching.rb +227 -0
  9. data/roda-3.105.0/lib/roda/plugins/_symbol_class_matchers.rb +108 -0
  10. data/roda-3.105.0/lib/roda/plugins/_symbol_regexp_matchers.rb +24 -0
  11. data/roda-3.105.0/lib/roda/plugins/additional_render_engines.rb +61 -0
  12. data/roda-3.105.0/lib/roda/plugins/additional_view_directories.rb +66 -0
  13. data/roda-3.105.0/lib/roda/plugins/all_verbs.rb +50 -0
  14. data/roda-3.105.0/lib/roda/plugins/assets.rb +902 -0
  15. data/roda-3.105.0/lib/roda/plugins/assets_preloading.rb +89 -0
  16. data/roda-3.105.0/lib/roda/plugins/assume_ssl.rb +28 -0
  17. data/roda-3.105.0/lib/roda/plugins/autoload_hash_branches.rb +79 -0
  18. data/roda-3.105.0/lib/roda/plugins/autoload_named_routes.rb +65 -0
  19. data/roda-3.105.0/lib/roda/plugins/backtracking_array.rb +88 -0
  20. data/roda-3.105.0/lib/roda/plugins/bearer_token.rb +28 -0
  21. data/roda-3.105.0/lib/roda/plugins/branch_locals.rb +74 -0
  22. data/roda-3.105.0/lib/roda/plugins/break.rb +43 -0
  23. data/roda-3.105.0/lib/roda/plugins/caching.rb +206 -0
  24. data/roda-3.105.0/lib/roda/plugins/capture_erb.rb +94 -0
  25. data/roda-3.105.0/lib/roda/plugins/chunked.rb +331 -0
  26. data/roda-3.105.0/lib/roda/plugins/class_level_routing.rb +111 -0
  27. data/roda-3.105.0/lib/roda/plugins/class_matchers.rb +155 -0
  28. data/roda-3.105.0/lib/roda/plugins/common_logger.rb +80 -0
  29. data/roda-3.105.0/lib/roda/plugins/conditional_sessions.rb +67 -0
  30. data/roda-3.105.0/lib/roda/plugins/content_for.rb +103 -0
  31. data/roda-3.105.0/lib/roda/plugins/content_security_policy.rb +329 -0
  32. data/roda-3.105.0/lib/roda/plugins/cookie_flags.rb +157 -0
  33. data/roda-3.105.0/lib/roda/plugins/cookies.rb +51 -0
  34. data/roda-3.105.0/lib/roda/plugins/csrf.rb +81 -0
  35. data/roda-3.105.0/lib/roda/plugins/custom_block_results.rb +85 -0
  36. data/roda-3.105.0/lib/roda/plugins/custom_matchers.rb +89 -0
  37. data/roda-3.105.0/lib/roda/plugins/default_headers.rb +64 -0
  38. data/roda-3.105.0/lib/roda/plugins/default_status.rb +35 -0
  39. data/roda-3.105.0/lib/roda/plugins/delay_build.rb +18 -0
  40. data/roda-3.105.0/lib/roda/plugins/delegate.rb +77 -0
  41. data/roda-3.105.0/lib/roda/plugins/delete_empty_headers.rb +36 -0
  42. data/roda-3.105.0/lib/roda/plugins/direct_call.rb +38 -0
  43. data/roda-3.105.0/lib/roda/plugins/disallow_file_uploads.rb +38 -0
  44. data/roda-3.105.0/lib/roda/plugins/drop_body.rb +43 -0
  45. data/roda-3.105.0/lib/roda/plugins/each_part.rb +87 -0
  46. data/roda-3.105.0/lib/roda/plugins/early_hints.rb +25 -0
  47. data/roda-3.105.0/lib/roda/plugins/empty_root.rb +49 -0
  48. data/roda-3.105.0/lib/roda/plugins/environments.rb +79 -0
  49. data/roda-3.105.0/lib/roda/plugins/erb_h.rb +43 -0
  50. data/roda-3.105.0/lib/roda/plugins/error_email.rb +152 -0
  51. data/roda-3.105.0/lib/roda/plugins/error_handler.rb +133 -0
  52. data/roda-3.105.0/lib/roda/plugins/error_mail.rb +149 -0
  53. data/roda-3.105.0/lib/roda/plugins/exception_page.rb +445 -0
  54. data/roda-3.105.0/lib/roda/plugins/filter_common_logger.rb +46 -0
  55. data/roda-3.105.0/lib/roda/plugins/flash.rb +117 -0
  56. data/roda-3.105.0/lib/roda/plugins/h.rb +59 -0
  57. data/roda-3.105.0/lib/roda/plugins/halt.rb +108 -0
  58. data/roda-3.105.0/lib/roda/plugins/hash_branch_view_subdir.rb +76 -0
  59. data/roda-3.105.0/lib/roda/plugins/hash_branches.rb +145 -0
  60. data/roda-3.105.0/lib/roda/plugins/hash_matcher.rb +39 -0
  61. data/roda-3.105.0/lib/roda/plugins/hash_paths.rb +128 -0
  62. data/roda-3.105.0/lib/roda/plugins/hash_public.rb +125 -0
  63. data/roda-3.105.0/lib/roda/plugins/hash_public_cache.rb +91 -0
  64. data/roda-3.105.0/lib/roda/plugins/hash_routes.rb +304 -0
  65. data/roda-3.105.0/lib/roda/plugins/head.rb +99 -0
  66. data/roda-3.105.0/lib/roda/plugins/header_matchers.rb +88 -0
  67. data/roda-3.105.0/lib/roda/plugins/heartbeat.rb +41 -0
  68. data/roda-3.105.0/lib/roda/plugins/hmac_paths.rb +413 -0
  69. data/roda-3.105.0/lib/roda/plugins/hooks.rb +92 -0
  70. data/roda-3.105.0/lib/roda/plugins/host_authorization.rb +155 -0
  71. data/roda-3.105.0/lib/roda/plugins/host_routing.rb +190 -0
  72. data/roda-3.105.0/lib/roda/plugins/hsts.rb +35 -0
  73. data/roda-3.105.0/lib/roda/plugins/indifferent_params.rb +124 -0
  74. data/roda-3.105.0/lib/roda/plugins/inject_erb.rb +33 -0
  75. data/roda-3.105.0/lib/roda/plugins/invalid_request_body.rb +107 -0
  76. data/roda-3.105.0/lib/roda/plugins/ip_from_header.rb +34 -0
  77. data/roda-3.105.0/lib/roda/plugins/json.rb +119 -0
  78. data/roda-3.105.0/lib/roda/plugins/json_parser.rb +107 -0
  79. data/roda-3.105.0/lib/roda/plugins/link_to.rb +83 -0
  80. data/roda-3.105.0/lib/roda/plugins/mail_processor.rb +629 -0
  81. data/roda-3.105.0/lib/roda/plugins/mailer.rb +283 -0
  82. data/roda-3.105.0/lib/roda/plugins/map_matcher.rb +48 -0
  83. data/roda-3.105.0/lib/roda/plugins/match_affix.rb +64 -0
  84. data/roda-3.105.0/lib/roda/plugins/match_hook.rb +34 -0
  85. data/roda-3.105.0/lib/roda/plugins/match_hook_args.rb +93 -0
  86. data/roda-3.105.0/lib/roda/plugins/middleware.rb +225 -0
  87. data/roda-3.105.0/lib/roda/plugins/middleware_stack.rb +101 -0
  88. data/roda-3.105.0/lib/roda/plugins/module_include.rb +96 -0
  89. data/roda-3.105.0/lib/roda/plugins/multi_public.rb +95 -0
  90. data/roda-3.105.0/lib/roda/plugins/multi_route.rb +156 -0
  91. data/roda-3.105.0/lib/roda/plugins/multi_run.rb +144 -0
  92. data/roda-3.105.0/lib/roda/plugins/multi_view.rb +83 -0
  93. data/roda-3.105.0/lib/roda/plugins/multibyte_string_matcher.rb +57 -0
  94. data/roda-3.105.0/lib/roda/plugins/named_routes.rb +167 -0
  95. data/roda-3.105.0/lib/roda/plugins/named_templates.rb +102 -0
  96. data/roda-3.105.0/lib/roda/plugins/not_allowed.rb +149 -0
  97. data/roda-3.105.0/lib/roda/plugins/not_found.rb +55 -0
  98. data/roda-3.105.0/lib/roda/plugins/optimized_segment_matchers.rb +53 -0
  99. data/roda-3.105.0/lib/roda/plugins/optimized_string_matchers.rb +50 -0
  100. data/roda-3.105.0/lib/roda/plugins/padrino_render.rb +42 -0
  101. data/roda-3.105.0/lib/roda/plugins/param_matchers.rb +83 -0
  102. data/roda-3.105.0/lib/roda/plugins/params_capturing.rb +116 -0
  103. data/roda-3.105.0/lib/roda/plugins/part.rb +76 -0
  104. data/roda-3.105.0/lib/roda/plugins/partials.rb +74 -0
  105. data/roda-3.105.0/lib/roda/plugins/pass.rb +47 -0
  106. data/roda-3.105.0/lib/roda/plugins/path.rb +255 -0
  107. data/roda-3.105.0/lib/roda/plugins/path_matchers.rb +54 -0
  108. data/roda-3.105.0/lib/roda/plugins/path_rewriter.rb +111 -0
  109. data/roda-3.105.0/lib/roda/plugins/permissions_policy.rb +336 -0
  110. data/roda-3.105.0/lib/roda/plugins/placeholder_string_matchers.rb +48 -0
  111. data/roda-3.105.0/lib/roda/plugins/plain_hash_response_headers.rb +32 -0
  112. data/roda-3.105.0/lib/roda/plugins/precompile_templates.rb +156 -0
  113. data/roda-3.105.0/lib/roda/plugins/public.rb +182 -0
  114. data/roda-3.105.0/lib/roda/plugins/r.rb +35 -0
  115. data/roda-3.105.0/lib/roda/plugins/recheck_precompiled_assets.rb +107 -0
  116. data/roda-3.105.0/lib/roda/plugins/redirect_http_to_https.rb +99 -0
  117. data/roda-3.105.0/lib/roda/plugins/redirect_path.rb +59 -0
  118. data/roda-3.105.0/lib/roda/plugins/relative_path.rb +74 -0
  119. data/roda-3.105.0/lib/roda/plugins/render.rb +998 -0
  120. data/roda-3.105.0/lib/roda/plugins/render_coverage.rb +105 -0
  121. data/roda-3.105.0/lib/roda/plugins/render_each.rb +224 -0
  122. data/roda-3.105.0/lib/roda/plugins/render_locals.rb +102 -0
  123. data/roda-3.105.0/lib/roda/plugins/request_aref.rb +77 -0
  124. data/roda-3.105.0/lib/roda/plugins/request_headers.rb +81 -0
  125. data/roda-3.105.0/lib/roda/plugins/response_attachment.rb +94 -0
  126. data/roda-3.105.0/lib/roda/plugins/response_content_type.rb +79 -0
  127. data/roda-3.105.0/lib/roda/plugins/response_request.rb +29 -0
  128. data/roda-3.105.0/lib/roda/plugins/route_block_args.rb +50 -0
  129. data/roda-3.105.0/lib/roda/plugins/route_csrf.rb +388 -0
  130. data/roda-3.105.0/lib/roda/plugins/run_append_slash.rb +51 -0
  131. data/roda-3.105.0/lib/roda/plugins/run_handler.rb +51 -0
  132. data/roda-3.105.0/lib/roda/plugins/run_require_slash.rb +46 -0
  133. data/roda-3.105.0/lib/roda/plugins/sec_fetch_site_csrf.rb +131 -0
  134. data/roda-3.105.0/lib/roda/plugins/send_file.rb +127 -0
  135. data/roda-3.105.0/lib/roda/plugins/sessions.rb +537 -0
  136. data/roda-3.105.0/lib/roda/plugins/shared_vars.rb +86 -0
  137. data/roda-3.105.0/lib/roda/plugins/sinatra_helpers.rb +443 -0
  138. data/roda-3.105.0/lib/roda/plugins/slash_path_empty.rb +26 -0
  139. data/roda-3.105.0/lib/roda/plugins/static.rb +42 -0
  140. data/roda-3.105.0/lib/roda/plugins/static_routing.rb +89 -0
  141. data/roda-3.105.0/lib/roda/plugins/status_303.rb +34 -0
  142. data/roda-3.105.0/lib/roda/plugins/status_handler.rb +86 -0
  143. data/roda-3.105.0/lib/roda/plugins/streaming.rb +170 -0
  144. data/roda-3.105.0/lib/roda/plugins/strip_path_prefix.rb +33 -0
  145. data/roda-3.105.0/lib/roda/plugins/symbol_matchers.rb +188 -0
  146. data/roda-3.105.0/lib/roda/plugins/symbol_status.rb +32 -0
  147. data/roda-3.105.0/lib/roda/plugins/symbol_views.rb +37 -0
  148. data/roda-3.105.0/lib/roda/plugins/timestamp_public.rb +78 -0
  149. data/roda-3.105.0/lib/roda/plugins/type_routing.rb +215 -0
  150. data/roda-3.105.0/lib/roda/plugins/typecast_params.rb +1183 -0
  151. data/roda-3.105.0/lib/roda/plugins/typecast_params_sized_integers.rb +108 -0
  152. data/roda-3.105.0/lib/roda/plugins/unescape_path.rb +41 -0
  153. data/roda-3.105.0/lib/roda/plugins/view_options.rb +201 -0
  154. data/roda-3.105.0/lib/roda/plugins/view_subdir_leading_slash.rb +49 -0
  155. data/roda-3.105.0/lib/roda/plugins.rb +68 -0
  156. data/roda-3.105.0/lib/roda/request.rb +722 -0
  157. data/roda-3.105.0/lib/roda/response.rb +221 -0
  158. data/roda-3.105.0/lib/roda/session_middleware.rb +183 -0
  159. data/roda-3.105.0/lib/roda/version.rb +18 -0
  160. data/roda-3.105.0/lib/roda.rb +586 -0
  161. data/ultra-lite-hub.gemspec +12 -0
  162. metadata +201 -0
@@ -0,0 +1,86 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The status_handler plugin adds a +status_handler+ method which sets a
7
+ # block that is called whenever a response with the relevant response code
8
+ # with an empty body would be returned.
9
+ #
10
+ # This plugin does not support providing the blocks with the plugin call;
11
+ # you must provide them to status_handler calls afterwards:
12
+ #
13
+ # plugin :status_handler
14
+ #
15
+ # status_handler(403) do
16
+ # "You are forbidden from seeing that!"
17
+ # end
18
+ #
19
+ # status_handler(404) do
20
+ # "Where did it go?"
21
+ # end
22
+ #
23
+ # status_handler(405, keep_headers: ['Accept']) do
24
+ # "Use a different method!"
25
+ # end
26
+ #
27
+ # Before a block is called, any existing headers on the response will be
28
+ # cleared, unless the +:keep_headers+ option is used. If the +:keep_headers+
29
+ # option is used, the value should be an array, and only the headers listed
30
+ # in the array will be kept.
31
+ module StatusHandler
32
+ CLEAR_HEADERS = :clear.to_proc
33
+ private_constant :CLEAR_HEADERS
34
+
35
+ def self.configure(app)
36
+ app.opts[:status_handler] ||= {}
37
+ end
38
+
39
+ module ClassMethods
40
+ # Install the given block as a status handler for the given HTTP response code.
41
+ def status_handler(code, opts=OPTS, &block)
42
+ # For backwards compatibility, pass request argument if block accepts argument
43
+ arity = block.arity == 0 ? 0 : 1
44
+ handle_headers = case keep_headers = opts[:keep_headers]
45
+ when nil, false
46
+ CLEAR_HEADERS
47
+ when Array
48
+ if Rack.release >= '3'
49
+ keep_headers = keep_headers.map(&:downcase)
50
+ end
51
+ lambda{|headers| headers.delete_if{|k,_| !keep_headers.include?(k)}}
52
+ else
53
+ raise RodaError, "Invalid :keep_headers option"
54
+ end
55
+
56
+ meth = define_roda_method(:"_roda_status_handler__#{code}", arity, &block)
57
+ self.opts[:status_handler][code] = define_roda_method(:"_roda_status_handler_#{code}", 1) do |result|
58
+ res = @_response
59
+ res.status = result[0]
60
+ handle_headers.call(res.headers)
61
+ result.replace(_roda_handle_route{arity == 1 ? send(meth, @_request) : send(meth)})
62
+ end
63
+ end
64
+
65
+ # Freeze the hash of status handlers so that there can be no thread safety issues at runtime.
66
+ def freeze
67
+ opts[:status_handler].freeze
68
+ super
69
+ end
70
+ end
71
+
72
+ module InstanceMethods
73
+ private
74
+
75
+ # If routing returns a response we have a handler for, call that handler.
76
+ def _roda_after_20__status_handler(result)
77
+ if result && (meth = opts[:status_handler][result[0]]) && (v = result[2]).is_a?(Array) && v.empty?
78
+ send(meth, result)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ register_plugin(:status_handler, StatusHandler)
85
+ end
86
+ end
@@ -0,0 +1,170 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The streaming plugin adds support for streaming responses
7
+ # from roda using the +stream+ method:
8
+ #
9
+ # plugin :streaming
10
+ #
11
+ # route do |r|
12
+ # stream do |out|
13
+ # ['a', 'b', 'c'].each{|v| out << v; sleep 1}
14
+ # end
15
+ # end
16
+ #
17
+ # In order for streaming to work, any webservers used in
18
+ # front of the roda app must not buffer responses.
19
+ #
20
+ # The stream method takes the following options:
21
+ #
22
+ # :callback :: A callback proc to call when the connection is closed.
23
+ # :loop :: Whether to call the stream block continuously until the connection is closed.
24
+ # :async :: Whether to call the stream block in a separate thread (default: false). Only supported on Ruby 2.3+.
25
+ # :queue :: A queue object to use for asynchronous streaming (default: `SizedQueue.new(10)`).
26
+ #
27
+ # If the :loop option is used, you can override the
28
+ # handle_stream_error method to change how exceptions
29
+ # are handled during streaming. This method is passed the
30
+ # exception and the stream. By default, this method
31
+ # just reraises the exception, but you can choose to output
32
+ # the an error message to the stream, before raising:
33
+ #
34
+ # def handle_stream_error(e, out)
35
+ # out << 'ERROR!'
36
+ # raise e
37
+ # end
38
+ #
39
+ # Ignore errors completely while streaming:
40
+ #
41
+ # def handle_stream_error(e, out)
42
+ # end
43
+ #
44
+ # or handle the errors in some other way.
45
+ module Streaming
46
+ # Class of the response body in case you use #stream.
47
+ class Stream
48
+ include Enumerable
49
+
50
+ # Handle streaming options, see Streaming for details.
51
+ def initialize(opts=OPTS, &block)
52
+ @block = block
53
+ @out = nil
54
+ @callback = opts[:callback]
55
+ @closed = false
56
+ end
57
+
58
+ # Add output to the streaming response body. Returns number of bytes written.
59
+ def write(data)
60
+ data = data.to_s
61
+ @out.call(data)
62
+ data.bytesize
63
+ end
64
+
65
+ # Add output to the streaming response body. Returns self.
66
+ def <<(data)
67
+ write(data)
68
+ self
69
+ end
70
+
71
+ # If not already closed, close the connection, and call
72
+ # any callbacks.
73
+ def close
74
+ return if closed?
75
+ @closed = true
76
+ @callback.call if @callback
77
+ end
78
+
79
+ # Whether the connection has already been closed.
80
+ def closed?
81
+ @closed
82
+ end
83
+
84
+ # Yield values to the block as they are passed in via #<<.
85
+ def each(&out)
86
+ @out = out
87
+ @block.call(self)
88
+ ensure
89
+ close
90
+ end
91
+ end
92
+
93
+ # Class of the response body if you use #stream with :async set to true.
94
+ # Uses a separate thread that pushes streaming results to a queue, so that
95
+ # data can be streamed to clients while it is being prepared by the application.
96
+ class AsyncStream
97
+ include Enumerable
98
+
99
+ # Handle streaming options, see Streaming for details.
100
+ def initialize(opts=OPTS, &block)
101
+ @stream = Stream.new(opts, &block)
102
+ @queue = opts[:queue] || SizedQueue.new(10) # have some default backpressure
103
+ @thread = Thread.new { enqueue_chunks }
104
+ end
105
+
106
+ # Continue streaming data until the stream is finished.
107
+ def each(&out)
108
+ dequeue_chunks(&out)
109
+ @thread.join
110
+ end
111
+
112
+ # Stop streaming.
113
+ def close
114
+ @queue.close # terminate the producer thread
115
+ @stream.close
116
+ end
117
+
118
+ private
119
+
120
+ # Push each streaming chunk onto the queue.
121
+ def enqueue_chunks
122
+ @stream.each do |chunk|
123
+ @queue.push(chunk)
124
+ end
125
+ rescue ClosedQueueError
126
+ # connection was closed
127
+ ensure
128
+ @queue.close
129
+ end
130
+
131
+ # Pop each streaming chunk from the queue and yield it.
132
+ def dequeue_chunks
133
+ while chunk = @queue.pop
134
+ yield chunk
135
+ end
136
+ end
137
+ end
138
+
139
+ module InstanceMethods
140
+ # Immediately return a streaming response using the current response
141
+ # status and headers, calling the block to get the streaming response.
142
+ # See Streaming for details.
143
+ def stream(opts=OPTS, &block)
144
+ if opts[:loop]
145
+ block = proc do |out|
146
+ until out.closed?
147
+ begin
148
+ yield(out)
149
+ rescue => e
150
+ handle_stream_error(e, out)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ stream_class = (opts[:async] && RUBY_VERSION >= '2.3') ? AsyncStream : Stream
157
+
158
+ throw :halt, @_response.finish_with_body(stream_class.new(opts, &block))
159
+ end
160
+
161
+ # Handle exceptions raised while streaming when using :loop
162
+ def handle_stream_error(e, out)
163
+ raise e
164
+ end
165
+ end
166
+ end
167
+
168
+ register_plugin(:streaming, Streaming)
169
+ end
170
+ end
@@ -0,0 +1,33 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The strip_path_prefix plugin makes Roda strip a given prefix off internal absolute paths,
7
+ # turning them to relative paths. Roda by default stores internal paths as absolute paths.
8
+ # The main reason to use this plugin is when the internal absolute path could change at
9
+ # runtime, either due to a symlink change or chroot call, or you really want to use
10
+ # relative paths instead of absolute paths.
11
+ #
12
+ # Examples:
13
+ #
14
+ # plugin :strip_path_prefix # Defaults to Dir.pwd
15
+ # plugin :strip_path_prefix, File.dirname(Dir.pwd)
16
+ module StripPathPrefix
17
+ # Set the regexp to use when stripping prefixes from internal paths.
18
+ def self.configure(app, prefix=Dir.pwd)
19
+ prefix += '/' unless prefix.end_with?("/")
20
+ app.opts[:strip_path_prefix] = /\A#{Regexp.escape(prefix)}/
21
+ end
22
+
23
+ module ClassMethods
24
+ # Strip the path prefix from the gien path if it starts with the prefix.
25
+ def expand_path(path, root=opts[:root])
26
+ super.sub(opts[:strip_path_prefix], '')
27
+ end
28
+ end
29
+ end
30
+
31
+ register_plugin(:strip_path_prefix, StripPathPrefix)
32
+ end
33
+ end
@@ -0,0 +1,188 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The symbol_matchers plugin allows you do define custom regexps to use
7
+ # for specific symbols. For example, if you have a route such as:
8
+ #
9
+ # r.on :username do |username|
10
+ # # ...
11
+ # end
12
+ #
13
+ # By default this will match all nonempty segments. However, if your usernames
14
+ # must be 6-20 characters, and can only contain +a-z+ and +0-9+, you can do:
15
+ #
16
+ # plugin :symbol_matchers
17
+ # symbol_matcher :username, /([a-z0-9]{6,20})/
18
+ #
19
+ # Then the route will only if the path is +/foobar123+, but not if it is
20
+ # +/foo+, +/FooBar123+, or +/foobar_123+.
21
+ #
22
+ # You can provide a <tt>segment: true</tt> option to symbol_matcher to speed up
23
+ # the matching on Ruby 2.4+:
24
+ #
25
+ # symbol_matcher :username, /([a-z0-9]{6,20})/, segment: true
26
+ #
27
+ # Use of <tt>segment: true</tt> requires that the regexp not match more than
28
+ # one segment (i.e. it cannot match +/+). Additionally, the entire segment will
29
+ # be captured, so any capture groups in the regexp will be ignored.
30
+ #
31
+ # By default, this plugin sets up the following symbol matchers:
32
+ #
33
+ # :d :: <tt>/(\d+)/</tt>, a decimal segment
34
+ # :rest :: <tt>/(.*)/</tt>, all remaining characters, if any
35
+ # :w :: <tt>/(\w+)/</tt>, an alphanumeric segment
36
+ #
37
+ # If the placeholder_string_matchers plugin is loaded, this feature also applies to
38
+ # placeholders in strings, so the following:
39
+ #
40
+ # r.on "users/:username" do |username|
41
+ # # ...
42
+ # end
43
+ #
44
+ # Would match +/users/foobar123+, but not +/users/foo+, +/users/FooBar123+,
45
+ # or +/users/foobar_123+.
46
+ #
47
+ # If using this plugin with the params_capturing plugin, this plugin should
48
+ # be loaded first.
49
+ #
50
+ # You can provide a block when calling +symbol_matcher+, and it will be called
51
+ # for all matches to allow for type conversion:
52
+ #
53
+ # symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
54
+ # Date.new(y.to_i, m.to_i, d.to_i)
55
+ # end
56
+ #
57
+ # route do |r|
58
+ # r.on :date do |date|
59
+ # # date is an instance of Date
60
+ # end
61
+ # end
62
+ #
63
+ # If you have a segment match the passed regexp, but decide during block
64
+ # processing that you do not want to treat it as a match, you can have the
65
+ # block return nil or false. This is useful if you want to make sure you
66
+ # are using valid data:
67
+ #
68
+ # symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
69
+ # y = y.to_i
70
+ # m = m.to_i
71
+ # d = d.to_i
72
+ # Date.new(y, m, d) if Date.valid_date?(y, m, d)
73
+ # end
74
+ #
75
+ # You can have the block return an array to yield multiple captures.
76
+ #
77
+ # The second argument to symbol_matcher can be a symbol already registered
78
+ # as a symbol matcher. This can DRY up code that wants a conversion
79
+ # performed by an existing class matcher or to use the same regexp:
80
+ #
81
+ # symbol_matcher :employee_id, :d do |id|
82
+ # id.to_i
83
+ # end
84
+ # symbol_matcher :employee, :employee_id do |id|
85
+ # Employee[id]
86
+ # end
87
+ #
88
+ # With the above example, the :d matcher matches only decimal strings, but
89
+ # yields them as string. The registered :employee_id matcher converts the
90
+ # decimal string to an integer. The registered :employee matcher builds
91
+ # on that and uses the integer to lookup the related employee. If there is
92
+ # no employee with that id, then the :employee matcher will not match.
93
+ #
94
+ # If using the class_matchers plugin, you can provide a recognized class
95
+ # matcher as the second argument to symbol_matcher, and it will work in
96
+ # a similar manner:
97
+ #
98
+ # symbol_matcher :employee, Integer do |id|
99
+ # Employee[id]
100
+ # end
101
+ #
102
+ # Blocks passed to the symbol matchers plugin are evaluated in route
103
+ # block context.
104
+ #
105
+ # If providing a block to the symbol_matchers plugin, the symbol may
106
+ # not work with the params_capturing plugin. Note that the use of
107
+ # symbol matchers inside strings when using the placeholder_string_matchers
108
+ # plugin only uses the regexp, it does not respect the conversion blocks
109
+ # registered with the symbols.
110
+ module SymbolMatchers
111
+ def self.load_dependencies(app)
112
+ app.plugin :_symbol_regexp_matchers
113
+ app.plugin :_symbol_class_matchers
114
+ end
115
+
116
+ def self.configure(app)
117
+ app.opts[:symbol_matchers] ||= {}
118
+ app.symbol_matcher(:d, /(\d+)/, segment: true)
119
+ app.symbol_matcher(:w, /(\w+)/, segment: true)
120
+ app.symbol_matcher(:rest, /(.*)/)
121
+ end
122
+
123
+ module ClassMethods
124
+ # Set the matcher and block to use for the given class.
125
+ # The matcher can be a regexp, registered symbol matcher, or registered class
126
+ # matcher (if using the class_matchers plugin).
127
+ #
128
+ # If providing a regexp, the block given will be called with all regexp captures.
129
+ # If providing a registered symbol or class, the block will be called with the
130
+ # captures returned by the block for the registered symbol or class, or the regexp
131
+ # captures if no block was registered with the symbol or class. In either case,
132
+ # if a block is given, it should return an array with the captures to yield to
133
+ # the match block.
134
+ def symbol_matcher(s, matcher, opts=OPTS, &block)
135
+ _symbol_class_matcher(Symbol, s, matcher, block, opts) do |meth, array|
136
+ define_method(meth){array}
137
+ end
138
+
139
+ nil
140
+ end
141
+
142
+ # Freeze the class_matchers hash when freezing the app.
143
+ def freeze
144
+ opts[:symbol_matchers].freeze
145
+ super
146
+ end
147
+ end
148
+
149
+ module RequestMethods
150
+ private
151
+
152
+ # Use regular expressions to the symbol-specific regular expression
153
+ # if the symbol is registered. Otherwise, call super for the default
154
+ # behavior.
155
+ def _match_symbol(s)
156
+ meth = :"match_symbol_#{s}"
157
+ if respond_to?(meth, true)
158
+ # Allow calling private match methods
159
+ _, re, convert_meth, consume_meth = send(meth)
160
+ if re
161
+ send(consume_meth, re, convert_meth)
162
+ else
163
+ # defined in class_matchers plugin
164
+ _consume_segment(convert_meth)
165
+ end
166
+ else
167
+ super
168
+ end
169
+ end
170
+
171
+ # Return the symbol-specific regular expression if one is registered.
172
+ # Otherwise, call super for the default behavior.
173
+ def _match_symbol_regexp(s)
174
+ meth = :"match_symbol_#{s}"
175
+ if respond_to?(meth, true)
176
+ # Allow calling private match methods
177
+ re, = send(meth)
178
+ re
179
+ else
180
+ super
181
+ end
182
+ end
183
+ end
184
+ end
185
+
186
+ register_plugin(:symbol_matchers, SymbolMatchers)
187
+ end
188
+ end
@@ -0,0 +1,32 @@
1
+ # frozen-string-literal: true
2
+
3
+ require 'rack/utils'
4
+
5
+ class Roda
6
+ module RodaPlugins
7
+ # The symbol_status plugin patches the +status=+ response method to
8
+ # accept the status name as a symbol. If given an integer value,
9
+ # the default behaviour is used.
10
+ #
11
+ # Examples:
12
+ # r.is "needs_authorization" do
13
+ # response.status = :unauthorized
14
+ # end
15
+ # r.is "nothing" do
16
+ # response.status = :no_content
17
+ # end
18
+ #
19
+ # The conversion is done through <tt>Rack::Utils.status_code</tt>.
20
+ module SymbolStatus
21
+ module ResponseMethods
22
+ # Sets the response status code by fixnum or symbol name
23
+ def status=(code)
24
+ code = Rack::Utils.status_code(code) if code.is_a?(Symbol)
25
+ super(code)
26
+ end
27
+ end
28
+ end
29
+
30
+ register_plugin(:symbol_status, SymbolStatus)
31
+ end
32
+ end
@@ -0,0 +1,37 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The symbol_views plugin allows match blocks to return
7
+ # symbols, and consider those symbols as views to use for the
8
+ # response body. So you can take code like:
9
+ #
10
+ # r.root do
11
+ # view :index
12
+ # end
13
+ # r.is "foo" do
14
+ # view :foo
15
+ # end
16
+ #
17
+ # and DRY it up:
18
+ #
19
+ # r.root do
20
+ # :index
21
+ # end
22
+ # r.is "foo" do
23
+ # :foo
24
+ # end
25
+ module SymbolViews
26
+ def self.load_dependencies(app)
27
+ app.plugin :custom_block_results
28
+ end
29
+
30
+ def self.configure(app)
31
+ app.opts[:custom_block_results][Symbol] = :view
32
+ end
33
+ end
34
+
35
+ register_plugin(:symbol_views, SymbolViews)
36
+ end
37
+ end
@@ -0,0 +1,78 @@
1
+ # frozen-string-literal: true
2
+
3
+ #
4
+ class Roda
5
+ module RodaPlugins
6
+ # The timestamp_public plugin adds a +timestamp_path+ method for constructing
7
+ # timestamp paths, and a +r.timestamp_public+ routing method to serve static files
8
+ # from a directory (using the public plugin). This plugin is useful when you want
9
+ # to modify the path to static files when the modify timestamp on the file changes,
10
+ # ensuring that requests for the static file will not be cached.
11
+ #
12
+ # Note that while this plugin will not serve files outside of the public directory,
13
+ # for performance reasons it does not check the path of the file is inside the public
14
+ # directory when getting the modify timestamp. If the +timestamp_path+ method is
15
+ # called with untrusted input, it is possible for an attacker to get the modify
16
+ # timestamp for any file on the file system.
17
+ #
18
+ # Examples:
19
+ #
20
+ # # Use public folder as location of files, and static as the path prefix
21
+ # plugin :timestamp_public
22
+ #
23
+ # # Use /path/to/app/static as location of files, and public as the path prefix
24
+ # opts[:root] = '/path/to/app'
25
+ # plugin :public, root: 'static', prefix: 'public'
26
+ #
27
+ # # Assuming public is the location of files, and static is the path prefix
28
+ # route do
29
+ # # Make GET /static/1238099123/images/foo.png look for public/images/foo.png
30
+ # r.timestamp_public
31
+ #
32
+ # r.get "example" do
33
+ # # "/static/1238099123/images/foo.png"
34
+ # timestamp_path("images/foo.png")
35
+ # end
36
+ # end
37
+ module TimestampPublic
38
+ def self.load_dependencies(app, opts=OPTS)
39
+ app.plugin :public, opts
40
+ end
41
+
42
+ # Use options given to setup timestamped file serving. The following option is
43
+ # recognized by the plugin:
44
+ #
45
+ # :prefix :: The prefix for paths, before the timestamp segment
46
+ #
47
+ # The options given are also passed to the public plugin.
48
+ def self.configure(app, opts=OPTS)
49
+ app.opts[:timestamp_public_prefix] = (opts[:prefix] || app.opts[:timestamp_public_prefix] || "static").dup.freeze
50
+ end
51
+
52
+ module InstanceMethods
53
+ # Return a path to the static file that could be served by r.timestamp_public.
54
+ # This does not check the file is inside the directory for performance reasons,
55
+ # so this should not be called with untrusted input.
56
+ def timestamp_path(file)
57
+ mtime = File.mtime(File.join(opts[:public_root], file))
58
+ "/#{opts[:timestamp_public_prefix]}/#{sprintf("%i%06i", mtime.to_i, mtime.usec)}/#{file}"
59
+ end
60
+ end
61
+
62
+ module RequestMethods
63
+ # Serve files from the public directory if the file exists,
64
+ # it includes the timestamp_public prefix segment followed by
65
+ # a integer segment for the timestamp, and this is a GET request.
66
+ def timestamp_public
67
+ if is_get?
68
+ on roda_class.opts[:timestamp_public_prefix], Integer do |_|
69
+ public
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ register_plugin(:timestamp_public, TimestampPublic)
77
+ end
78
+ end