jeremyevans-roda-4f30bb3/Rakefile 0000664 0000000 0000000 00000006164 15167207754 0017064 0 ustar 00root root 0000000 0000000 require "rake"
require "rake/clean"
NAME = 'roda'
VERS = lambda do
require_relative 'lib/roda/version'
Roda::RodaVersion
end
CLEAN.include ["#{NAME}-*.gem", "rdoc", "coverage", "www/public/*.html", "www/public/rdoc", "spec/assets/app.*.css", "spec/assets/app.*.js", "spec/assets/app.*.css.gz", "spec/assets/app.*.js.gz", "spec/iv.erb"]
# Gem Packaging and Release
desc "Packages #{NAME}"
task :package=>[:clean] do |p|
sh %{gem build #{NAME}.gemspec}
end
### RDoc
desc "Generate rdoc"
task :website_rdoc do
rdoc_dir = "www/public/rdoc"
rdoc_opts = ["--line-numbers", "--inline-source", '--title', 'Roda: Routing tree web toolkit']
begin
gem 'hanna'
rdoc_opts.concat(['-f', 'hanna'])
rescue Gem::LoadError
end
rdoc_opts.concat(['--main', 'README.rdoc', "-o", rdoc_dir])
rdoc_opts.concat(%w"README.rdoc CHANGELOG doc/CHANGELOG.old MIT-LICENSE" + Dir["lib/**/*.rb"] + Dir["doc/**/*.rdoc"] + Dir['doc/release_notes/*.txt'])
FileUtils.rm_rf(rdoc_dir)
require "rdoc"
RDoc::RDoc.new.document(rdoc_opts)
end
### Website
desc "Make local version of website"
task :website_base do
sh %{#{FileUtils::RUBY} -I lib www/make_www.rb}
end
desc "Make local version of website, with rdoc"
task :website => [:website_base, :website_rdoc]
desc "Serve local version of website via rackup"
task :serve => :website do
sh %{#{FileUtils::RUBY} -C www -S rackup}
end
### Specs
spec = proc do |env|
env.each{|k,v| ENV[k] = v}
sh "#{FileUtils::RUBY} #{"-w" if RUBY_VERSION >= '3'} #{'-W:strict_unused_block' if RUBY_VERSION >= '3.4'} spec/all.rb"
env.each{|k,v| ENV.delete(k)}
if File.directory?('.sass-cache')
require 'fileutils'
FileUtils.rm_r('.sass-cache')
end
end
desc "Run specs"
task "spec" do
spec.call({})
end
task :default=>:spec
desc "Run specs with method visibility checking"
task "spec_vis" do
spec.call('CHECK_METHOD_VISIBILITY'=>'1')
end
desc "Run specs with coverage"
task "spec_cov" do
spec.call('COVERAGE'=>'< 4')
spec.call('COVERAGE'=>'< 3.1')
spec.call('COVERAGE'=>'< 3')
spec.call('COVERAGE'=>'< 1.6', 'RODA_RENDER_COMPILED_METHOD_SUPPORT'=>'no')
end
desc "Run specs with Rack::Lint"
task "spec_lint" do
spec.call('LINT'=>'1')
end
desc "Run specs in CI mode"
task "spec_ci" do
# Use LINT on about half of the tested Ruby versions,
# rotating each day.
spec.call(RUBY_VERSION[2].to_i.odd? ^ Time.now.wday.odd? ? {} : {'LINT'=>'1'})
end
### Other
desc "Print #{NAME} version"
task :version do
puts VERS.call
end
desc "Start an IRB shell using the extension"
task :irb do
require 'rbconfig'
ruby = ENV['RUBY'] || File.join(RbConfig::CONFIG['bindir'], RbConfig::CONFIG['ruby_install_name'])
irb = ENV['IRB'] || File.join(RbConfig::CONFIG['bindir'], File.basename(ruby).sub('ruby', 'irb'))
sh %{#{irb} -I lib -r #{NAME}}
end
# Other
desc "Check documentation for plugin files"
task :check_plugin_doc do
text = File.binread('www/pages/documentation.erb')
skip = %w'delay_build'
Dir['lib/roda/plugins/*.rb'].map{|f| File.basename(f).sub('.rb', '') if File.size(f)}.sort.each do |f|
puts f if !f.start_with?('_') && !skip.include?(f) && !text.include?(">#{f}<")
end
end
jeremyevans-roda-4f30bb3/doc/ 0000775 0000000 0000000 00000000000 15167207754 0016155 5 ustar 00root root 0000000 0000000 jeremyevans-roda-4f30bb3/doc/CHANGELOG.old 0000664 0000000 0000000 00000062621 15167207754 0020153 0 ustar 00root root 0000000 0000000 = 2.29.0 (2017-08-16)
* Deprecate accessing multi_route namespace when there are no routes (jeremyevans)
* Deprecate additional internal constants (jeremyevans)
* Respect :root app option when using :layout_opts=>:views render plugin option (jeremyevans)
* Deprecate rendering templates outside of render plugin :allowed_paths option by default (jeremyevans)
* Deprecate :cache=>nil/false render plugin option overriding :cache render/view method option (jeremyevans)
* Deprecate using :header matcher in header_matchers plugin without :header_matcher_prefix app option (jeremyevans)
* Deprecate using content_for multiple times with the same key in the content_for plugin unless :append plugin option is used (jeremyevans)
* Deprecate use of :host matcher with regexp value in header_matchers plugin without :host_matcher_captures app option (jeremyevans)
* Deprecate view_options plugin locals handling, move to the new branch_locals plugin (jeremyevans)
* Deprecate render plugin locals handling, move to the new render_locals plugin (jeremyevans)
* Deprecate the :ext render method and plugin option (jeremyevans)
* Deprecate the view_subdirs plugin alias for the view_options plugin (jeremyevans)
* Deprecate Stream#callback in the streaming plugin (jeremyevans)
* Deprecate the automatic support for EventMachine in the streaming plugin (jeremyevans)
* Deprecate static_path_info plugin, which has been a no-op in Roda 2 (jeremyevans)
* Deprecate render plugin :escape option loading Erubis escaping support (jeremyevans)
* Deprecate the per_thread_caching plugin (jeremyevans)
* Deprecate the websockets plugin (jeremyevans)
* Deprecate treating unsupported matchers as always matching (jeremyevans)
* Deprecate ignoring unsupported match block return values (jeremyevans)
* Deprecate the :format, :opt, and :optd default symbol matchers in the symbol_matchers plugin (jeremyevans)
* Deprecate use of placeholders in string matchers by default, add placeholder_string_matchers plugin for it (jeremyevans)
= 2.28.0 (2017-07-14)
* Deprecate unneeded internal constants (jeremyevans)
* Optimize for ruby 2.3+ using frozen string literals instead of constants (jeremyevans)
* Move 303 default redirect status from sinatra_helpers to status_303 plugin, so it can be loaded separately (plujon) (#122)
= 2.27.0 (2017-06-14)
* Add class_matchers plugin for matching other classes (in addition to String/Integer), with user specified regexps and type conversion (jeremyevans)
* Support String class matcher for non-empty segments, same behavior as symbol matchers but more intuitive and DRY (jeremyevans)
* Support Integer class matcher for \d+ segments, yielding matched values as integers (jeremyevans)
= 2.26.0 (2017-05-16)
* Support :skip_middleware option to csrf plugin to add only the methods and not add the middleware (luciusgone) (#118)
* Handle multiple types with matching suffixes in the type_routing plugin (e.g. tar.gz and gz) (tomdalling) (#117)
= 2.25.0 (2017-04-18)
* Add error_mail plugin, similar to error_email but using mail instead of net/smtp directly (jeremyevans)
= 2.24.0 (2017-03-15)
* Have h plugin use cgi/escape if available for faster escaping (jeremyevans)
* Add disallow_file_uploads plugin for raising an exception if a multipart file upload is attempted (jeremyevans)
* Add strip_path_prefix plugin for stripping prefixes off of internal absolute paths, making them relative paths (jeremyevans)
* Add Roda.expand_path method to DRY up path expansion (jeremyevans)
* Support :freeze_middleware option, which freezes all middleware instances when building the rack app (jeremyevans)
* Allow middleware plugin to accept a block that will be used to configure the application when used as middleware (jeremyevans)
* Support an options hash when loading the cookies plugin, that will be used as the defaults for setting and deleting cookies (mwpastore, jeremyevans) (#112)
* Make the static_routing plugin work with the hooks plugin if the hooks plugin is loaded first (jeremyevans) (#110)
* Do not modify the render plugin's cache if loading the plugin multiple times (jeremyevans)
= 2.23.0 (2017-02-24)
* Add :inherit_cache render plugin option, to create a copy of the cache for subclasses, instead of using an empty cache (jeremyevans)
* In development mode, default to :explicit_cache=>true, :cache=>true instead of :cache=>false (jeremyevans)
* Add :explicit_cache render plugin option, to only cache templates if the :cache option is given to render/view (jeremyevans)
* Add error_email_content method to error_email plugin (jeremyevans)
* Make error_email method in error_email plugin support non-exception arguments (jeremyevans)
* Make Roda.freeze in the static_routing plugin return self (jeremyevans)
= 2.22.0 (2017-01-20)
* Add support for :verbatim_string_matcher option, for making all string matchers match verbatim (jeremyevans)
* Add support for :unsupported_matcher => :raise option, for raising on unsupported matcher values (jeremyevans)
* Add support for :unsupported_block_result => :raise option, for raising on unsupported route/match block return values (jeremyevans)
= 2.21.0 (2016-12-16)
* Add handle_stream_error method to streaming plugin, for handling errors when using stream(:loop=>true) (jeremyevans)
= 2.20.0 (2016-11-13)
* Support :escape=>:erubi option in the render plugin to use the erubi template engine (jeremyevans)
= 2.19.0 (2016-10-14)
* Don't add Content-Type/Content-Length headers for 1xx, 204, 205, 304 statuses (celsworth, jeremyevans) (#101, #102)
* Optimize indifferent_params plugin when using Rack 2 (jeremyevans)
* Fix assets_paths method in assets plugin when subresource integrity is used (jeremyevans, celsworth)
* Make assets plugin depend on h plugin, instead of using Rack::Utils.escape_html (jeremyevans)
* Make h plugin not escape / (celsworth, jeremyevans) (#100)
= 2.18.0 (2016-09-13)
* Add assets_preloading plugin, for creating link tags or Link header for preloading assets (celsworth, jeremyevans) (#98)
* Add assets_paths method to assets plugin, for just the paths to the assets, instead of the full tags (celsworth) (#96)
* Make type_routing plugin work correctly with public plugin (celsworth, jeremyevans) (#95)
* Add static_routing plugin for 3-4x increase in performance for large numbers of static routes (jeremyevans)
* Make head plugin work with not_allowed plugin if loaded after (jeremyevans) (#92)
= 2.17.0 (2016-08-13)
* Add :postprocessor option to assets plugin, for postprocessing assets (e.g. autoprefixing CSS) (celsworth) (#86)
* Fix path passed to rack apps when using r.run and the type_routing plugin (jeremyevans) (#82)
* Support :classes option to error_handler plugin for overriding which exception classes to rescue (jeremyevans)
* Support :layout_opts=>:merge_locals option in render plugin for merging view template locals into layout template locals (jeremyevans) (#80)
* Support :sri option to assets plugin to enable subresource integrity (jeremyevans)
* Add run_append_slash plugin, so r.run uses "/" instead of "" for app's PATH_INFO (kenaniah) (#77)
= 2.16.0 (2016-07-13)
* Add type_routing plugin, for routing based on path extensions and Accept headers (Papierkorb, jeremyevans) (#75)
* Add unescape_path plugin, for decoding URL-encoded PATH_INFO before routing (jeremyevans) (#74)
* Add request_headers plugin, for simpler access to request headers (celsworth) (#72)
= 2.15.0 (2016-06-13)
* Add public plugin for r.public method for serving all files in the public directory (jeremyevans)
* Make send_file in sinatra_helpers plugin work with Rack 2 (jeremyevans)
* Make :header matcher prefixes the env key with HTTP_ if application :header_matcher_prefix option is set (timothypage, jeremyevans) (#69)
* Add content_for plugin :append option to support appending to the existing content (evanleck, jeremyevans) (#66)
= 2.14.0 (2016-05-13)
* Add symbol_status plugin for using symbols as status codes (Papierkorb) (#65)
* Make middleware plugin also run the application's middleware (jeremyevans)
= 2.13.0 (2016-04-14)
* Add :check_paths and :allowed_paths to render plugin options to avoid security issues with template rendering (jeremyevans)
= 2.12.0 (2016-03-15)
* Allow error handler access to the request's remaining_path (jeremyevans)
* Add optimized_string_matchers plugin, containing optimized matchers for single string arguments (jeremyevans)
* Optimize string matching code for strings without placeholders for up to a 60% performance increase (jeremyevans)
* Optimize symbol matching code for up to a 60% performance increase (jeremyevans)
= 2.11.0 (2016-02-16)
* Support :scope option in render plugin, for specifying object in which to evaluate the template (jeremyevans)
* Make minjs compressor support in assets plugin support latest version of Minjs (jeremyevans)
* Add params_capturing plugin, for storing matcher captures in the request params (jeremyevans)
= 2.10.0 (2016-01-15)
* Do not override existing Content-Type header in json plugin (jeremyevans)
* Add :content_type option to json plugin to override Content-Type header used (Kyrremann) (#58)
* Add support for running with --enable-frozen-string-literal on ruby 2.3 (jeremyevans)
* Add Streaming::Stream#write method so that IO.copy_stream will work (janko-m) (#56)
= 2.9.0 (2015-12-15)
* Support passing the content as a string argument instead of a block in the content_for plugin (badosu) (#52)
= 2.8.0 (2015-11-16)
* Add multi_view plugin for easily setting up routing for rendering multiple views (jeremyevans)
* Make content_for plugin work with haml and potentially other non-erb template engines (plukevdh) (#50)
= 2.7.0 (2015-10-13)
* Add run_handler plugin for modifying rack response arrays when using r.run, and continuing routing for 404 responses (jeremyevans)
* Add response_request plugin allowing response object access to request object (jeremyevans)
* Add default_status plugin for overriding the default response status (celsworth) (#47)
* Make RodaCache synchronize access on MRI (jeremyevans)
* Support opts[:host_matcher_captures] = true to make :host=>/regexp/ matcher yield captures in the header_matchers plugin (jeremyevans)
* Allow Roda.rewrite_path to take a block in the path_rewriter plugin (Freaky) (#45)
= 2.6.0 (2015-09-14)
* Add :params and :params! matchers to param_matchers plugin (jeremyevans)
* Merge options when loading csrf plugin multiple times (jeremyevans)
* Allow request.halt to work in before hooks in the hooks plugin (celsworth) (#38)
= 2.5.1 (2015-08-13)
* Allow multi_route and middleware plugins to work together (janko-m) (#36)
= 2.5.0 (2015-07-14)
* Make :by_name option to path plugin default to true in development (jeremyevans)
* Add :cache_class option to render plugin, for customized template cache behavior (celsworth) (#34)
* Add :compiled_asset_host option to assets plugin, to use a host for compiled assets (jeremyevans)
* Allow r.multi_run to take a block that is called with the prefix before dispatching to the rack app (mikz) (#32)
= 2.4.0 (2015-06-15)
* Add websockets plugin, for integration with faye-websocket (jeremyevans)
* Add status_handler plugin, similar to not_found but for any status code (celsworth) (#29)
* Support Closure Compiler, Uglifier, and MinJS for compressing javascript in the assets plugin (jeremyevans)
* Make Roda.plugin always return nil (jeremyevans)
* Add :gzip option to assets plugin (jeremyevans)
= 2.3.0 (2015-05-13)
* Make assets plugin work better with json plugin when r.assets is the last method called in a route block (jeremyevans) (#27)
* Support no_mail! method in the mailer plugin, for skipping an email (jeremyevans)
* Add precompile_templates plugin, for saving memory when using a forking webserver (jeremyevans)
* Document how to allow per-branch HTML escaping of <%= %> in the view_options plugin (jeremyevans)
* Add :include_request option to json and json_parser plugins to include request in :serializer/:parser call (janko-m) (#26)
* Optimize template cache lookup in render plugin when :cache_key is given (jeremyevans)
* Add :engine_opts option to render plugin, for specifying per-template engine options (jeremyevans)
* The render plugin and render/view :ext option is now replaced by the :engine option (jeremyevans)
* Add path_rewriter plugin, for rewriting paths before routing (jeremyevans)
* Add :cache_key option to render/view to explicitly set the template cache key (jeremyevans)
* Don't cache templates if :template_block is given to render/view, unless :cache=>true is used (jeremyevans)
* Add :cache option to render/view to force caching or not caching the template (jeremyevans)
* Avoid rehashing hashes at runtime in plugins (jeremyevans)
* Add heartbeat plugin for heartbeat support (jeremyevans)
* Support :serializer option in json plugin (janko-m) (#21)
* Add json_parser plugin, for parsing request bodies in JSON format (jeremyevans)
= 2.2.0 (2015-04-13)
* Add :escaper render plugin option to support custom escaping of <%= %> tags when :escape is used (jeremyevans)
* Add :escape_safe_classes render plugin option, to not escape certain string subclasses when :escape is used (jeremyevans)
* Split partials method from padrino_render plugin into partials plugin (kematzy) (#19)
* Add shared_vars plugin, for sharing variables between multiple Roda apps (jeremyevans)
* Add delay method to chunked plugin, for delaying a block execution until right before content template rendering (jeremyevans)
* Have default Content-Type header when using the default_headers plugin (jeremyevans)
* Add :by_name option to the path plugin, for registering classes by name, useful when reloading code (jeremyevans)
* Add Roda.path_block to get the block related to the given class used for Road#path (jeremyevans)
* Make Roda#path work correctly in subclasses (jeremyevans)
= 2.1.0 (2015-03-13)
* Have add_file in the mailer plugin support blocks, which are called after the file has been added (jeremyevans)
* Add append_view_subdir to view_options, for appending to an existing view subdirectory (jeremyevans)
* Rename view_subdirs plugin to view_options, add support for branch/route specific view/layout options/locals (jeremyevans)
* Merge :locals set in the render plugin options into :locals provided in call to view/render (jeremyevans)
* Add support for registering classes in the path plugin for use with Roda#path (jeremyevans)
* Use :add_script_name app option as default for path method :add_script_name option in path plugin (jeremyevans)
* Support :add_script_name app option in assets plugin, to prefix URLs with SCRIPT_NAME (jeremyevans)
* Make r.multi_route in multi_route plugin work without any named routes defined (jeremyevans)
* Add :static plugin, for more easily serving static files (jeremyevans)
* Recognize Roda :root option in render and assets plugins (jeremyevans)
* Make :layout=>false option in render plugin override previous layout template (jeremyevans)
* Make add_file in the mailer plugin add the files after the email body instead of before (jeremyevans)
= 2.0.0 (2015-02-13)
* Allow Roda app to be used as a regular rack app even when using the middleware plugin (jeremyevans)
* Make render plugin :layout option always be true or false (jeremyevans)
* Make :layout=>true view option use the default layout (jeremyevans)
* Make error_handler plugin rescue ScriptError in addition to StandardError (jeremyevans)
* Make halt plugin integrate with symbol_views, json, and similar plugins (jeremyevans)
* Add padrino_render plugin, adding render/partial methods that work similar to Padrino (jeremyevans)
* Add Roda#render_template private method for template rendering, for use by plugins (jeremyevans)
* Make Roda#initialize take env hash, #call take route_block, remove private #_route (jeremyevans)
* Remove keep_remaining_path/update_remaining_path private request methods (jeremyevans)
* Don't modify SCRIPT_NAME/PATH_INFO during routing, merging static_path_info plugin into core (jeremyevans)
* Remove code deprecated in Roda 1.3.0 (jeremyevans)
= 1.3.0 (2015-01-13)
* Make static_path_info plugin restore original SCRIPT_NAME/PATH_INFO before returning from r.run (jeremyevans)
* Add RodaMajorVersion, RodaMinorVersion, and RodaPatchVersion (jeremyevans)
* Add delete_empty_headers plugin for deleting response headers that are empty before return response (jeremyevans)
* Make freeze class method freeze internal data structures to avoid thread safety issues (jeremyevans)
* Deprecate mutating plugin option hashes for chunked, default_headers, error_email, json, and render plugins (jeremyevans)
* Fix subclassing app and using r.multi_run in subclass in multi_run plugin (jeremyevans)
* Support :classes option in json plugin to set the classes to use (jeremyevans)
* Improve performance in default_headers plugin by not duping the headers (jeremyevans)
* Use :template_opts instead of :opts for providing options to the template in the render plugin (jeremyevans)
* Support :match_header_yield Roda option in the header_matchers plugin, causing the :header match to yield the value (jeremyevans)
* Move :param and :param! hash matchers to the param_matchers plugin (jeremyevans)
* Add path_matchers plugin, for :extension, :prefix, and :suffix hash matchers (jeremyevans)
* Move Roda.hash_matcher to hash_matcher plugin (jeremyevans)
* Move Roda.request_module and .response_module to module_include plugin (jeremyevans)
* Move RodaResponse#set_cookie and #delete_cookie to cookies plugin (jeremyevans)
* Deprecate RodaRequest#full_path_info, use #path instead (jeremyevans)
* Add class_delegate to the delegate plugin (jeremyevans)
* Make not_found plugin clear headers for response if it is not found (jeremyevans)
* Make error_handler plugin use a new response instead of reusing existing response (jeremyevans)
* Make RodaResponse a subclass of Object instead of Rack::Response (jeremyevans)
= 1.2.0 (2014-12-17)
* Don't override explicit nil :default_encoding template option in the render plugin (jeremyevans)
* Add remaining_path and matched_path request methods (jeremyevans)
* Add slash_path_emty plugin, for considering a path of "/" as empty when doing a terminal match (jeremyevans)
* Remove def_verb_method request class method (jeremyevans)
* Support :add_script_name, :name, :url, and :url_only options when creating named paths in the path plugin (jeremyevans)
* Add match_affix plugin, for overriding default prefix/suffix used in match patterns (jeremyevans)
* Add empty_root plugin, for making root matcher also match empty string (jeremyevans)
* Add roda_class instance methods to RodaRequest and RodaResponse, to DRY up plugin code (jeremyevans)
* Add sinatra_helpers plugin, porting Sinatra::Helpers methods not covered by other plugins (jeremyevans)
* Don't set the default headers until the response is finished (jeremyevans)
* Add RodaRequest#default_redirect_status, so plugins can override the default status used for redirects (jeremyevans)
* Add drop_body plugin, for automatically dropping body and Content-{Length,Type} headers based on response status (jeremyevans)
* Add clear_middleware! class method, for clearing the current middleware (jeremyevans)
* Add inherit_middleware class accessor, allowing users to turn off middleware inheritance (jeremyevans)
* Add multi_run plugin, for dispatching to multiple rack applications based on the request path prefix (jeremyevans)
* Add environments plugin, for handling development/test/production environments (jeremyevans)
* Do not cache templates by default if RACK_ENV is development (jeremyevans)
* Add delay_build plugin, to delay building the rack app until Roda.app is called (jeremyevans)
* Add :user_agent hash matcher to the header_matchers plugin (jeremyevans)
* Fix caching of templates in the render plugin when :opts or :template_class is used (jeremyevans)
* Require loading the render plugin again if you want to change the default layout (jeremyevans)
* Pass :css_opts and :js_opts as template options (via :opts) instead of render options when rendering (jeremyevans)
* Only pass :opts hash to template class during rendering, instead of all render/view options (jeremyevans)
* Support :template_class option in the render plugin for overriding template class to use (jeremyevans)
* Automatically dup unfrozen Array/Hash opts values when subclassing (jeremyevans)
* Add named_templates plugin, for creating inline templates by name, instead of storing them in the file system (jeremyevans)
* Support :template option in for render/view to specify template to use, instead of requiring separate argument (jeremyevans)
* Add class_level_routing plugin, for a DSL similar to Sinatra (jeremyevans)
* Make RodaRequest.consume_pattern not capture pattern by default (jeremyevans)
* Add static_path_info plugin, making Roda not modify PATH_INFO or SCRIPT_NAME during routing (jeremyevans)
* Use local/instance variable lookups instead of method calls to improve performance (jeremyevans)
* Add RodaRequest#session, and have #session delegate to that (jeremyevans)
* Add delegate plugin, for easily creating methods that delegate to request or response (jeremyevans)
* Add mailer plugin, allowing use of a routing tree for email instead of web responses (jeremyevans)
= 1.1.0 (2014-11-11)
* Add assets plugin, for rendering assets on the fly, or compiling them to a single compressed file (cj, jeremyevans) (#5)
* Make InstanceMethods in plugins not include constants, as they would pollute the constant namespace (jeremyevans)
* Make response.finish add the Content-Length header, not response.write (jeremyevans)
* Add response.finish_with_body to override response body used (jeremyevans)
* Use allocate instead of new in rack app (jeremyevans)
* Add chunked plugin, for easy streaming of template responses using Transfer-Encoding: chunked (jeremyevans)
* Add namespace support to the multi_route plugin, to support more complex applications (jeremyevans)
* Make r.multi_route use named route return value if not passed a block (jeremyevans)
* Make r.multi_route prefer longer route if multiple routes have the same prefix (jeremyevans)
* Add caching plugin, for handling http caching (jeremyevans)
* Support adding middleware after the route block has been added (jeremyevans)
* Allow Roda subclasses to use route block from superclass (jeremyevans)
* Have r.multi_route ignore non-String named routes (jeremyevans)
* Pick up newly added named routes while running in the multi_route plugin, useful for development (jeremyevans)
* Add path plugin, for named path support (jeremyevans) (#4)
* Add error_email plugin, for easily emailing an error notification for an exception (jeremyevans)
= 1.0.0 (2014-08-19)
* Don't have :extension hash matcher force a terminal match (jeremyevans)
* Add :content option to view method in render plugin to use given content instead of rendering a template (jeremyevans)
* Add :escape option to render plugin for using erb templates where <%= %> escapes and <%== %> does not (jeremyevans)
* Make multi_route plugin route("route_name") method a request method instead of an instance method (jeremyevans)
* Add r.multi_route method to multi_route plugin, for dispatching to named route based on first segment in path (jeremyevans)
* Allow non-GET requests to use r.redirect with no argument, redirecting to current path (jeremyevans)
* Add head plugin, for handling HEAD requests like GET requests with an empty body (jeremyevans)
* Optimize consuming patterns by using a positive lookahead assertion (jeremyevans)
* Add not_allowed plugin, for automatically returning 405 Method Not Allowed responses (jeremyevans)
* Optimize match blocks with no arguments (jeremyevans)
* Add content_for plugin, for storing content in one template and retrieving it in another (jeremyevans)
* Add render_each plugin, for rendering a template for each value in an enumerable (jeremyevans)
* Add backtracking_array plugin, allowing array matchers to backtrack if later matchers do not match (jeremyevans)
* Add :all hash matcher, allowing array matchers to include conditions where you want to match multiple conditions (jeremyevans)
* Add json plugin, allowing match blocks to return arrays/hashes, returning JSON (jeremyevans)
* Add view_subdirs plugin, for setting a subdirectory for views on a per-request basis (jeremyevans)
* Allow default halt method to take no arguments, and use the current response (jeremyevans)
* Add symbol_views plugin, allowing match blocks to return a template name symbol (jeremyevans)
* Add per_thread_caching plugin, for using separate caches per thread instead of shared thread-safe caches (jeremyevans)
* Add hash_matcher class method, for easily creating hash match methods (jeremyevans)
* Add symbol_matchers plugin, for using symbol-specific matching regexps (jeremyevans)
* Add csrf plugin for csrf protection using rack_csrf (jeremyevans)
* Optimize r.is, r.get, r.post and similar methods by reducing the number of Array objects created (jeremyevans)
* Support RequestClassMethods and ResponseClassMethods in plugins (jeremyevans)
* Add Roda::RodaCache for a thread safe cache, currently used for match patterns, templates, and plugins (jeremyevans)
* Optimize matching by caching consume regexp for strings, regexp, symbol, and :extension matchers (jeremyevans)
* Add r.root for GET / requests, for easier to read version of r.get "" (jeremyevans)
* Optimize r.is terminal matcher, remove :term hash matcher (jeremyevans)
* Make flash plugin no longer depend on sinatra-flash (jeremyevans)
* Move version file to roda/version so it can be required separately without loading dependencies (jeremyevans)
= 0.9.0 (2014-07-30)
* Initial public release
jeremyevans-roda-4f30bb3/doc/conventions.rdoc 0000664 0000000 0000000 00000014167 15167207754 0021404 0 ustar 00root root 0000000 0000000 = Conventions
This guide goes over conventions for directory layout and file layout for Roda applications.
You are free to ignore these conventions, they mostly exist to help users who are unsure how
to structure their Roda applications.
== Directory Layout
Which directory layout to use should reflect the size of your application.
=== Small Applications
For a small application, the following directory layout is recommended:
Rakefile
app_name.rb
assets/
config.ru
db.rb
migrate/
models.rb
models/
public/
spec/
views/
+app_name.rb+ should contain the Roda application, and should reflect the name of your application.
So, if your application is named +FooBar+, you should use +foo_bar.rb+.
+config.ru+ should contain the code the webserver uses to determine which application to run.
+views/+ should contain your template files. This assumes you are using the +render+ plugin
and server-side rendering. If you are creating a single page application and just serving
JSON, then you won't need a +views+ directory. For small applications, all view files should be
in the +views+ directory.
+public/+ should contain any static files that should be served directly by the webserver.
Again, for pure JSON applications, you won't need a +public+ directory.
+assets/+ should contain the source files for your CSS and javascript assets. If you are
not using the +assets+ plugin, you won't need an +assets+ directory.
+db.rb+ should contain the minimum code to setup a database connection, without loading any of
the applications models. This can be required in cases where you don't want the models loaded,
such as when running migrations. This file should be required by +models.rb+.
+models.rb+ should contain all code related to your ORM. This file should be required
by +app_name.rb+. This keeps your model code separate from your web code, making it easier
to use outside of your web code. It allows you to get an IRB shell for accessing your models
via irb -r ./models, without loading the Roda application.
+models/+ should contain your ORM models, with a separate file per model class.
+migrate/+ should create your database migration files, if you are using an ORM that uses
migrations.
+spec/+ (or +test/+ should contain your specifications/tests. For a small application, it's recommended
to have a single file for your model tests, and a single file for your web/integration tests.
+Rakefile+ should contain the rake tasks for the application. The convention is that the
default rake task will run all specs/tests related to the application. If you are using
the +assets+ plugin, you should have an assets:precompile task for precompiling
assets.
=== Large Applications
Large applications generally need more structure:
Rakefile
app_name.rb
assets/
helpers/
migrate/
models.rb
models/
public/
routes/
prefix1.rb
prefix2.rb
spec/
models/
web/
views/
prefix1/
prefix2/
For larger apps, the +Rakefile+, +assets/+, +migrate+, +models.rb+, +models/+, +public/+, remain the same.
+app_name.rb+ should use the +hash_branch_view_subdir+ plugin (which builds on the +hash_branches+ and
+view_options+ plugin), or the +multi_run+ plugin.
The routes used by the +hash_branches+ or +multi_run+ should be stored in routing files in the +routes/+
directory, with one file per prefix.
For specs/tests, you should have +spec/models/+ and +spec/web/+, with one file per model in +spec/models/+
and one file per prefix in +spec/web/+. Substitute +spec+ with +test+ if that is what you are using as the
name of the directory.
You should have a separate view subdirectory per prefix. With the +hash_branch_view_subdir+, the application
will automatically set a separate view subdirectory per routing tree branch.
+helpers/+ should be used to store helper methods for your application, that you call in your routing files
and views. In a small application, these methods should just be specified in +app_name.rb+
=== Really Large Applications
For very large applications, it's expected that there will be deviations from these conventions. However,
it is recommended to use the +hash_branch_view_subdir+ or +multi_run+ plugins to organize your application, and have
subdirectories in the +routes/+ directory, and nested subdirectories in the +views/+ directory.
== Roda Application File Layout
=== Small Applications
For a small application, the convention in Roda is to layout your Roda application file (+app_name.rb+) like this:
require 'roda'
require_relative 'models'
class AppName < Roda
SOME_CONSTANT = 1
use SomeMiddleware
plugin :render, escape: true
plugin :assets
route do |r|
# ...
end
def view_method
'foo'
end
end
You should first require +roda+ and +./models+, followed by any other libraries needed by the
application.
You should subclass Roda and make the application's name the name of the Roda subclass.
Inside the subclass, you first define the constants used by the application. Then you add
any middleware used by the application, followed by loading any plugins used by the application.
Then you add the route block for the application. After the route block, define the instance methods
used in your route block or views.
=== Large Applications
For larger applications, there are some slight changes to the Roda application file layout:
require 'roda'
require_relative 'models'
class AppName < Roda
SOME_CONSTANT = 1
use SomeMiddleware
plugin :render, escape: true, layout: './layout'
plugin :assets
plugin :hash_branch_view_subdir
Dir['routes/*.rb'].each{|f| require_relative f}
route do |r|
r.hash_branches('')
r.root do
# ...
end
end
Dir['helpers/*.rb'].each{|f| require_relative f}
end
After loading the +hash_branch_view_subdir+ plugin, you require all of your
routing files. Inside your route block, instead of defining your routes, you just call
+r.hash_branches+, which will dispatch to all of your routing files. After your route
block, you require all of your helper files containing the instance methods for your
route block or views, instead of defining the methods directly.
jeremyevans-roda-4f30bb3/doc/release_notes/ 0000775 0000000 0000000 00000000000 15167207754 0021005 5 ustar 00root root 0000000 0000000 jeremyevans-roda-4f30bb3/doc/release_notes/1.0.0.txt 0000664 0000000 0000000 00000022511 15167207754 0022203 0 ustar 00root root 0000000 0000000 = New Plugins
* A csrf plugin has been added for CSRF prevention, using
Rack::Csrf. It also adds helper methods for views such as
csrf_tag.
* A symbol_matchers plugin has been added, for customizing
the regexps used per symbol. This also affects the use
of embedded colons in strings. This supports the following
symbol regexps by default:
:d :: (\d+), a decimal segment
:format :: (?:\.(\w+))?, an optional format/extension
:opt :: (?:\/([^\/]+))?, an optional segment
:optd :: (?:\/(\d+))?, an optional decimal segment
:rest :: (.*), all remaining characters, if any
:w :: (\w+), a alphanumeric segment
This allows you to write code such as:
plugin :symbol_matchers
route do |r|
r.is "track/:d" do
end
end
And have it only match routes such as /track/123, not
/track/abc.
Note that :opt, :optd, and :format are only going to make sense
when used as embedded colons in strings, due to how segment matching
works.
You can add your own symbol matchers using the symbol_matcher
class method:
plugin :symbol_matchers
symbol_matcher :slug, /([\w-]+)/
route do |r|
r.on :slug do
end
end
* A symbol_views plugin has been added, which allows match blocks to
return symbols, which are interpreted as template names:
plugin :symbol_views
route do |r|
:template_name # same as view :template_name
end
* A json plugin has been added, which allows match blocks to return
arrays or hashes, and uses a JSON version of them as the response
body:
plugin :json
route do |r|
{'a'=>[1,2,3]} # response: {"a":[1,2,3]}
end
This also sets the Content-Type of the response to application/json.
To convert additional object types to JSON, you can modify
json_response_classes:
plugin :json
json_response_classes << Sequel::Model
* A view_subdirs plugin has been added for setting a default
subdirectory to use for views:
Roda.route do |r|
r.on "admin" do
set_view_subdir "admin"
r.is do
view "index" # uses admin/index view
end
end
end
* A render_each plugin has been added, for rendering the same
template for multiple objects, and returning the concatenation
of all of the output:
<%= render_each([1,2,3], 'number') %>
This renders the number template 3 times. Each time the template
is rendered, a local variable named number will be present with
the current entry in the enumerable. You can control the name of
the local variable using the :local option:
<%= render_each([1,2,3], 'number', :local=>:n) %>
* A content_for plugin has been added, for storing content in one
template and retrieving that content in a different template (such
as the layout). To set content, you call content_for with a block:
<% content_for :foo do %>
content for foo
<% end %>
To retrieve content, you call content_for without a block:
<%= content_for :foo %>
This plugin probably only works when using erb templates.
* A not_allowed plugin has been added, for automatically returning 405
Method Not Allowed responses when a route is handled for a different
request method than the one used. For this routing tree:
plugin :not_allowed
route do |r|
r.get "foo" do
end
end
If you submit a POST /foo request, it will return a 405 error
instead of a 404 error.
This also handles cases when multiple methods are supported for
a single path, so for this routing tree:
route do |r|
r.is "foo" do
r.get do
end
r.post do
end
end
end
If you submit a DELETE /foo request, it will return a 405 error
instead of a 404 error.
* A head plugin has been added, automatically handling HEAD requests
the same as GET requests, except returning an empty body. So for
this routing tree:
plugin :head
route do |r|
r.get "foo" do
end
end
A request for HEAD /foo will return a 200 result instead of a 404
error.
* A backtracking_array plugin has been added, which makes matching
backtrack to the next entry in an array if a later matcher fails.
For example, the following code does not match /foo/bar by
default in Roda:
r.is ['foo', 'foo/bar'] do
end
This is because the 'foo' entry in the array matches, so the
array matches. However, after the array is matched, the terminal
matcher added by r.is fails to match. That causes the routing
method not to match the request, so the match block is not called.
With the backtracking_array plugin, failures of later matchers after
an array matcher backtrack so the next entry in the array is tried.
* A per_thread_caching plugin has been added, allowing you to change
from a thread-safe shared cache to a per-thread cache, which may
be faster on alternative ruby implementations, at the cost of
additional memory usage.
= New Features
* The hash_matcher class method has been added to make it easier to
define custom hash matchers:
hash_matcher(:foo) do |v|
self['foo'] == v
end
route do |r|
r.on :foo=>'bar' do
# matches when param foo has value bar
end
end
* An r.root routing method has been added for handling GET
requests where the current path is /. This is basically
a faster and simpler version of r.get "", except it does
not consume the / from the path.
* The r.halt method now works without an argument, in which
case it uses the current response.
* The r.redirect method now works without an argument for non-GET
requests, redirecting to the current path.
* An :all hash matcher has been added, which takes an array and
matches only if all of the elements match. This is mainly
designed for usage inside an array matcher, so:
r.on ["foo", {:all=>["bar", :id]}] do
end
will match either /foo or /bar/123, but not /bar.
* The render plugin's view method now accepts a :content option,
in which case it uses the content directly without running it
through the template engine. This is useful if you have
arbitrary content you want rendered inside the layout.
* The render plugin now accepts an :escape option, in which case
it will automatically set the default :engine_class for erb
templates to an Erubis::EscapedEruby subclass. This changes the
behavior of erb templates such that:
<%= '' %> # <escaped>
<%== '' %> #
This makes it easier to protect against XSS attacks in your
templates, as long as you only use <%== %> for content that has
already been escaped.
Note that similar behavior is available in Erubis by default,
using the :opts=>{:escape_html=>true} render option, but that
doesn't handle postfix conditionals in <%= %> tags.
* The multi_route plugin now has an r.multi_route method, which
will attempt to dispatch to one of the named routes based on
first segment in the path. So this routing tree:
plugin :multi_route
route "a" do |r|
r.is "c" do
"e"
end
end
route "b" do |r|
r.is "d" do
"f"
end
end
route do |r|
r.multi_route
end
will return "e" for /a/c and "f" for /b/d.
* Plugins can now override request and response class methods
using RequestClassMethods and ResponseClassMethods modules.
= Optimizations
* String, hash, and symbol matchers are now much faster by caching
the underlying regexp.
* String, hash, and symbol matchers are now faster by using a
regexp positive lookahead assertion instead of an additional
capture.
* Terminal matching in the r.is, r.get, and r.post routing methods
is now faster, as it does not use a hash matcher internally.
* The routing methods are now faster by reducing the number of
Array objects created.
* Calling routing methods without arguments is now faster.
* The r.get method is now faster by reducing the number of string
allocations.
* Many request methods are faster by reducing the number of
method calls used.
* Template caching no longer uses a mutex on MRI, since one is
not needed for thread safety there.
= Other Improvements
* The flash plugin now implements its own flash hash instead of
using sinatra-flash. It is now slightly faster and handles nil
keys in #keep and #discard.
* Roda's version is now stored in roda/version.rb so that it can be
required without requiring Roda itself.
= Backwards Compatibility
* The multi_route plugin's route instance method has been changed
to a request method. So the new usage is:
plugin :multi_route
route "a" do |r|
end
route do |r|
r.route "a" # instead of: route "a"
end
* The session key used for the flash hash in the flash plugin is
now :_flash, not :flash.
* The :extension matcher now longer forces a terminal match, use
one of the routing methods that forces a terminal match if you
want that behavior.
* The :term hash matcher has been removed.
* The r.consume private method now takes the exact regexp to use
to search the current path, it no longer enforces a preceeding
slash and that the match end on a segment boundary.
* Dynamically constructing match patterns is now a potential
memory leak due to them being cached. So you shouldn't do
things like:
r.on r['param'] do
end
* Many private routing methods were changed or removed, if you were
using them, you'll probably need to update your code.
jeremyevans-roda-4f30bb3/doc/release_notes/1.1.0.txt 0000664 0000000 0000000 00000016707 15167207754 0022216 0 ustar 00root root 0000000 0000000 = New Plugins
* An assets plugin has been added, for rendering your CSS and
javascript asset files on the fly in development, and compiling
them to a single, compressed file in production.
When loading the plugin, you just specify the assets to use via :css
and/or :js options:
plugin :assets, :css=>'some_file.scss', :js=>'some_file.coffee'
Inside your Roda.route block, you call r.assets to serve the assets:
route do |r|
r.assets
end
In your views, you can call the assets method, which returns strings
containing link/script tags for your assets:
<%= assets(:css) %>
<%= assets(:js) %>
In production mode, you call compile_assets after loading the
plugin, and it will compile all of the asset files into a single
file per type, optionally compress it (using yuicompressor), and
write the file to the public folder where it can be served by the
webserver. In compiled mode, calling assets in your views will
reference the compiled file.
It's possible to precompile your assets before application boot, so
they don't need to be compiled every time your application boots.
The assets plugin also supports asset groups, useful when different
sections of your application use different sets of assets.
* A chunked plugin has been added, for streaming template rendering
using Transfer-Encoding: chunked. By default, this flushes the
rendering of the top part of the layout template before rendering
the content template, allowing the client to load the assets
necessary to fully render the page while the content template is
still being rendered on the server. This can significantly decrease
client rendering times.
To use chunked encoding for a response, just call the chunked method
instead of view:
r.root do
chunked(:index)
end
If you want to execute code after flushing the top part of the layout
template, but before rendering the content template, pass a block to
chunked:
r.root do
chunked(:index) do
# expensive calculation here
end
end
If you want to use chunked encoding for all responses, pass the
:chunk_by_default option when loading the plugin:
plugin :chunked, :chunk_by_default => true
Inside your layout or content templates, you can call the flush method
to flush the current result of the template to the user, useful for
streaming large datasets.
<% (1..100).each do |i| %>
<%= i %>
<% sleep 0.1 %>
<% flush %>
<% end %>
* A caching plugin has been added, for simple HTTP caching support.
The implementation is based on Sinatra's, and offers
r.last_modifed and r.etag methods for conditional responses:
r.get '/albums/:d' do |album_id|
@album = Album[album_id]
r.last_modified @album.updated_at
r.etag @album.sha1
view('album')
end
This also adds response.cache_control and response.expires methods
for setting the Cache-Control/Expires headers for the response.
* A path plugin has been added for simple support for named paths:
plugin :path
path :foo, '/foo' # foo_path => '/foo'
path :bar do |bar| # bar_path(bar) => '/bar/1'
"/bar/#{bar.id}"
end
* An error_email plugin has been added, for easily emailing error
notifications for an exception. This is designed for use with
the error_handler plugin, and should only be used in low-traffic
environments:
plugin :error_email, :to=>'to@example.com',
:from=>'from@example.com'
plugin :error_handler do |e|
error_email(e)
'Internal Server Error'
end
= multi_route Plugin Improvements
* The multi_route plugin now supports namespaces, allowing it to
support routing trees of arbitrary complexity. Previously, only
a single namespace was supported. For example, if you want
to store your named routes in a directory tree:
/routes/foo.rb
/routes/foo/baz.rb
/routes/foo/quux.rb
/routes/bar.rb
/routes/bar/baz.rb
/routes/bar/quux.rb
You can load all of the routing files in the routes subdirectory
tree, and structure your routing tree as follows:
# app.rb
route do |r|
r.multi_route
end
# routes/foo.rb
route('foo') do |r|
check_foo_access!
r.multi_route('foo')
end
# routes/bar.rb
route('bar') do |r|
check_bar_access!
r.multi_route('bar')
end
# routes/foo/baz.rb
route('baz', 'foo') do
# ...
end
* Newly added named routes are now picked up while running, useful in
development when using code reloading.
* r.multi_route now ignores non-String named routes, allowing you to
only dispatch to the String named routes. Previously, calling
r.multi_route when any non-String names routes were present resulted
in an error.
* r.multi_route now prefers longer routes to shorter routes if
routes have the same prefix. This can fix behavior if you have
named routes such as "foo" and "foo/bar".
* If you don't pass a block to r.multi_route, it will use the
return value of the named route as the block value to return,
instead of always returning nil.
= Optimizations
* Dispatch speed is slightly improved by using allocate instead of
new to create new instances.
* Hash allocations in the render plugin have been reduced.
= Other Improvements
* The Roda.route block is now inherited when subclassing, making
it possible to subclass a Roda application and have the subclass
work without adding a route block.
* Middleware added to a Roda app after the Roda.route method is
called are now used instead of being ignored.
* A response.finish_with_body method has been added, for overriding
the response body to use. This is useful if you want to support
arbitrary response bodies.
* The render plugin now defaults the locals argument to an empty
frozen hash instead of nil when rendering templates via tilt.
This is faster as it avoids a hash allocation inside tilt, and
also works with broken external tilt templates that require that
the locals argument be a hash.
* Plugins that ship with Roda no longer set constants inside
InstanceMethods. Instead, the constants are set at the plugin
module level. This is done to avoid polluting the namespace of
the application with the constants. Roda's policy is that all
internal constants inside the Roda namespace are prefixed with
Roda, so they don't pollute the user's namespace, and setting
these constants inside InstanceMethods in plugins violated that
policy.
= Backwards Compatibility
* response.write no longer sets a Content-Length header. Instead,
response.finish sets it. This is faster if you call
response.write multiple times, and more correct if you call
response.finish without calling response.write.
* In the render plugin, modifying render_opts directly is now
deprecated and will raise an error in the next major release (the
hash will be frozen). Instead, users should call plugin :render
again with a new hash, which will be merged into the existing
render_opts hash.
* Moving plugin's constants from InstanceMethods to the plugin level
can break applications where the constant was referenced directly.
For example, if you were doing:
Roda::SESSION_KEY
to get the constant for the session key, you would now need to use:
Roda::RodaPlugins::Base::SESSION_KEY
In general, it is recommended to not reference such constants at
all. If you think there should be a general reason to access them,
request that a method is added that returns them.
jeremyevans-roda-4f30bb3/doc/release_notes/1.2.0.txt 0000664 0000000 0000000 00000031346 15167207754 0022213 0 ustar 00root root 0000000 0000000 = New Plugins
* A static_path_info plugin has been added, which doesn't modify
SCRIPT_NAME/PATH_INFO during routing, only before dispatching
the request to another rack application via r.run. This is
faster and avoids problems caused by changing SCRIPT_NAME/PATH_INFO
during routing, such as methods that return paths that depend on
SCRIPT_NAME. This behavior will become Roda's default starting
in Roda 2, and it is recommended that all Roda apps use it.
* A mailer plugin has been added, which allows you to use Roda's
render plugin to create email bodies, and allows you to use Roda's
routing tree features to DRY up your mailing code similar to how it
DRYs up your web code.
Here is an example routing tree using the mailer plugin:
class Mailer < Roda
plugin :render
plugin :mailer
route do |r|
r.on "user/:d" do |user_id|
# DRY up code by setting shared behavior in higher level
# branches, instead of duplicating it inside each subtree.
@user = User[user_id]
from 'notifications@example.com'
to @user.email
r.mail "open_account" do
subject 'Welcome to example.com'
render(:open_account)
end
r.mail "close_account" do
subject 'Thank you for using example.com'
render(:close_account)
end
end
end
end
With your routing tree setup, you can use the sendmail method to
send email:
Mailer.sendmail("/user/1/open_account")
If you want a Mail::Message object returned for further modification
before sending, you can use mail instead of of sendmail:
Mailer.mail("/user/2/close_account").deliver
* A delegate plugin has been added, allowing you to easily create
methods in the route block scope that delegate to the request or
response. While Roda does not pollute your namespaces by default,
this allows you to choose to do so yourself if you find it offers
a nicer API. Example:
class App < Roda
plugin :delegate
request_delegate :root, :on, :is, :get, :post, :redirect
route do |r|
root do
redirect "/hello"
end
on "hello" do
get "world" do
"Hello world!"
end
is do
get do
"Hello!"
end
post do
puts "Someone said hello!"
redirect
end
end
end
end
end
* A class_level_routing plugin has been added, allowing you to define
your routes at the class level if desired. The routes defined at
the class level can still use a routing tree for further routing.
Example:
class App < Roda
plugin :class_level_routing
root do
request.redirect "/hello"
end
get "hello/world" do
"Hello world!"
end
is "hello" do
request.get do
"Hello!"
end
request.post do
puts "Someone said hello!"
request.redirect
end
end
end
* A named_templates plugin has been added, for creating inline
templates associated with a given name, that are used by
the render plugin's render/view method in preference to
templates stored in the filesystem. This makes it simpler to
ship single-file Roda applications that use templates. Example:
class App < Roda
plugin :named_templates
template :layout do
"<%= yield %>"
end
template :index do
"Hello <%= @user %>!
"
end
route do |r|
@user = 'You'
render(:index)
end
# => "Hello You!
"
end
* A multi_run plugin has been added, for dispatching to multiple
rack applications based on the request path prefix. This
provides a similar API as the multi_route plugin, but allows
you to separate your applications per routing subtree, as
opposed to multi_route which uses the same application for
all routing subtrees.
With the multi_run plugin, you call the class level run method
with the routing prefix and the rack application to use, and
you call r.multi_run to dispatch to all of the applications
based on the prefix.
class App < Roda
plugin :multi_run
run "foo", Foo
run "bar", Bar
run "baz", Baz
route do |r|
r.multi_run
end
end
In this case, Foo, Bar, and Baz, can be subclasses of App, which
allows them to share methods that should be shared, but still
define methods themselves that are not shared by the other
applications.
* A sinatra_helpers plugin has been added, that ports over most
of the Sinatra::Helpers methods that haven't already been added
by other plugins. All of the methods are added either to the
request or response class as appropriate. By default, delegate
methods are also added to the route block scope, but you can
turn this off by passing a :delegate=>false option when loading
the plugin, which avoids polluting the route block namespace.
The sinatra_helpers plugin adds the following request methods:
* back
* error
* not_found
* uri
* send_file
And the following response methods:
* body
* body=
* status
* headers
* mime_type
* content_type
* attachment
* informational?
* success?
* redirect?
* client_error?
* not_found?
* server_error?
* A slash_path_empty plugin has been added, which changes Roda
so that "/" is considered an empty path when doing a
terminal match via r.is or r.get/r.post with a path.
class App < Roda
plugin :slash_path_empty
route do |r|
r.get "albums" do
# matches both GET /albums and GET /albums/
end
end
end
* An empty_root plugin has been added, which makes r.root match
the empty string, in addition to /. This can be useful in
cases where a partial match on the patch has been completed.
class App < Roda
plugin :empty_root
route do |r|
r.on "albums" do
r.root do
# matches both GET /albums and GET /albums/
end
end
end
end
* A match_affix plugin has been added, for overriding the default
prefix/suffix used in match patterns. For example, if you want
to require that a leading / be specified in your routes. and
you want to consume any trailing slash:
class App < Roda
plugin :match_affix, "", /(\/|\z)/
route do |r|
r.on "/albums" do |s|
# GET /albums # s => ""
# GET /albums/ # s => "/"
end
end
end
* An environments plugin has been added, giving some simple
helpers for executing code in different environments. Example:
class App < Roda
plugin :environments
environment # => :development
development? # => true
test? # => false
production? # => false
# Set the environment for the application
self.environment = :test
test? # => true
configure do
# called, as no environments given
end
configure :development, :production do
# not called, as no environments match
end
configure :test do
# called, as environment given matches current environment
end
end
* A drop_body plugin has been added, which automatically drops the
body, Content-Type header, and Content-Length header when the
response status indicates no body (100-102, 204, 205, 304).
* A delay_build plugin has been added, which delays building the
rack application until Roda.app is called, and only rebuilds the
rack application if build! is called. This removes O(n^2)
performance in the pathological case of adding a route block
and then calling Roda.use many times to add middlewares, though
you have to add a few hundred middlewares for the difference
to be noticeable.
= New Features
* r.remaining_path and r.matched_path have been added for returning
the remaining path that will be used for matching, and for
returning the path already matched. Currently, these just provide
the PATH_INFO and SCRIPT_NAME, but starting in Roda 2 PATH_INFO
and SCRIPT_NAME will not be modified during routing, and you'll
need to use these methods if you want to find out the remaining
or already matched paths.
* The render plugin now supports a :template option to render/view
to specify the template to use, instead of requiring a separate
argument.
* The render plugin now supports a :template_class option, allowing
you to override the default template class that Roda would use.
* The render plugin now supports a :template_block option, specifying
the block to pass when creating a template.
* The path class method added by the path plugin now accepts :name,
:url, :url_only, and :add_script_name options:
:name :: Specifies name for method
:url :: Creates a url method in addition to a path method
:url_only :: Only creates a url method, not a path method
:add_script_name :: prefixes the path with SCRIPT_NAME
Note that if you plan to use :add_script_name, you should use
the static_path_info plugin so that the method created does not
return different results depending on where you are in the
routing tree.
* A :user_agent hash matcher has been added to the header_matchers
plugin.
* An inherit_middleware class accessor has been added. This can
be set to false if you do not want subclasses to inherit
middleware from the superclass. This is useful if the
superclass dispatches to the subclass via r.run, as otherwise
it would have to run the same middleware stack twice.
* A clear_middleware! class accessor has been added, allowing
you to clear the current middleware stack.
* RodaRequest#default_redirect_status has been added, allowing
plugins to override the default status used for redirect if
a status is not given.
* Roda{Request,Response}#roda_class has been added, which
returns the Roda class related to the given request/response.
= Other Improvements
* The render plugin no longer caches templates by default if
RACK_ENV is development.
* When subclassing a Roda app, unfrozen Array/Hash entries in the
opts hash are now duped into the subclass, so the subclass
no longer needs to dup them manually. Note that plugins that
use nested arrays/hashes in the opts hash still need to dup
manually inside ClassMethods#inherited. For the plugins where
it is possible, it is recommended to store plugin options in a
frozen object in the opts hash, and require loading the plugin
again to modify the plugin options.
* Caching of templates is now fixed when the render/view :opts is
used to specify template options per-call.
* An explicit :default_encoding of nil in the render plugin's
:opts hash is no longer overwritten with
Encoding.default_external.
* Roda#session now returns the same object as RodaRequest#session.
* The view_subdirs, content_for, and render_each plugins now all
depend on the render plugin.
* The not_allowed plugin now depends on the all_verbs plugin.
* Local/instance variables are now used in more places instead of
method calls, improving performance.
= Backwards Compatibility
* The render plugin's render/view methods no longer pass the given
hash directly to the underlying template. To pass options to the
template engine, use a separate hash under the :opts key:
render :file, :opts=>{:foo=>'bar'}
This is more consistent with the class-level render plugin options,
which also uses :opts to pass options to the template engine.
The :js_opts and :css_opts options to the assets plugin are now
passed as the :opts hash, so they continue to affect the template
engine, so they no longer specify general render method options.
* Modifying render_opts :layout after loading the render plugin
now has no effect. You need to use plugin :render, :layout=>'...'
to set the layout to use now.
* Default headers are not set on a response until the response is
finished. This allows you to check for header presence during
routing to detect whether the header was specifically set for the
current request.
* RodaRequest.consume_pattern no longer captures anything by default.
Previously, it did so in order to update SCRIPT_NAME, but that is
now handled differently. This should only affect external plugins
that attempt to override RodaRequest#consume.
* RodaRequest.def_verb_method has been removed.
* The hooks, default_headers, json, and multi_route plugins all store
their class-level metadata in the opts hash instead of separate
class instance variables. This should have no affect unless you
were accessing the class instance variables directly.
* The render plugin internals changed significantly, it now passes
internal data using a hash. This should only affect users that
were overriding render plugin methods.
jeremyevans-roda-4f30bb3/doc/release_notes/1.3.0.txt 0000664 0000000 0000000 00000010215 15167207754 0022204 0 ustar 00root root 0000000 0000000 = Preparation for Roda 2
* In Roda 2 (the next version), the PATH_INFO and SCRIPT_NAME
env variables will not be modified during routing. Instead,
Roda will use the static_path_info plugin behavior by default.
Users are strongly encouraged to use the static_path_info
plugin to make sure their apps will work with Roda 2.
* In Roda 2, Roda#initialize will take the env hash, and #call
will take the route block. The private #_route method will be
eliminated. This should not affect applications, but it will
affect plugins that override these methods.
* The issues mentioned below should all have deprecation warnings,
except where noted.
== New Plugins to Replace Deprecated Features
* RodaResponse#set_cookie and #delete_cookie have been moved to the
cookies plugin.
* Roda.request_module and .response_module have been moved to the
module_include plugin.
* Roda.hash_matcher has been moved to the hash_matcher plugin.
* The :extension hash matcher has been moved to the path_machers
plugin. This plugin also contains new :prefix and :suffix hash
matchers.
* The :param and :param! hash matchers have been moved to the
param_matchers plugin.
== Other Deprecation Issues
* The :opts render plugin option and :opts option to render and
view are now deprecated. Use the :template_opts option instead.
* RodaRequest#full_path_info has been deprecated, switch to using
#path.
* Mutating plugin option hashes for the chunked, default_headers,
error_email, json, and render plugins is now deprecated, these
option hashes will be frozen in Roda 2.
* The :header hash matcher in the header_matchers plugin now
gives a deprecation warning, because in Roda 2, the matcher will
yield the value of the header to the block. To get the new
behavior and silence the deprecation warning, you need to set an
option:
Roda.opts[:match_header_yield] = true
* Mutating json_result_classes directly in the json plugin is now
deprecated. You should now pass a :classes option to the plugin
specifying the classes to convert, if you want to handle classes
other than Array and Hash. There will not be a deprecation warning
if you attempt to mutate json_result_classes.
= New Features
* The Roda.freeze method now freezes internal datastructures to avoid
thread safety issues. The plugins that ship with Roda will freeze
their datastructures when Roda.freeze is called. It is recommended
that production applications call freeze on the application after
fully loading it, especially if they are using a threaded webserver
and a non-MRI ruby.
* A delete_empty_headers plugin has been added that automatically
deletes headers set to the empty string. This makes it simpler to
delete default headers if they shouldn't be set for a specific
request.
* A class_delegate method has been added to the delegate plugin. This
makes it easier to create instance methods that call class methods.
* Roda::RodaMajorVersion, RodaMinorVersion, and RodaPatchVersion
constants have been added.
= Other Improvements
* The error_handler plugin now uses a new response instead of reusing
the existing response. This fixes cases where changing the
Content-Type and then raising an exception would result in a error
page that used the previously set Content-Type.
* The not_found plugin now clears previous set headers before calling
not found. This fixes cases where Content-Type was set previously,
and also fixes an incorrect Content-Length being used for the
response.
* The static_path_info plugin now restores the original SCRIPT_NAME
and PATH_INFO before returning from r.run, fixing usage with some
middleware.
* The multi_run plugin now works when subclassing the app.
* The default_headers plugin is now faster by skipping an unnecessary
hash duplication.
* A Gemfile has been added to make development slightly easier.
= Backwards Compatibility
* RodaResponse is no longer a subclass of Rack::Response, it is now a
subclass of Object. This shouldn't have an effect unless you were
calling a method on an instance that was defined by Rack::Response
and not RodaResponse, but most of those methods would have raised
exceptions.
jeremyevans-roda-4f30bb3/doc/release_notes/2.0.0.txt 0000664 0000000 0000000 00000004673 15167207754 0022215 0 ustar 00root root 0000000 0000000 = Backwards Compatibility
* RodaResponse#set_cookie and #delete_cookie have been removed.
* Roda.request_module and .response_module have been removed.
* Roda.hash_matcher has been removed.
* The :extension hash matcher has been removed.
* The :param and :param! hash matchers have been removed.
* RodaRequest#full_path_info has been removed.
* The :opts render plugin option is no longer respected, Use the
:template_opts option instead.
* Plugin option hashes for the chunked, default_headers,
error_email, and render plugins are now frozen.
* The :header hash matcher in the header_matchers plugin now
yields the header value to the block.
* Roda.json_result_classes in the json plugin is now frozen.
* The PATH_INFO and SCRIPT_NAME env variables are no longer modified
during routing.
* Roda#initialize now takes an env hash, and #call now takes the
route block. The private #_route method has been removed.
* RodaRequest#keep_remaining_path/#updating_remaining_path private
methods have been removed.
* The render plugin's :layout option is now always set to true or
false, specifying whether a layout should be used by default.
The template used for a layout is now located as the :template
option inside :layout_opts.
= New Plugins
* A padrino_render plugin has been added, which adds render/partial
methods that work similarly to Padrino's.
= Other New Features
* A Roda#render_template private method has been added to the render
plugin. All internal users of render should switch to calling
render_template.
* The halt plugin now integrates with the symbol_views and json
plugins, allowing things like:
r.halt(:template)
r.halt('key'=>'value')
= Other Improvements
* The error_handler plugin now rescues ScriptError in addition to
StandardError. This handles SyntaxError (raised by ERB),
LoadError (raised by require), and NotImplementedError (raised
by TSort).
* Using a :layout=>true option to the render plugin's view method
now uses the default layout template, instead of a template named
'true'. It can be used to force a layout even if the render
plugin has been configured to not use a layout by default.
* Roda apps that use the middleware plugin can now be used as regular
rack apps. Previously, using the middleware plugin made it
impossible to use the app as a regular rack app.
* Roda#request and #response are now faster.
* Roda avoids creating unnecessary hashes in more places now.
jeremyevans-roda-4f30bb3/doc/release_notes/2.1.0.txt 0000664 0000000 0000000 00000010631 15167207754 0022205 0 ustar 00root root 0000000 0000000 = New Plugins
* A view_options plugin has been added, for branch/route specific
setting of view and layout options and locals. This allows for
DRYer code when you want to change the view or layout settings
for an entire routing branch. Options and locals set at the
branch or route level have higher priority than those set at
the plugin level, but lower priority than those provided as
arguments to the render/view methods. Example:
class App < Roda
plugin :view_options
route do |r|
r.on 'albums' do
layout_options :template=>'layouts/3_columns'
layout_locals :heading=>'Albums'
view_options :ext=>'haml'
view_locals :name=>'Foo'
# ...
end
end
end
The view_options plugin is also a superset of the previous
view_subdirs plugin, and attempts to load view_subdirs will
now load view_options. In addition to set_view_subdir, the
view_options plugin now supports append_view_subdir, which
will append a subdirectory to an existing subdirectory, which
makes it simpler to deal with nested view file hierarchies.
* A static plugin has been added for easily serving static files
using Rack::Static. Example:
class App < Roda
plugin :static, ['/js', '/css']
# or:
plugin :static, ['/js', '/css'], :root=>'pub'
end
= Other New Features
* Roda now supports a :root option for the application that sets
the root directory. This is useful if the application's files
are not stored in the process's working directory, which is
common for processes containing of multiple Roda applications.
By setting the :root option, plugins that use the file system
will default to making relative paths relative to the :root
option instead of the process's working directory. The
assets, render, and static plugins currently support the :root
option. Example:
class App < Roda
opts[:root] = File.dirname(__FILE__)
end
* Roda now supports an :add_script_name option for the application,
which makes plugins automatically prepend the SCRIPT_NAME for the
request's environment to any paths created. This allows Roda
applications to work transparently whenever they are mounted
inside of another rack application.
The assets and path plugins currently recognize the
:add_script_name option. Example:
class App < Roda
opts[:add_script_name] = true
end
* The path plugin now adds a Roda#path method, which creates paths
based on the type of argument used. You can register classes
with the path plugin by providing Roda.path with a class, which
will cause Roda#path to recognize them and handle them accordingly.
Example:
class App < Roda
plugin :path
path(Track){|track| "/albums/#{track.album_id}/tracks/#{track.number}"}
route do
r.get 'tracks/:id' do |track_id|
r.redirect(path(Track[track_id]))
end
end
end
= Other Improvements
* add_file in the mailer plugin now adds the files after the email
body instead of before. This fixes some issues where the email
body would end up empty, due to issues with the mail gem's API.
add_file now accepts a block, and the block is called after the
file has been attached. Among other things, this allows you to
change the content_type for an attached file:
add_file 'path/to/file' do
response.mail.attachments.last.content_type = 'text/foo'
end
* r.multi_route in the multi_route plugin now works if there are
no named routes defined.
* A render plugin :locals option is now respected, setting defaults
to use for locals in views. Additionally, a :locals option in
the :layout_opts option is now respected for setting locals in
layouts. If both the render plugin option is set and :locals is
passed to render/view, the two will be merged together.
Previously, providing a :locals option to render/view would cause
the plugin level option to be ignored.
= Backwards Compatibility
* Using the render plugin :layout=>nil option now removes any
layout template set previously using :layout. Previously, the
layout template would still be kept, but it would not be used
by default.
* Accessing attachments after adding a file using add_file in the
mailer plugin no longer works, as the adding is now delayed until
after the body is set. You should now pass a block to add_file
if you want to access the attachment after it has been added.
jeremyevans-roda-4f30bb3/doc/release_notes/2.10.0.txt 0000664 0000000 0000000 00000001541 15167207754 0022265 0 ustar 00root root 0000000 0000000 = New Features
* The json plugin now accepts a :content_type option, which will
override the default Content-Type response header used for
responses.
* Stream#write has been added to the streaming plugin, allowing
the following type of code to work:
stream do |out|
IO.copy_stream(StringIO.new(content), out)
end
= Other Improvements
* Roda now works with ruby 2.3's --enable-frozen-string-literal,
and all of the library files are set to use frozen string
literals by default.
Most of roda's plugin-specific dependencies were found to have
issues with frozen string literals, and while pull requests have
been sent to fix the issues, it's unlikely that you would
currently be able to use --enable-frozen-string-literal in
production.
* The json plugin will no longer override a Content-Type header if
one is already set.
jeremyevans-roda-4f30bb3/doc/release_notes/2.11.0.txt 0000664 0000000 0000000 00000004145 15167207754 0022271 0 ustar 00root root 0000000 0000000 = New Features
* A params_capturing plugin has been added, which makes string and
symbol matchers update the request params with the value of the
captured segments, using the matcher as the key:
plugin :params_capturing
route do |r|
# GET /foo/123/abc/67
r.on("foo/:bar/:baz", :quux) do
r[:bar] #=> '123'
r[:baz] #=> 'abc'
r[:quux] #=> '67'
end
end
Note that this updating of the request params using the matcher as
the key is only done if all arguments to the matcher are symbols
or strings.
All matchers will update the request params by adding all
captured segments to the captures key, including
symbol and string matchers:
r.on(:x, /(\d+)\/(\w+)/, ':y') do
r[:x] #=> nil
r[:y] #=> nil
r[:captures] #=> ["foo", "123", "abc", "67"]
end
Note that the request params captures entry will be appended to with
each nested match:
r.on(:w) do
r.on(:x) do
r.on(:y) do
r.on(:z) do
r[:captures] # => ["foo", "123", "abc", "67"]
end
end
end
end
Note that any existing params captures entry will be overwritten
by this plugin. You can use r.GET or r.POST to get the underlying
entry, depending on how it was submitted.
Also note that the param keys are actually stored in r.params as
strings and not symbols (r[] converts the argument to a string
before looking it up in r.params).
Also note that this plugin will not work correctly if you are using
the symbol_matchers plugin with custom symbol matching and are using
symbols that capture multiple values or no values.
* A :scope option is now supported by render/view in the render plugin,
which allows you to specify the object in which context to evaluate
the template.
= Other Improvements
* The assets plugin's support for the Minjs javascript minifier now
supports the latest version (0.4.1).
= Backwards Compatibility
* Support for Minjs <0.4.0 has been dropped from the assets plugin.
Please upgrade Minjs at the same time you upgrade Roda if you are
using both of them.
jeremyevans-roda-4f30bb3/doc/release_notes/2.12.0.txt 0000664 0000000 0000000 00000002612 15167207754 0022267 0 ustar 00root root 0000000 0000000 = New Features
* An optimized_string_matchers plugin has been added, which contains
optimized matchers for single strings. r.on_branch is an optimized
version of r.on and r.is_exactly is optimized version of r.is:
plugin :optimized_string_matchers
route do |r|
r.on_branch "x" do
# matches /x and paths starting with /x/
r.is_exactly "y" do
# matches /x/y
end
end
end
Both of these methods will work even if the strings have placeholders,
but no captures will be yielded to the blocks.
* The error_handler plugin now has access to the request's
remaining_path when handling an error. You can then compare the
remaining_path to path_info to see how much of request was already
routed, which can be useful when reporting errors.
= Other Improvements
* String matching for strings without placeholders is now 60% faster
as it uses optimized string operations instead of a regexp match.
* Symbol matching is now 60% faster as it uses optimized string
operations instead of a regexp match.
= Backwards Compatibility
* The match methods no longer automatically reset the remaining_path
via ensure. This means that using non-local jumps out of the code
such as begin/rescue and throw/catch will not reset remaining_path
automatically. Users that want to reset remaining path in
such cases should use their own ensure blocks.
jeremyevans-roda-4f30bb3/doc/release_notes/2.13.0.txt 0000664 0000000 0000000 00000001003 15167207754 0022261 0 ustar 00root root 0000000 0000000 = New Features
* The render plugin now supports :check_paths and :allowed_paths
options. Setting :check_paths to true will turn on path checking of
template files. By default, template files are required to be in
the :views directory, otherwise an exception will be raised. Using
the :check_paths option can prevent security issues when template
names are derived from user input. The :allowed_paths option
overrides which path prefixes are allowed. In Roda 3, :check_paths
will default to true.
jeremyevans-roda-4f30bb3/doc/release_notes/2.14.0.txt 0000664 0000000 0000000 00000002511 15167207754 0022267 0 ustar 00root root 0000000 0000000 = New Features
* A symbol_status plugin has been added for using symbolic status names
in response.status=:
class App < Roda
plugin :symbol_status
route do |r|
r.is "needs_authorization"
response.status = :unauthorized
end
end
= Other Improvements
* The middleware plugin will now also run the application's middleware
when the application is used as middleware. For example, if you
have the following code in your config.ru file:
class App < Roda
plugin :csrf
plugin :middleware
route{}
end
use App
previously, the csrf protection would not be enforced, as it uses a
middleware instead of being part of the application. Now, csrf
protection will be enforced. This change makes it so the Roda
application operates the same way regardless of whether it is run
as the rack application or used as rack middleware.
Because of this change, if you are nesting roda applications using
the middleware plugin, you may need to use the middleware plugin's
:env_var option to specify the environment variable used to
indicate to the Roda application that it is being run as middleware.
= Backwards Compatibility
* See above changes to the middleware plugin if you are using
middleware inside a Roda application that uses the middleware
plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/2.15.0.txt 0000664 0000000 0000000 00000003277 15167207754 0022302 0 ustar 00root root 0000000 0000000 = New Features
* A public plugin has been added. This adds an r.public method
for serving all files in the public directory. The public
plugin can replace usage of the static plugin, and is more
flexible. You can place r.public at any point in the routing
tree, and it will use the remaining path to lookup the file
in the public directory. You can also pass the :gzip option
when loading the plugin, and it will serve already gzipped
files to the client if the client supports gzipped transfer
encoding and the file exists with a .gz extension. Example:
plugin :public
route do |r|
# Serve public files before routing
r.public
# ...
end
* The :header matcher added by the header_matchers plugin now
automatically prefixes the key with HTTP_ when looking it up in
the environment, if the :header_matcher_prefix application
option is set. This behavior will probably be the default in
Roda 3.
# Before
r.on :header => 'http_accept'
# Now, with :header_matcher_prefix=>true application option
r.on :header => 'accept'
* The content_for plugin now accepts an :append=>true option to
support appending to the existing content instead of overwriting
the existing content if called multiple times. This behavior
will probably be the default in Roda 3.
# Before
content_for(:form, 'a')
content_for(:form, 'b')
content_for(:form) # => 'b'
# Now, with :append=>true option
content_for(:form, 'a')
content_for(:form, 'b')
content_for(:form) # => 'ab'
= Other Improvements
* The r.send_file method in the sinatra_helpers plugin now works
correctly when using rack 2.
* The specs now run correctly on Windows.
jeremyevans-roda-4f30bb3/doc/release_notes/2.16.0.txt 0000664 0000000 0000000 00000002775 15167207754 0022305 0 ustar 00root root 0000000 0000000 = New Features
* A type_routing plugin has been added. This plugin allows routing
based on the requested type, which can be submitted either via a
file extension or Accept header:
plugin :type_routing
route do |r|
r.get 'a' do
r.html{ "This is the HTML response
" }
r.json{ '{"json": "ok"}' }
r.xml{ "This is the XML response " }
end
end
# /a or /a.html => HTML response
# /a.json => JSON response
# /a.xml => XML response
The response content type is set appropriately when the r.html,
r.json, or r.xml block is yielded to. Using plugin options, you can
add support for custom types, and choose whether to use only file
extensions or only Accept headers for type matching.
* A request_headers plugin has been added. This allows easier access
to request headers. For example, to access a header called
X-My-Header, by default you would need to use the CGI mangled name:
r.env['HTTP_X_MY_HEADER']
The request_headers plugin allows the easier to use:
r.headers['X-My-Header']
* An unescape_path plugin has been added. By default, Roda does not
unescape a URL-encoded PATH_INFO before routing. This plugin allows
URL-encoded PATH_INFO to work, supporting %2f as well as / as path
separators, and having captures return unescaped values:
plugin :unescape_path
route do |r|
# Assume /b/a URL encoded at %2f%62%2f%61
r.on :x, /(.)/ do |*x|
# x => ['b', 'a']
end
end
jeremyevans-roda-4f30bb3/doc/release_notes/2.17.0.txt 0000664 0000000 0000000 00000004425 15167207754 0022300 0 ustar 00root root 0000000 0000000 = New Plugins
* A run_append_slash plugin has been added, which automatically uses
"/" as the path instead of an empty path when calling rack
applications with r.run:
route do |r|
r.on "a" do
r.run App
end
end
# without run_append_slash:
# GET /a => App gets "" as PATH_INFO
# GET /a/ => App gets "/" as PATH_INFO
# with run_append_slash:
# GET /a => App gets "/" as PATH_INFO
# GET /a/ => App gets "/" as PATH_INFO
By default, the path is modified directly, but if you want to
redirect instead, you can pass a :use_redirects option when loading
the plugin.
= New Features
* The assets plugin now supports an :sri option to enable subresource
integrity on the link/script tags generated by the assets helper.
This option takes either :sha256, :sha384, or :sha512 values,
specifying the hash algorithm to use. Roda 3 will default to using
:sri => :sha256.
* The assets plugin now supports a :postprocessor option, which should
be a callable object. If the option is given, it will be called with
the filename, type, and rendered asset output of the file (CSS/JS),
and should return the postprocessed content to use. This allows any
type of custom postprocessing to be done, such as CSS autoprefixing.
* The render plugin now supports a :layout_opts=>:merge_locals option,
which will automatically merge view template locals into layout
template locals. This is useful if you want to use the same local
variable for both templates.
* The error_handler plugin now supports a :classes option, allowing
you to override which exception classes to handle. This allows it
to be used with libraries which use exception classes that
subclass from Exception instead of StandardError.
= Other Improvements
* The type_routing plugin now works correctly when using r.run.
Previously, if the type routing plugin recognized and removed
the file extension used in the requested path, it would not
add the file extension back to the path when passing the request
to another rack app.
= Backwards Compatibility
* In the assets plugin, Roda::RodaRequest.assets_matchers now uses
symbols instead of strings as the first argument in each entry.
This should not affect you unless you were accessing the values
directly.
jeremyevans-roda-4f30bb3/doc/release_notes/2.18.0.txt 0000664 0000000 0000000 00000004461 15167207754 0022301 0 ustar 00root root 0000000 0000000 = New Plugins
* A static_routing plugin has been added, which can give a 3-4x
increase in performance for large number of static routes, and
makes routing O(1) for static routes. Static routes are routes
that match full paths, with no placeholders, and are checked before
using the normal routing tree.
Static routes are defined via class-level static_* routing methods.
There is a static_* routing method for each HTTP verb (e.g.
static_get), as well as a static_route method, which will work
for any HTTP verb, with the verb-specific method taking priority.
By using static_route, you can get significantly faster performance
while retaining some of the benefits of Roda's routing tree design
(simple shared logic with verb specific behavior). Example:
plugin :static_routing
static_route '/foo' do |r|
@var = :foo
r.get do
'Not actually reached'
end
r.post{'static POST /#{@var}'}
end
static_get '/foo' do |r|
'static GET /foo'
end
route do |r|
'Not a static route'
end
Because static routing routes on the full path instead of by
path segment, the methods takes the full path as a string,
including the leading slash.
* An assets_preloading plugin has been added, which makes it simple
to generate HTML link tags or a Link header value to tell the
browser to preload assets for performance reasons.
# In routes, using the Link header:
response.headers['Link'] = preload_assets_link_header(:css)
# In templates, using a link tag:
<%= preload_assets_link_tags(:css) %>
= New Features
* RodaRequest#real_remaining_path has been added. This is designed
to be overridden by plugins that modify remaining_path for internal
routing purposes. RodaRequest#run now uses real_remaining_path
when passing requests to other rack applications.
* An assets_paths method has been added to the assets plugin. This
is similar to the assets method, but it returns an array of paths
to the assets, instead of a HTML link/script tag.
= Other Improvements
* The public plugin now works correctly when used with the
type_routing plugin, for paths ending in extensions that
type_routing is configured to handle.
* The head plugin now works with the not_allowed plugin if it is
loaded after the not_allowed plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/2.19.0.txt 0000664 0000000 0000000 00000002175 15167207754 0022302 0 ustar 00root root 0000000 0000000 = Improvements
* The indifferent_params plugin is now optimized when using Rack 2,
using Rack 2's query_parser API, and it no longer needs to do a
deep copy of the params.
* The Content-Type and Content-Length headers are no longer added
for 1xx, 204, 205, and 304 responses.
* The assets_paths method in the assets plugin now works
correctly when subresource integrity is enabled.
* The asset paths are now escaped in tags by the assets and
assets_preloading plugins. While it's unlikely a developer
would use an asset path that requires escaping, that case is
now handled correctly.
* The h plugin no longer calls Rack::Utils.escape_html, instead
implementing it's own html escaping.
* The assets plugin now uses the h plugin, instead of calling
Rack::Utils.escape_html.
= Backwards Compatibility
* The h plugin's html escaping no longer escapes "/", which is
a behavior change if you are using any recent version of rack.
The security arguments made to escape "/" could be applied to
many other characters, so if you want to escape "/", you should
probably use a separate method that escapes all \W characters.
jeremyevans-roda-4f30bb3/doc/release_notes/2.2.0.txt 0000664 0000000 0000000 00000005551 15167207754 0022213 0 ustar 00root root 0000000 0000000 = New Plugins
* A shared_vars plugin has been added, for sharing variables between
multiple Roda apps. Example:
class API < Roda
plugin :shared_vars
route do |r|
user = shared[:user]
# ...
end
end
class App < Roda
plugin :shared_vars
route do |r|
r.on :user_id do |user_id|
shared[:user] = User[user_id]
r.run API
end
end
end
If you pass a hash to shared, it will update the shared
variables with the content of the hash:
route do |r|
r.on :user_id do |user_id|
shared(:user => User[user_id])
r.run API
end
end
You can also pass a block to shared, which will set the
shared variables only for the given block, restoring the
previous shared variables afterward:
route do |r|
r.on :user_id do |user_id|
shared(:user => User[user_id]) do
r.run API
end
end
end
* The partials method added by the padrino_render plugin has been
extracted into a partials plugin, for users who want to use the
partials method without having render use a layout by default.
= Other New Features
* The render plugin when using the :escape option now additionally
supports an :escape_safe_classes option, which accepts a class or
array of classes that should not be automatically escaped when using
<%= %> tags. This makes easier to integrate with helpers or
external libraries that return already html escaped strings, without
using <%== %> tags.
For complete control, an :escaper option is also supported, which
will be used for escaping <%= %> strings. The value of this option
should be an object that responds to escape_xml with a single
argument and returns an output string.
* A Roda#delay method has been added to the chunked plugin, which
delays execution of the given block until right before the content
template is rendered. This allows you to continue to use the
routing tree to DRY up your code even when streaming template
rendering. Example:
r.on 'albums/:d' do |album_id|
delay do
@album = Album[album_id]
end
r.get 'info' do
chunked(:info)
end
r.get 'tracks' do
chunked(:tracks)
end
end
* The path plugin now supports a :by_name option, which makes lookup
of the class be by name as opposed to by reference. This is
designed for use in development when doing code reloading.
* Roda.path_block has been added to the path plugin for returning the
block associated with the given class.
= Other Improvements
* The default_headers plugin now defaults to the same headers that
Roda uses by default (just the Content-Type header with text/html
value). Previously, it did not set a Content-Type header if you
didn't specify one.
* In the path plugin, Roda#path now works correctly in subclasses.
jeremyevans-roda-4f30bb3/doc/release_notes/2.20.0.txt 0000664 0000000 0000000 00000000323 15167207754 0022263 0 ustar 00root root 0000000 0000000 = New Features
* The render plugin now supports :erubi as an :escape option value,
which will change the plugin to use Erubi instead of Erubis as the
template processor. Erubi is a simplified Erubis fork.
jeremyevans-roda-4f30bb3/doc/release_notes/2.21.0.txt 0000664 0000000 0000000 00000001076 15167207754 0022272 0 ustar 00root root 0000000 0000000 = New Features
* The streaming plugin now supports a handle_stream_error method to
handle exceptions when using stream(:loop=>true). This method
takes the exception and the stream object, and can be used to log
errors, output errors into the stream, close the stream, ignore
errors, or any other error handling.
= Other Improvements
* A couple of unused variable assignments have been removed, providing
a minor speedup.
* The specs no longer produce deprecation warnings when using Minitest
5.10.
* Some verbose warnings have been removed from the specs.
jeremyevans-roda-4f30bb3/doc/release_notes/2.22.0.txt 0000664 0000000 0000000 00000002760 15167207754 0022274 0 ustar 00root root 0000000 0000000 = New Features
* An :unsupported_block_result => :raise option is now supported
for Roda applications. This will raise a RodaError if an
unsupported value is returned from a match block or the
route block. This can make it easier to discover potential
problems in the routing tree. This option may become the
default behavior in Roda 3.
* An :unsupported_matcher => :raise option is now supported for
Roda applications. This will raise a RodaError if you use an
unsupported value as a matcher. This can make it easier to
discover potential problems in the routing tree. This option
may become the default behavior in Roda 3.
* A :verbatim_string_matcher option is now supported for Roda
applications. This will make all string matchers only match
the path verbatim, disallowing the use of a colon for
placeholders in the string. It's recommended that users
switch to using separate symbol arguments for placeholders.
If you enable this option, you need to change code such as:
r.is "foo/:bar" do |bar|
end
to:
r.is "foo", :bar do |bar|
end
If you are looking to convert an existing routing tree
from using placeholders in strings to separate symbol
arguments, you can scan your routing tree for potential
usage of placeholders in strings:
grep ' r\..*['\''"].*:.*['\''"]' app.rb
This option may become the default behavior in Roda 3, with
a plugin added to support the current default behavior of
allowing placeholders in string matchers.
jeremyevans-roda-4f30bb3/doc/release_notes/2.23.0.txt 0000664 0000000 0000000 00000002226 15167207754 0022272 0 ustar 00root root 0000000 0000000 = New Features
* An :explicit_cache option has been added to the render plugin.
This is similar to the :cache=>false option, but instead of
disabling caching completely, this disables caching by default but
allows for explicit caching of templates by providing the :cache
option to view/render.
In development mode, Roda now defaults to :explicit_cache=>true
instead of :cache=>false.
* An :inherit_cache option has been added to the render plugin,
making subclasses of that class start with a dup of the template
cache, instead of starting with an empty template cache. This can
result in less memory used.
* Roda#error_email in the error_email plugin now accepts non-Exception
arguments (such as strings). This can be useful in conditions that
are errors you may want to notify about, where an exception hasn't
been raised.
* Roda#error_email_content has been added to the error_email plugin.
This can be used to create the email message, which can be delivered
via another mechanism, and may make testing easier.
= Other Improvements
* Roda.freeze in the static_routing plugin now returns self, fixing
code such as Roda.freeze.app.
jeremyevans-roda-4f30bb3/doc/release_notes/2.24.0.txt 0000664 0000000 0000000 00000004617 15167207754 0022301 0 ustar 00root root 0000000 0000000 = New Features
* The middleware plugin now accepts a block that can be used to
implement configurable middleware. This allows you to load the
Roda application as middleware in another application, and provide
options/block that are passed to the block you passed when loading
the middleware plugin. Example:
class Mid < Roda
plugin :middleware do |middleware, *args, &block|
middleware.opts[:middleware_args] = args
block.call(middleware)
end
end
class App < Roda
use Mid, :foo, :bar do |middleware|
middleware.opts[:middleware_args] << :baz
end
end
Note that when passing a block when loading the middleware plugin,
using the middleware in another rack application will actually
load a subclass of the middleware (Mid in the example above).
This allows you to use the Roda middleware multiple times in the
same process with different configurations.
* The cookies plugin now accepts options that are used as the default
options when setting and deleting cookies:
plugin :cookies, :path => '/foo', :domain => 'example.com'
* A strip_path_prefix plugin has been added, which can be used to
strip prefixes from internal paths. Internally Sequel stores
most paths as absolute paths, but there are cases where this
doesn't work well, such as symlink changes and chroot. This
plugin supports those scenarios.
* A disallow_file_uploads plugin has been added, which raises
an exception if the user attempts a multipart file upload.
More exactly, it makes the application raise an exception if
attempting to parse a request body when the user has submitted
a multipart file upload. This is useful if you don't need to
support file uploads in your application, or cases where no
paths are writable by the application (due to chroot or system call
filtering).
* The :freeze_middleware option has been added, which freezes all
middleware instances when building the rack application. This
can help find thread safety issues in middleware.
= Other Improvements
* The h plugin is now about 6x faster on ruby 2.3+ due to the use
of cgi/escape.
* The static_routing plugin now works with the hooks plugin if the
hooks plugin is loaded first.
* The render plugin's render cache is no longer cleared when loading
the plugin multiple times.
= Backwards Compatibility
* The h plugin now escapes ' as ' instead of '.
jeremyevans-roda-4f30bb3/doc/release_notes/2.25.0.txt 0000664 0000000 0000000 00000001026 15167207754 0022271 0 ustar 00root root 0000000 0000000 = New Features
* An error_mail plugin has been added for reporting exceptions raised
via email. This is similar to the existing error_email plugin, but
uses the mail library instead of net/smtp directly. If you are
already using the mail library and the error_email plugin in your
application, it's recommended to switch to the error_mail plugin.
Example:
plugin :error_mail, :to=>'to@example.com', :from=>'from@example.com'
plugin :error_handler do |e|
error_mail(e)
'Internal Server Error'
end
jeremyevans-roda-4f30bb3/doc/release_notes/2.26.0.txt 0000664 0000000 0000000 00000000755 15167207754 0022302 0 ustar 00root root 0000000 0000000 = New Features
* The csrf plugin now supports a :skip_middleware option, which adds
the methods without adding the middleware. This is designed for
cases where you are using multiple rack apps, where the rack_csrf
middleware is loaded in an earlier rack app, and you want to avoid
the duplicate CSRF checks.
= Other Improvements
* The type_routing plugin now supports using multiple extensions
where one extension is a suffix of another extension, such as
using gz and tar.gz.
jeremyevans-roda-4f30bb3/doc/release_notes/2.27.0.txt 0000664 0000000 0000000 00000003510 15167207754 0022273 0 ustar 00root root 0000000 0000000 = New Features
* String and Integer class matchers have been added. The
String class matches any non-empty segment and yields it as a
string. This is the same as the behavior of the symbol matchers,
but without the duplication. So instead of:
r.is "album", :album_name do |album_name|
end
you can now do:
r.is "album", String do |album_name|
end
This makes it a bit more intuitive that you want to match
any string, and avoids the redundancy between the symbol
name and block argument name.
The Integer class matches any integer segment (\d+) and yields it
as an integer:
r.is "album", Integer do |album_id|
# does not match "/albums/foo"
# matches "/albums/1", yielding 1 (not "1")
end
Previously, the :d matcher in the symbol_matchers plugin could
be used to only match integer segments, but it yielded results
as strings and not integers, so you still needed to convert the
type manually. Using Integer is a bit more intuitive than
using :d, and it handles the type conversion for you.
* A class_matchers plugin has been added for matching additional
classes, with user-specified regexps and type conversion. For
example, if you want to match YYYY-MM-DD segments and yield
them to the match blocks as ruby Date objects, you can do:
plugin :class_matchers
class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
[Date.new(y.to_i, m.to_i, d.to_i)]
end
and then in your routing tree, you can do:
r.on "posts", Date do |date|
# does not match "/posts/foo" or "/posts/2017-01"
# matches "/posts/2017-01-13", yielding Date.new(2017, 1, 13)
end
= Backwards Compatibility
* If you were using the Integer and String classes as matchers
before and expected them to always match, you'll need to
change your code to use true instead.
jeremyevans-roda-4f30bb3/doc/release_notes/2.28.0.txt 0000664 0000000 0000000 00000001055 15167207754 0022276 0 ustar 00root root 0000000 0000000 = New Features
* A status_303 plugin has been added, which changes the default
redirect status from 302 to 303 if the HTTP version is 1.1 and
the request is not a GET request.
= Other Improvements
* Roda is now optimized for ruby 2.3+ using frozen string literals
instead of constant references. This improves performance on ruby
2.3+, and decreases performance on ruby <2.3.
= Backwards Compatibility
* Many now unused internal constants are now deprecated, and
attempting to access them will result in deprecation warnings
on ruby 2.3+.
jeremyevans-roda-4f30bb3/doc/release_notes/2.29.0.txt 0000664 0000000 0000000 00000014553 15167207754 0022306 0 ustar 00root root 0000000 0000000 = Deprecated Features
Roda 2.29.0 will be the last minor release of Roda 2. Roda 3.0.0
will be released next month and will remove support for the following
deprecated features. All of these features will have deprecation
warnings if used in Roda 2.29.0.
* The use of placeholders in string matchers is now deprecated.
So code such as:
r.get "users/:user_id" do |id|
end
should be switched to using a class matcher such as String or
Integer:
r.get "users", Integer do |id|
end
or a symbol matcher:
r.get "users", :user_id do |id|
end
If you really want to keep support for placeholders in string
matchers, the support is available in the new
placeholder_string_matchers plugin.
* The :format, :opt, and :optd default symbol matchers are now
deprecated in the symbol_matchers plugin. These matchers
only made sense when placeholder string matchers are used,
which will no longer be the default behavior in Roda 3. These
methods can be defined manually if you are going to use the
placeholder_string_matchers plugin and still want to use
these symbol matchers:
symbol_matcher(:format, /(?:\.(\w+))?/)
symbol_matcher(:opt, /(?:\/([^\/]+))?/)
symbol_matcher(:optd, /(?:\/(\d+))?/)
* Ignoring unsupported match block return values is now deprecated.
Doing so can hide errors and make debugging more difficult. If you
get a deprecation warning related to this, just make sure the match
block returns nil or false to specify the match block return value
should be ignored.
* Treating unsupported matchers as always matching is now deprecated.
Doing so can hide errors and make debugging more difficult. If you
get a deprecation warning related to this, switch the matcher to
true instead of an unsupported object.
* The render plugin's handling of plugin level locals and merging of
template and layout locals is now deprecated. Users of these
features should switch to the new render_locals plugin.
* The view_options plugin's handling of per-branch view and layout
locals is now deprecated. Users of these feature should switch to
the new branch_locals plugin.
* The render plugin's support for Erubis escaping is now deprecated.
In Roda 3, the render plugin :escape option will use Erubi escaping.
Switch to using :escape=>:erubi temporarily to avoid the deprecation
warning.
* Using the render plugin to render a template that is outside one of
the allowed paths is now deprecated unless the :check_paths option
has been set to false. In Roda 3, the default behavior will change
to checking that template files are in one of the allowed paths.
* The :ext option in the render plugin is now deprecated, users should
switch to using the :engine option, which has always had priority.
* Using the :cache=>true option to the view/render method in the
render plugin is now deprecated if the :cache=>nil/false option
was given when loading the plugin. In Roda 3, the default behavior
will change so that the :cache=>nil/false plugin option still
allows caching via the :cache=>true method option. Users can use
the :explicit_cache=>true render plugin option instead of the
:cache=>nil render plugin option to work around the deprecation
warning.
* Attempting to use multi_route while routing with a namespace that
hasn't yet been defined is now deprecated. The previous behavior
was to ignore undefined namespaces, but that is more likely to
hide an error than be desired behavior. In Roda 3, using an
undefined namespace will raise an error.
* The streaming plugin's support for EventMachine is now deprecated,
as is related support for Stream#callback. The streaming plugin
will be much simpler in Roda 3 by dropping this support.
* Calling content_for in the content_for plugin multiple times with
the same argument is now deprecated unless the content_for
plugin :append option is used to specify behavior. The default
behavior in Roda 3 will change to appending to the existing
content instead of overwriting the existing content.
* The :host matcher in the header_matchers plugin is now deprecated
when using a regexp value unless the :host_matcher_captures app
option is used. In Roda 3, the :host matcher will automatically
yield any regexp captures to the match block.
* The :header matcher in the header_matchers plugin is now deprecated
unless the :header_matcher_prefix app option is used. In Roda 3,
the :header matcher will always prefix the argument given with
HTTP_.
* The websockets plugin is now deprecated. It was one of the less
commonly used plugins, and the tests for it were subject to race
conditions and failed occassionally, and even when they worked
they almost doubled the testing time. Anyone wanting to use it
should consider maintaining it as an external plugin.
* The per_thread_caching, static_path_info, and view_subdirs
plugins are now deprecated. static_path_info has been a no-op since
Roda 3, view_subdirs is just an alias for view_options, and
per_thread_caching doesn't change behavior and is unlikely to
significantly increase performance.
* Additional internal constants are now deprecated. Deprecation
warnings for accessing these constants will only be displayed on
ruby 2.3+.
= Forward Compatibility
Roda 3.0.0 will also include some behavior changes which will not
have deprecation warnings:
* Ruby 1.8.7 support will be dropped. Ruby 1.9.2 will be the new
minimum supported version.
* Subclassing a Roda app that uses the render plugin will always
use a copy of the superclass's template cache.
* The assets plugin will default to using subresource integrity
using SHA256 for compiled assets, and using SHA256 instead of
SHA1 for compiled asset hashes.
* Using an Roda app as middleware will now always use a subclass
of the app for the middleware.
* public_send will be used instead of send internally unless it is
expected that private methods will be called.
* The match methods added by the symbol_matchers and hash_matchers
plugins will be private instead of public.
= New Features
* The render plugin now has the :layout_opts=>:views plugin option
respect the :root app option.
* RodaPlugins::OPTS and RodaPlugins::EMPTY_ARRAY have been added.
These are a frozen empty hash and a frozen empty array, and
they are designed for use in plugins so that similar objects are
not needed to be defined separately in each plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/2.3.0.txt 0000664 0000000 0000000 00000010677 15167207754 0022221 0 ustar 00root root 0000000 0000000 = New Plugins
* A json_parser plugin has been added, for parsing request bodies in
JSON format. This is faster than using a middleware to perform
the same task. This plugin supports a :parser option to use a
custom JSON parser, an :include_request option to include the
request when calling the parser, and a :error_handler option for
a proc to call with the request if there is an error when parsing.
Example:
plugin :json_parser,
:parser=>JSON.method(:parse),
:include_request=>false,
:error_handler=>proc{|r| r.halt [400, {}, []]}
* A path_rewriter plugin has been added, allowing for the rewriting
of paths before routing. This allows you to rewrite just the
routing path (the default), or PATH_INFO as well as the routing
path (if the :path_info option is used). This is useful if you
want to internally treat one path exactly the same as another
path.
By default, path rewriting is done on prefixes, so any path that
starts with the prefix will be rewritten. You can pass a
Regexp when rewriting the path for more complete control.
Examples:
plugin :path_rewriter
rewrite_path '/a', '/b'
# GET /a treated as GET /b
# GET /a/c treated as GET /b/c
rewrite_path /\A\/c\z/, '/d'
# GET /c treated as GET /d
# GET /c/e no change
* A precompiled_templates plugin has been added, for precompiling
templates before starting the application. This can save a
substantial amount of memory if you are using large templates or
a large number of small templates in conjunction when using
application preloading with a forking webserver. Example:
plugin :precompile_templates
precompile_templates "views/\*\*/*.erb"
precompile_templates "views/users/_*.erb", :locals=>[:user]
* A heartbeat plugin has been added, for easily handling
heartbeat/status requests. If a heartbeat/status request comes in,
it will get a 200 response with a body of "OK". This is designed
for automated systems that check if the application is functioning.
The default heartbeat path is /heartbeat, but you can choose a
different one using the :path option.
plugin :heartbeat, :path=>'/heartbeat'
= Other New Features
* The json plugin now supports a :serializer option to use a custom
serializer. Additionally, it now supports a :include_request
option to include the request when calling the serializer.
* In the render plugin, the render/view methods now support a
:cache=>false option to not cache the template. This can be useful
for large but rarely used templates, or where a new template object
is created for every render/view call.
* In the render plugin, the render/view methods now support a
:cache_key option to force a specific cache key. Manually setting
cache keys can result in improved performance, as automatically
determining the cache key can be a relatively expensive operation.
* The render plugin now supports a :engine_opts option, to specify
per-template engine options. :engine options should be a hash
keyed by render engine strings, with values being hashes of
template options.
* In the mailer plugin, a no_mail! method is now supported when
mailing, which will skip the current mail. This makes it easier
to delay the decision about actually sending the email till it is
time to send the email, which makes it easier to avoid race
conditions if you are using a job queue for mailing.
= Other Improvements
* Roda avoids rehashing hashes at runtime in some places, for a minor
speedup.
* If the :template_block is given to render/view, default to not
caching the template, since it is likely the template block is
specific to the request. Allow for the :cache=>true option to be
used to force the caching of the template.
* Roda now returns a 404 response for unmatched GET requests when
using the assets and json plugins where r.assets is the last
method called in a route block.
= Backwards Compatibility
* In the render plugin, the :ext option to the plugin and to the
render/view methods is now replaced by the :engine option.
Previously, :engine was used by default if :ext was not given. In
general, there is no need for two separate options, the engine is
used as the extension by Tilt.
In general, this is a backwards compatible change, except when
both :ext and :engine were specified differently as plugin options,
and an inline template is used with render or view without either
the :ext or :engine options being specified.
jeremyevans-roda-4f30bb3/doc/release_notes/2.4.0.txt 0000664 0000000 0000000 00000003152 15167207754 0022210 0 ustar 00root root 0000000 0000000 = New Plugins
* A websocket plugin has been added, for websocket support using
faye-websocket. Here's an example of a simple echo service using
websockets:
plugin :websockets
route do |r|
r.get "echo" do
r.websocket do |ws|
# Routing block taken for a websocket request to /echo
# ws is a Faye::WebSocket instance, so you can use the
# Faye::WebSocket API
ws.on(:message) do |event|
ws.send(event.data)
end
end
# View rendered if a regular GET request to /echo
view "echo"
end
end
* A status_handler plugin has been added, which allows Roda to
specially handle arbitrary status codes. Usage is similar to the
not_found plugin (which now uses status_handler internally):
plugin :status_handler
status_handler 403 do
"You are forbidden from seeing that!"
end
status_handler 404 do
"Where did it go?"
end
= Other New Features
* The assets plugin now supports a :gzip option, which will save
gzipped versions when compiling assets. When serving compiled
assets, if the request accepts gzip encoding, it will serve
the gzipped version. This also plays nicely with nginx's
gzip_static support.
* The assets plugin now supports Google Closure Compiler, Uglifier,
and MinJS for minifying javascript. You can now specify which
css and js compressors to use via the :css_compressor and
:js_compressor options.
= Backwards Compatibility
* Roda.plugin now always returns nil. Previously the return value
could be non-nil if the plugin used a configure method.
jeremyevans-roda-4f30bb3/doc/release_notes/2.5.0.txt 0000664 0000000 0000000 00000001557 15167207754 0022220 0 ustar 00root root 0000000 0000000 = New Features
* The assets plugin now supports a :compiled_asset_host option, which
specifies a hostname used to serve compiled assets.
* The render plugin now supports a :cache_class option, which
specificies a class to use for the thread-safe template cache.
This can be used to setup LRU caching or caching that checks
modify times on the underlying template files.
* r.multi_run in the multi_run plugin now accepts a block, and calls
the block before dispatching to the related rack application. This
can be used to modify the environment before dispatching. Example:
r.multi_run do |prefix|
env['authenticated'] = true
end
= Backwards Compatibility
* The :by_name option to the path plugin now defaults to true in
development mode. This should only negatively affect applications
that register anonymous classes with the path plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/2.5.1.txt 0000664 0000000 0000000 00000000154 15167207754 0022211 0 ustar 00root root 0000000 0000000 = Improvements
* The multi_route plugin now works correctly if the middleware plugin
is loaded after it.
jeremyevans-roda-4f30bb3/doc/release_notes/2.6.0.txt 0000664 0000000 0000000 00000001372 15167207754 0022214 0 ustar 00root root 0000000 0000000 = New Features
* :params and :params! matchers have been added to the param_matchers
plugin, allowing you to match multiple params at the same time:
r.on :params=>%w'foo bar' do |foo, bar| end
# instead of
r.on({:param=>'foo'}, :param=>'bar') do |foo, bar| end
= Other Improvements
* When loading the csrf plugin multiple times, instead of loading the
middleware multiple times with different settings, merge options
in later plugin calls into a single middleware option hash, and
only load the middleware once.
This allows plugins to depend on the csrf plugin, while also
allowing the application to use the csrf plugin with options.
* request.halt now works correctly when used inside a before hook when
using the hooks plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/2.7.0.txt 0000664 0000000 0000000 00000005213 15167207754 0022213 0 ustar 00root root 0000000 0000000 = New Features
* A default_status plugin has been added for changing the default
status for responses. Previously, the default status was hard
coded to 200, this plugin allows you to change it. The plugin
takes a block which is instance_evaled in the context of the
response:
plugin :default_status do
headers['Content-Type'] == 'foo' ? 201 : 200
end
Note that the default status for empty responses (used when no
route handles the response) is still 404, this just changes the
default for non-empty responses.
* A response_request plugin has been added for giving the response
instance access to the related request. This can be useful in
conjunction with the default_status plugin, if you want the
default status of the response to depend on the request, such as
using a different status for different request methods:
plugin :response_request
plugin :default_status do
request.post? ? 201 : 200
end
* A run_handler plugin has been added, for modifying rack response
arrays before returning them when using r.run. Additionally, it
allows for continuing with routing if the response returned by
r.run is a 404 response, using the :not_found=>:pass option:
plugin :run_handler
route do |r|
# Keep running code if RackAppFoo returns a 404 response
r.run RackAppFoo, :not_found=>:pass
# Change response status codes before returning.
r.run(RackAppBar) do |response|
response[0] = 200 if response[0] == 201
end
end
* Roda.rewite_path in the path_rewriter extension now accepts a block
to allow for dynamic replacements. The block is yielded a MatchData
instance:
rewrite_path(/\A\/a/(\w+)/){|match| match[1].capitalize}
# PATH_INFO '/a/moo' => remaining_path '/a/Moo'
rewrite_path(/\A\/a/(\w+)/, :path_info => true) do |match|
match[1].capitalize
end
# PATH_INFO '/a/moo' => PATH_INFO '/a/Moo'
* The :host matcher in the header_matchers plugin will now yield the
regexp captures to the block if given a regexp when the
:host_matcher_captures application option is set. This behavior
will become the default behavior in Roda 3. This will allow for
code like:
opts[:host_matcher_captures] = true
route do |r|
r.on :host=>/\A(\w+).example.com\z/ do |subdomain|
# ...
end
end
= Other Improvements
* RodaCache now uses a mutex to synchronize access on MRI.
Previously, it relied on the global interpreter lock, but testing
has shown that is not reliable in all cases. RodaCache has always
used a mutex for synchronization on other ruby implementations,
this just extends that code to MRI as well.
jeremyevans-roda-4f30bb3/doc/release_notes/2.8.0.txt 0000664 0000000 0000000 00000001340 15167207754 0022211 0 ustar 00root root 0000000 0000000 = New Features
* A multi_view plugin has been added, for easily setting up routing
for rendering multiple views:
plugin :multi_view
route do |r|
r.multi_view(['foo', 'bar', 'baz'])
end
# or:
route do |r|
r.multi_view(/(foo|bar|baz)/)
end
# or:
regexp = multi_view_compile(['foo', 'bar', 'baz'])
route do |r|
r.multi_view(regexp)
end
# all are equivalent to:
route do |r|
r.get 'foo' do
view('foo')
end
r.get 'bar' do
view('bar')
end
r.get 'baz' do
view('baz')
end
end
= Other Improvements
* The content_for plugin now supports haml templates. Previous only
erb templates were supported.
jeremyevans-roda-4f30bb3/doc/release_notes/2.9.0.txt 0000664 0000000 0000000 00000000241 15167207754 0022211 0 ustar 00root root 0000000 0000000 = New Features
* The content_for plugin now supports passing the content as a
string argument instead of a block:
<% content_for :foo, "Some content" %>
jeremyevans-roda-4f30bb3/doc/release_notes/3.0.0.txt 0000664 0000000 0000000 00000005377 15167207754 0022220 0 ustar 00root root 0000000 0000000 = Major Changes
* String matchers now match literally by default, for simplicity,
understandability, and performance. Use the String class matcher
or a symbol matcher to match arbitrary segments.
# Before
r.is "artists/:name" do |artist_name|
end
# Now
r.is "artists", String do |artist_name|
end
# or
r.is "artists", :name do |artist_name|
end
You can use the placeholder_string_matchers plugin to restore
the historical behavior if you don't want to modify your
routes.
* Using an unsupported matcher now raises an error, making it
more likely to detect using a unexpected value as a matcher
(which previously matched everything).
* Have a route/match block return an unsupported value now
raises an error if nothing has been written to the body, making
it more likely to detect using an unexpected value as a block
result (which previously was ignored).
= Backwards Compatibility
* Deprecated plugins, features, and constants have been removed.
Before upgrading to 3.0.0, please upgrade to 2.29.0 first and
fix any deprecation warnings.
* Ruby 1.8.7 support has been dropped. Ruby 1.9.2 is the new
minimum supported version.
* The :check_paths render plugin option now defaults to true so that
generated template paths are checked by default, reducing the risk
of rendering arbitrary files.
* The assets plugin now defaults to using subresource integrity
with SHA256 for compiled assets, and using SHA256 instead of
SHA1 for compiled asset hashes.
* Subclassing a Roda app that uses the render plugin now always
uses a copy of the superclass's template cache.
* Using a Roda app as middleware now always uses a subclass
of the app for the middleware.
* public_send is now used instead of send internally unless it is
expected that private methods will be called.
* The match methods added by the symbol_matchers and hash_matchers
plugins are now private instead of public.
= Other Improvements
* The streaming plugin has been greatly simplified, by dropping
deprecated compatibility for EventMachine.
* It is now possible to reset the :include_request option to false
in the json and json_parser plugins by loading the plugin a
second time with the option set.
* The precompile_templates plugin now always sorts locals. This
plugin should now be used with Tilt 2.0.1+ (which also sorts
locals), though it will still work with earlier Tilt versions.
* The multi_run plugin now recomputes the regexp when freezing the
app.
= Deprecated Features
These features will be removed in Roda 3.1.0:
* Roda.thread_safe_cache is now deprecated. RodaCache is now used
as the thread-safe cache class.
* RodaRequest#placeholder_string_matcher? (private method) is now
deprecated and always returns false.
jeremyevans-roda-4f30bb3/doc/release_notes/3.1.0.txt 0000664 0000000 0000000 00000001566 15167207754 0022215 0 ustar 00root root 0000000 0000000 = New Features
* A :timestamp_paths option has been added to the assets plugin to
include timestamps in paths in non-compiled mode. This can fix
asset staleness issues when using a caching proxy. This is
not needed in compiled mode, as the asset file names include the
hash of the asset. It is not the default in non-compiled mode,
as few people would use a caching proxy in non-compiled mode.
= Other Improvements
* Make set_layout_locals and set_view_locals in branch_locals
plugin work when the other is not called.
* When testing support for uglifier usability as a JS asset
compressor, handle case where uglifier is installed but there is
no available javascript runtime.
= Backwards Compatibility
* The deprecated Roda.thread_safe_cache method has been removed.
* The deprecated private RodaRequest#placeholder_string_matcher?
method has been removed.
jeremyevans-roda-4f30bb3/doc/release_notes/3.10.0.txt 0000664 0000000 0000000 00000014164 15167207754 0022273 0 ustar 00root root 0000000 0000000 = New Features
* A sessions plugin has been added that supports encrypted and
signed sessions. This plugin is now the recommended way to
implement sessions, replacing the previously recommended
Rack::Session::Cookie middleware.
The sessions plugin encrypts session data using the AES-256-CTR
cipher, and then signs the encrypted data with HMAC-SHA-256. By
doing this, attackers must be able to forge a valid HMAC before
they can try to exploit possible weaknesses in the encryption,
such as timing attacks during decryption that are dependent on
attacker chosen initialization vectors or ciphertext.
In addition to encryption and a stronger default signature
algorithm compared to Rack::Session::Cookie, the sessions
plugin has the following benefits:
* Built in session expiration enabeld by default, to mitigate
possible session replay issues (default: 30 days since session
creation, 7 days since last update).
* Padding by default to minimize information leakage due to
differing session data sizes (session data padded to
a multiple of 32 bytes by default before encryption).
* Automatic deflate compression of large sessions before
encryption (by default if session data is over 128 bytes).
* JSON is used for serialization instead of Marshal, preventing
remote code execution vulnerabilities if the session secret
is disclosed. Note that this means that many ruby types do not
round trip in the session, such as Symbol and Time instances.
This will probably be the largest barrier to adoption, as you
need to make sure your application only uses types that
round-trip through JSON before you start using the sessions
plugin.
* A plain hash is used for the session, instead of a hash-like
object. One consequence of this is that keys in the session
are not automatically converted to strings.
Rack::Session::Cookie converts session keys to strings for
keys at the top level, but not for keys in subhashes.
* In general sessions are smaller even if deflate compression is not
used, despite requiring 16 bytes for the cipher initialization
vector. The main reason for this is that the sessions plugin does
not set a session id, since one is not needed for cookie sessions.
* The sessions plugin requires a :secret option be set that is
at least 64 bytes, so that users have to make a determined
effort to use weak secrets.
* The HMAC calculation considers the cookie key, so that if the
same session secret is used for multiple applications with
different cookie keys, an attacker cannot use the session from
one application in a different application.
The sessions plugin ties into the Roda#session method instead
of being a rack middleware. This makes it about twice as
fast as Rack::Session::Cookie if the session is not accessed.
If the session is accessed, the sessions plugin is roughly
as fast as Rack::Session::Cookie, even though it uses a
stronger HMAC and has to encrypt and decrypt the session.
Because the sessions plugin is not a middleware, it does not
offer session support to other middleware, only to the app
itself. If you would like to use the same approach as the
sessions plugin uses but would like support for middleware to
access the sessions, a roda/session_middleware file has been
added. This file contains RodaSessionMiddleware, which is a
middleware that can be used by any other Rack app for session
support, and which uses a SessionHash class similar to the one used
by Rack::Session::Cookie.
To integrate with other plugins that can optionally use symbols
or strings in sessions, the sessions plugin sets the
:sessions_convert_symbols application option to true. Other plugins
can check for this application option, and if set, should use
strings instead of symbols in the session.
The sessions plugin should be loaded after the flash plugin if both
are used in the same application, so that the flash is rotated
correctly in the session.
* The middleware plugin now supports a :handle_result option, which
can be any callable object. If set, this object is called with the
environment of the request and the rack response after either the
Roda app or next middleware returns the rack response. The rack
response can be modified by the callable object, and the response
(after possible modification) will be returned to the previous
middleware. Example:
plugin :middleware, :handle_result=>(proc do |env, res|
res[1]['MyHeader'] = 'HeaderValue'
end)
* The :json_parser and :json_serializer application options are now
supported. If set, these options are used for parsing and
serializing JSON instead of the default of JSON.parse and .to_json.
= Other Improvements
* RodaRequest initialization is now faster by avoiding 1-2 method
calls.
* typecast_params.Integer in the typecast_params plugin now handles
numeric input as long the numeric input does not have fractional
parts. This makes it more usable when handling JSON input.
* If the flash is empty after the request is processed, the flash
session key is removed from the session instead of being left as
an empty hash. If addition to making the session smaller, this
makes the session appear empty if there are no other keys in the
session, which works better with the sessions plugin as empty
sessions will remove the session cookie completely.
= Backwards Compatibility
* The flash plugin now uses '_flash' instead of :_flash as the session
key. When using session middleware that uses
Rack::Session::Abstract::SessionHash to store the session (e.g.
Rack::Session::Cookie), session keys are converted internally to
strings, so this change will not affect you unless you are using
alternative session support. Even if your session does treat
:_flash different than '_flash' in keys, the plugin will still work
because it will try :_flash if there is no value for '_flash'. This
change was made to support the sessions plugin, which doesn't
convert keys to strings.
* This DEFAULT_PARSER and DEFAULT_SERIALIZER constants from the
the json_parser and json plugins have been removed.
jeremyevans-roda-4f30bb3/doc/release_notes/3.100.0.txt 0000664 0000000 0000000 00000002415 15167207754 0022347 0 ustar 00root root 0000000 0000000 = New Features
* A sec_fetch_site_csrf plugin has been implemented, which implements
CSRF protection using the Sec-Fetch-Site header. This offers weaker
CSRF protection than the route_csrf plugin, but doesn't require CSRF
tokens in forms. Other caveats when using the plugin:
* Not all browsers set the Sec-Fetch-Site header. Some popular
browsers did not add support until 2023.
* Sec-Fetch-Site is only set on HTTPS requests, not on HTTP
requests, so if you need to support HTTP requests, you cannot
rely on it.
* There is no support for cross-site secure CSRF protection by
sharing the token used.
Like the route_csrf plugin, the sec_fetch_site_csrf plugin exposes
a method (check_sec_fetch_site!) that you can call at the
appropriate point in your routing tree to enforce the CSRF
protection.
By default, only same-origin requests are allowed by default. Using
plugin options, you can support same-site or none requests, or
support requests where the header is not present.
For CSRF violations, the default is to raise an exception. You can
use plugin options to either return a blank 403 page or clear the
current session. You can also pass a block to either the plugin or
to the check_sec_fetch_site! method for custom handling.
jeremyevans-roda-4f30bb3/doc/release_notes/3.101.0.txt 0000664 0000000 0000000 00000000325 15167207754 0022346 0 ustar 00root root 0000000 0000000 = New Features
* A bearer_token plugin has been added for retrieving a bearer token
(if provided) from the HTTP Authorization header:
# HTTP Header: Authorization: Bearer foo
r.bearer_token # => "foo"
jeremyevans-roda-4f30bb3/doc/release_notes/3.102.0.txt 0000664 0000000 0000000 00000001273 15167207754 0022352 0 ustar 00root root 0000000 0000000 = New Features
* A response_attachment plugin has been extracted from the
sinatra_helpers plugin, allowing the use of the response.attachment
method without having to include the rest of sinatra_helpers:
response.attachment "a.csv"
# content-disposition: attachment; filename="a.csv"
# content-type: text/csv
* A send_file plugin has been extracted from the sinatra_helpers
plugin, allowing the use of send_file without having to include the
rest of sinatra_helpers. This allows you to return the content of a
file as the response. It also sets the content-disposition and
content-type headers as appropriate based on the file extension:
send_file 'path/to/file.txt'
jeremyevans-roda-4f30bb3/doc/release_notes/3.103.0.txt 0000664 0000000 0000000 00000001233 15167207754 0022347 0 ustar 00root root 0000000 0000000 = New Features
* An ip_from_header plugin has been added, which makes request.ip
pull the IP address from a header if it is present. If you know that
all requests are coming from a proxy, this is simpler and faster
than attempting to parse the information out of the Forwarded or
X-Forwarded-For headers.
* A hash_public plugin has been added, which operates similarly to
the timestamp plugin plugin, but does cache busting based on the
hash of the file content instead of the modification time of the
file. This is useful in containerized environments where the
modification time of the file may change even if the content of
the file does not.
jeremyevans-roda-4f30bb3/doc/release_notes/3.11.0.txt 0000664 0000000 0000000 00000003771 15167207754 0022276 0 ustar 00root root 0000000 0000000 = Improvements
* The order in which internal plugin before and after hooks are run
when multiple plugins are loaded is now fixed and does not depend
on the order in which the plugins are loaded. This can prevent
some issues in cases the plugins were not loaded in the order
previously recommended in the documentation.
Internal plugin before hooks are now run in the following order:
* hooks
* heartbeat
* static_routing
and internal plugin after hooks are now run in the following order:
* class_level_routing
* status_handler
* head
* flash
* session
* hooks
* Default compression of sessions over 128 bytes in length has been
disabled in the sessions plugin. Compression of sessions must now
be manually enabled if it is desired by setting :gzip_over to an
integer.
This change is being made to avoid possible compression ratio
attacks if both sensitive data and user-submitted data are stored in
the session. Such attacks were mitigated by the sessions plugin's
default use of padding after compression, and the JSON serialization
format used, but disabling compression avoids the possibility.
This does not affect backwards compatibility, as compressed sessions
will still be decompressed correctly, unless the size of the session
cookie when not using compression is over 4096 bytes.
= Backwards Compatibility
* When using the error_handler plugin, if routing raises an exception that
is handled by the error handler, but an exception is raised by a plugin
internal after hook after the error handler has been run, the exception
will be logged to the rack.errors entry in the environment, but it will
be otherwise ignored.
Exceptions raised inside the error handler will continue to be be raised
to the application's caller.
Additionally, the error_handler plugin no longers call before hooks
during error handling.
* A private Roda#_call method has been added. This could potentially
cause issues for applications that add their own _call method.
jeremyevans-roda-4f30bb3/doc/release_notes/3.12.0.txt 0000664 0000000 0000000 00000001425 15167207754 0022271 0 ustar 00root root 0000000 0000000 = New Features
* A common_logger plugin has been added for common log support. This
offers about 30% better performance than Rack::CommonLogger, with
the following differences:
* When timing requests, doesn't consider middleware or proxy the
body, so timing information is just the time that Roda takes
to process the request.
* Only looks for "Content-Length" as a header, not different
capitalizations (Roda only uses "Content-Length" internally).
* Logs to $stderr instead of rack.errors in request environment
if a logger object is not explicitly passed.
= Other Improvements
* Internal before/after hook methods now use more descriptive names
for easier debugging, with a naming format designed to not
conflict with hook methods in external plugins.
jeremyevans-roda-4f30bb3/doc/release_notes/3.13.0.txt 0000664 0000000 0000000 00000003134 15167207754 0022271 0 ustar 00root root 0000000 0000000 = New Features
* An exception_page plugin has been added for displaying debugging
information for a given exception. It is based on
Rack::ShowExceptions, with the following differences:
* Not a middleware, so it doesn't handle exceptions itself, and
has no effect on the callstack unless the exception_page
method is called.
* Supports external javascript and stylesheets, allowing context
toggling to work in applications that use a content security
policy to restrict inline javascript and stylesheets (:assets,
:css_file, and :js_file options).
* Has fewer dependencies (does not require ostruct and erb).
* Sets the Content-Type for the response, and returns the body
string, but does not modify other headers or the response status.
* Supports a configurable amount of context lines in backtraces
(:context option).
* Supports optional JSON formatted output, if used with the json
plugin (:json option).
Because this plugin just adds a method you can call, you can
selectively choose when to display a debugging page and when not
to, as well as customize the debugging parameters on a per-call
basis (such as returning JSON formatted debugging information
for JSON requests, and HTML formatted debugging information for
normal requests).
= Other Improvements
* The common_logger plugin now correctly handles cases where an
exception is being raised and there is no rack response to
introspect.
= Backwards Compatibility
* Stream#write in the streaming plugin now returns the number of
bytes written instead of self, so it works with IO.copy_stream.
jeremyevans-roda-4f30bb3/doc/release_notes/3.14.0.txt 0000664 0000000 0000000 00000002121 15167207754 0022265 0 ustar 00root root 0000000 0000000 = New Features
* The convert! and convert_each! methods in the typecast_params plugin
now support a :raise option for handling missing parameters specified
as arguments to the methods.
If the :raise option is set to false for convert! and the parameter
argument is missing, then no conversion is done and an empty hash
is returned:
typecast_params.convert!('missing', raise: false) do |tp|
# ...
end
# => {}
If the :raise option is set to false for convert_each! and a :keys
option is given, any key not present is ignored and nil will be
returned for the converted value
typecast_params.convert_each!(:keys=>['present', 'missing'], raise: false) do |tp|
tp.int('b')
end
# => [{'b'=>1}, nil]
= Other Improvements
* The :symbolize setting to the convert! and convert_each! methods in
the typecast_params plugin is no longer persisted beyond the call
to the method. This fixes unexpected behavior if you do:
typecast_params.convert!(:symbolize=>true) do |tp|
# ...
end
typecast_params.convert! do |tp|
# ...
end
jeremyevans-roda-4f30bb3/doc/release_notes/3.14.1.txt 0000664 0000000 0000000 00000004032 15167207754 0022271 0 ustar 00root root 0000000 0000000 = Security Fix
* Do not post-process content_for block result with template engine
Since 2.8.0, the content_for block result was post-processed with the
template engine. There is no actual need to do so, as content_for is
not designed to render output, it is designed to store already
rendered output. This post-processing was introduced when support for
haml templates was added in 2.8.0.
Post-processing the output with the template engine is generally a
no-op for most usage as most output does not contain template
metaprogramming characters, which is why this went undetected for so
long. However, if a content_for block return value contained
unescaped user input, it was probably vulnerable to remote code
execution if the default ERB template engine is used, the same as if
the user input was passed directly to the render or view method.
Example of a vulnerable usage (assuming automatic escaping is not
enabled) would be:
<% content_for :foo do %>
User name: <%= request.params['user_name'] %>
<% end %>
Such usage is likely vulnerable to cross site scripting unless the
content_for output is escaped before being displayed, even without
the content_for template post-processing. However, the post-processing
turned it from a cross site scripting vulnerability into a remote code
execution vulnerability. For non-ERB template engines, whether the
post-processing introduced a vulnerability depends on the template
engine.
Note that if you were correctly escaping user input in your ERB
templates (either automatically or manually), you are unlikely to be
vulnerable as the escaping escaped the ERB template metacharacters
(< and >). For non-ERB templates, escaping the output may not have
mitigated the vulnerability, depending on what metacharacters
the template engine uses and whether the escaping will modify them.
Calling content_for with an argument was not vulnerable as no
post-processing was done on the argument, it was only done on
the block result.
jeremyevans-roda-4f30bb3/doc/release_notes/3.15.0.txt 0000664 0000000 0000000 00000001331 15167207754 0022270 0 ustar 00root root 0000000 0000000 = New Features
* The render plugin :escape option value can now be a string or an
array of strings, and then the plugin will will only add the
:escape template option for those specific template engines given.
By default, the :escape plugin option adds the :escape template
option for all engines, which breaks the usage with some engines
(such as the rcsv engine).
* The convert! and convert_each! methods in the typecast_params plugin
now support a :skip_missing option to support not storing missing
parameters:
typecast_params.convert! do |tp|
tp.int('missing')
end
# => {'missing'=>nil}
typecast_params.convert!(skip_missing: false) do |tp|
tp.int('missing')
end
# => {}
jeremyevans-roda-4f30bb3/doc/release_notes/3.16.0.txt 0000664 0000000 0000000 00000003476 15167207754 0022305 0 ustar 00root root 0000000 0000000 = New Features
* A mail_processor plugin has been added for processing mail using
a routing tree. Quick example:
class MailProcessor < Roda
plugin :mail_processor
route do |r|
# Match based on the To header, extracting the ticket_id
r.to /ticket\+(\d+)@example.com/ do |ticket_id|
if ticket = Ticket[ticket_id.to_i]
# Mark the mail as handled if there is a valid ticket
# associated
r.handle do
ticket.add_note(text: mail_text, from: from)
end
end
end
end
end
You can submit mail for processing by calling the process_mail
method with a Mail instance:
MailProcessor.process_mail(Mail.read('/path/to/message.eml'))
The mail_processor routing tree uses routing methods specific to
mail:
r.from :: match on the mail From address
r.to :: match on the mail To address
r.cc :: match on the mail CC address
r.rcpt :: match on the mail recipients (To and CC addresses by
default)
r.subject :: match on the mail subject
r.body :: match on the mail body
r.text :: match on text extracted from the message (same as mail
body by default)
r.header :: match on a mail header
To mark a mail as having been handled, you call the r.handle method
with a block, or one of the above methods prefixed by handle_
(e.g. r.handle_text).
The mail_processor plugin supports hooks that are called for handled
mail, unhandled mail, and all mail (for archiving). It also
supports the ability to configure how reply text is parsed out of
mail, who to consider as the recipients of the email, and the
ability to have separate routing blocks per recipient email address
(with O(1) delegation to the appropriate block if the recipient
addresses to match is a string).
jeremyevans-roda-4f30bb3/doc/release_notes/3.17.0.txt 0000664 0000000 0000000 00000004110 15167207754 0022270 0 ustar 00root root 0000000 0000000 = New Features
* A route_block_args plugin has been added, allowing you to customize
which objects are yielded to the the route block. You call the
plugin with a block, which is evaluated in the context of the
instance and should return an array of arguments for the instance
to yield to the route block.
To yield both the request and response objects, you can do:
plugin :route_block_args do |r|
[r, response]
end
route do |r, response|
# ...
end
In addition to the main route block, using this plugin also affects
the arguments passed to routing blocks in the following plugins:
* class_level_routing
* mailer
* mail_processor
* multi_route
* static_routing
= Other Improvements
* The set_layout_opts method in the view_options plugin can now
override the layout template even if the render plugin :layout
option is given.
* The mailer and mail_processor plugin now integrate with the hooks
plugin to support before/after hooks.
* Dispatching to the route block and RodaResponse#finish are both
slightly faster.
* Internal before hook handling has been moved from an internal
plugin into the core, and modified so that if you are not using
the internal before hook in any plugin, there is no runtime cost.
* The core now recognizes when plugins are using the internal after
hook, and automatically loads the internal plugin supporting the
after hook.
= Backwards Compatibility
* When using the render plugin with a :layout option, the render_opts
:layout option will be set to true if the layout is enabled.
Previously, the render_opts :layout option would retain the value
given as the plugin option. Options for the layout (including the
template) are still available in the render_opts :layout_opts
option. This change was made to fix the set_layout_opts bug in the
view_options plugin.
* RodaResponse#initialize no longer sets the response status to nil
if it was already set.
* RodaResponse#finish no longer sets the status on the receiver, it
just uses the receiver's status to set the rack response status.
jeremyevans-roda-4f30bb3/doc/release_notes/3.18.0.txt 0000664 0000000 0000000 00000016107 15167207754 0022302 0 ustar 00root root 0000000 0000000 = New Features
* A direct_call plugin has been added. This plugin makes Roda.call
call the app directly, skipping any middleware. This plugin
can be used for performance reasons, as the class itself can be
used as the base rack app, instead of using a lambda as the base
rack app. Roda.app.call will still call all middleware when
using this plugin.
= Other Improvements
* Blocks that are given during application configuration, and
previously executed with instance_exec, instead now define methods,
and Roda now calls these methods. This is a much faster approach.
This new approach, combined with the direct_call plugin and the
Roda.freeze optimizations, can be over 80% faster for trivial
applications, with measureable improvements in most applications.
As methods are strict in regards to arity and instance_exec is
not, Roda now checks all such blocks for arity mismatches, and
attempts to compensate for arity mismatches. In case of an arity
mismatch, Roda will define a method that will call instance_exec,
in which case there will not be a performance improvement.
For some methods, Roda may not know the expected arity until
runtime. In that case, Roda will check the arity at runtime and
try to call the method with the arity that it supports if there is
an arity mismatch.
You can control the checking of arity via two options:
:check_arity :: Set to false to turn off all arity checking. Set to
:warn to issue a warning when defining the method if
there is an arity mismatch (for methods where the
expected arity is known in advance).
:check_dynamic_arity :: Set to false to turn off arity checking for
methods defined where the arity is not known
at compile time. Set to :warn to issue a
warning at runtime every time the method is
called and there is an arity mismatch (for
methods where the expected arity is not
known in advance). Note that checking the
arity at runtime has a performance cost,
so for maximum performance this should be
set to false.
Note that this arity checking is only done to keep backwards
compatibility. Since lambdas already used strict arity, no arity
checking is done if the block is a lambda and not a regular proc.
Roda has a new dispatch API that works with these defined methods.
The new dispatch API uses the following methods:
* _roda_handle_main_route: Entry point for normal request dispatch.
* _roda_handle_route: Yields to the routing block, catching any
halts inside the block, treating the block as a routing block.
* _roda_main_route: Roda.route defines this method using the
block provided, it accepts the request as an argument.
* _roda_run_main_route: Calls _roda_main_route with the request,
allowing for plugins to execute code around the main routing,
while still being able to throw :halt to return a response.
All instance methods defined by Roda use the _roda_ prefix.
* When deleting the session cookie in the sessions plugin, the
Set-Cookie response header now uses the same path and domain
that was originally used to set the cookie. This can fix cases
where the cookie was not being cleared as expected.
* Freezing a Roda app now can add performance improvements in
addition to reliability improvements. When freezing the class,
if certain methods in the class have not been overridden, Roda
now defines aliases or more optimized methods to improve
performance.
* Roda now warns if the Roda#call method is overridden in a module,
without the module also overridding _roda_handle_main_route or
_roda_run_main_route. This indicates that the module needs
to be updated to use Roda's new dispatch API. Roda will continue
to work in this case, but it will be slower than the Roda's now
default behavior, as it will force usage of the old dispatch API.
This check will be removed in Roda 4, which will remove support
for Roda#call (and Roda#_call).
* When there is only a single internal before or after hook defined,
the hook is now faster by using a method alias.
* The route_csrf plugin block or :csrf_failure option proc now
integrates with the route_block_args plugin.
* The default_status plugin is now faster by defining the
default_status method directly.
* The default_headers plugin is now faster by defining an optimized
set_default_headers method directly.
* The hooks plugin is now faster by defining methods for each
hook block, with a main hook method that dispatches to each
of the hook block methods. If only a single hook block is
used, the main hook method is an alias to the hook block
method to avoid an extra method call.
* The following plugins now use define_method instead of
instance_exec for better performance:
* defaults_setter
* mail_processor
* multi_route
* named_templates
* path
* route_block_args
* route_csrf
* static_routing
* status_handler
* The internal after hook implementation has now been merged into
the error_handler plugin. This is faster in cases where the
error_handler plugin is used, and slower in cases where the
internal after hook plugin was used without the error_handler
plugin.
* The route_block_args plugin now handles cases where
Roda.convert_route_block has already been overridden.
* Performance of routing methods that can yield captures has been
improved.
* Hash#merge is now used in preference to Hash[].merge! in cases
where the receiver of Hash#merge would not be provided by the
user. This is because Hash#merge is faster than Hash[].merge!
in recent ruby versions. If the receiver of #merge is provided
by the user, then Hash[].merge! is still used to ensure that the
resulting value is plain hash.
* The static_routing plugin no longer removes existing static
routes if loaded more than once.
* Roda now warns when calling Roda.route without a block.
= Backwards Compatibility
* The route_block_args plugin no longer affects the
class_level_routing plugin. Support for this was added in Roda
3.17.0 when the route_block_args plugin was added, but this was a
mistake as class_level_routing blocks should be called with the
captures for their matchers, not with the route block args.
* Some of the internal state was changed in the following plugins:
* class_level_routing
* mail_processor
* multi_route
* named_templates
* static_routing
* status_handler
This only affects you if you were accessing the internal state
via the opts hash.
* The static_routing plugin no longer defines the r.static_route
method.
* The mailer plugin was switched to use the new dispatch API, and
will no longer handle cases where the old dispatch API (Roda#call)
was overrridden.
* The static_route method in the static_routing plugin must
now be called with a block. Previously, that would not
cause a failure until runtime, where it would fail when
you tried to execute the route.
jeremyevans-roda-4f30bb3/doc/release_notes/3.19.0.txt 0000664 0000000 0000000 00000016204 15167207754 0022301 0 ustar 00root root 0000000 0000000 = New Features
* A hash_routes plugin has been added for O(1) route dispatching at
any level of the routing tree. By default, Roda uses a linear
search of possible branches at each level of the routing tree,
which results in roughly O(log(n)) routing behavior in most
applications (where n is the total number of routes in the
application).
Assume you have the following routing tree:
route do |r|
r.on "a" do
# ...
end
r.on "b" do
# ...
end
r.is "c" do
# ...
end
# ...
end
With this routing tree, a request for /c will first check /a and
/b. This is not normally a performance issue, but if you have a
large number of routes at a particular level, it can be.
The hash_routes plugin allows you to convert this routing tree to:
plugin :hash_routes
hash_routes do
on "a" do |r|
# ...
end
on "b" do |r|
# ...
end
is "c" do |r|
# ...
end
# ...
end
route do |r|
r.hash_routes
end
This routing tree looks similar to Roda's standard routing tree, and
will have the same behavior as the previous example, but dispatching
to the routes inside the hash_routes block by the r.hash_routes
method will be an O(1) operation, instead of a linear search. This
can significantly improve performance in cases where you have a large
number of branches at any point in the routing tree.
In order to support O(1) route dispatching at any level of the
tree, the hash_routes plugin supports namespaces. You can use this
namespace support to keep the primary advantage of Roda when using
the hash_routes plugin, which is the ability to operate on a request
at any point during routing. Assume you have this routing tree:
hash_routes :root do
on "foo" do |r|
r.on Integer do |foo_id|
next unless @foo = Foo[foo_id]
r.hash_routes(:foo)
end
end
on "bar" do |r|
r.on Integer do |bar_id|
next unless @bar = Bar[bar_id]
r.hash_routes(:bar)
end
end
# ...
end
hash_routes :foo do
get "show" do
@page_title = @foo.name
view('foo/show')
end
# ...
end
hash_routes :bar do
post "edit" do
@bar.update(:name=>request.params['name'])
r.redirect "/"
end
# ...
end
route do |r|
r.hash_routes(:root)
end
With this routing tree, a GET /foo/123/show request will first get
dispatched to the on "foo" block in the :root namespace. That will
extract the 123 segment from the path, and use it to find the Foo
object with id 123 and set that to the instance variable @foo.
If there is no matching foo, the rest of the block will be skipped,
which will result in a 404 response. If there is a matching foo,
after setting the instance variable, it will dispatch to routes in
the :foo namespace, one of which is show, which will be able to use
the @foo variable, both in the route and in the view.
Similarly, a POST /bar/321/edit request would dispatch to the on
"bar" block in the :root namespace, will look up the matching bar,
then will dispatch to the edit route in the :bar namespace.
The hash_routes plugin can be used as a faster version of the
multi_route plugin's r.multi_route method. It can also be used as
a faster replacement for the multi_view plugin.
Please see the hash_routes plugin documentation for additional
methods and configuration styles supported by the plugin.
* A match_hook plugin has been added, which is called for each
successful match, before yielding to the match block. For example,
with the following routing tree:
plugin :match_hook
match_hook do
puts "#{r.matched_path}|#{r.remaining_path}"
end
route do |r|
r.on "a" do
r.is "b" do
r.get do
end
r.post do
end
end
end
end
A GET request for /a/b would call the match hook three times, and
output the following:
/a|/b # When the r.on block matches
/a/b| # When the r.is block matches
/a/b| # When the r.get block matches
A GET request for /a/c would call the match hook once, and output
the following:
/a|/b # When the r.on block matches
This plugin can be used to make debugging easier, as well as for
metrics.
= Other Improvements
* Per-cookie cipher secrets are now supported and used automatically
by default in the sessions plugin. This can prevent issues where
the cipher secret can be leaked if the random initialization vector
turns out not to be so random and ends up being reused. This
makes the session cookies slightly larger and about 10-20% slower.
Note that because of the way the sessions plugin is designed,
even if the cipher secret was leaked and you are not using
per-cookie cipher secrets, it would not allow an attacker to
forge a session, it would only allow them to read the contents of
an existing session.
If you are currently using the sessions plugin, and performing
rolling restarts, you should temporarily disable per-cookie session
secrets until all processes have been restarted and are able to
support per-cookie session secrets. You can do so by setting the
:per_cookie_cipher_secret sessions plugin option to false
temporarily until all processes have restarted and are running Roda
3.19.0+.
* When passing route blocks to Roda that have 0 arity instead of the
expected arity of 1, emulate an arity of 1 using an approach that is
about 2.75-8x faster. This emulation is still about 20% slower than
using the expected arity.
* Fix emulation of route blocks that have >1 arity but where the
expected arity is 1. Such blocks were not handled correctly in
Roda 3.18.0.
* String matching performance has been improved by 10-20%.
* Symbol and String class matching performance has improved by 10-20%.
* Terminal matching performance has improved by about 4x.
* Roda will now automatically load the direct_call plugin when
freezing the application if there is no middleware used and the
application has not been subclassed, for improved performance.
* Roda no longer builds the rack application until the app class
method is called. This can fix O(n^2) issues when building
applications with a lot of middleware. One consequence of
this is that a Roda.route block is no longer required. If
Roda.route is not called, then the default routing tree will
return a 404 response for all requests.
The delay_build plugin used to support delaying building the rack
application until a build! method is called. Now that Roda delays
building the rack application until the app method is called, there
is no reason to use this plugin, and it is now a no-op.
* The assets plugin :timestamp_paths option now supports a string
value to use a custom separator. A slash separator is still used
by default.
= Backwards Compatibility
* The static_routing plugin internals have changed, as the
static_routing is now implemented via the hash_routes plugin. If
you were depending on the internals, you will need to update your
code.
jeremyevans-roda-4f30bb3/doc/release_notes/3.2.0.txt 0000664 0000000 0000000 00000001167 15167207754 0022213 0 ustar 00root root 0000000 0000000 = New Features
* A timestamp_public plugin has been added for serving static files
with paths that change based on the modification timestamp of the
file. By using a new path, cached versions of the file will not
be used, fixing staleness issues. Example:
plugin :timestamp_public
route do |r|
# serves requests for /static/\d+/.*
r.timestamp_public
# /static/1234567890/path/to/file
timestamp_path("path/to/file")
end
= Other Improvements
* When using the assets plugin :timestamp_paths option, the
timestamps now include microseconds, to make cache poisoning more
difficult.
jeremyevans-roda-4f30bb3/doc/release_notes/3.20.0.txt 0000664 0000000 0000000 00000000444 15167207754 0022270 0 ustar 00root root 0000000 0000000 = Improvements
* For empty responses with status code 205, a Content-Length header
is now added with a value of 0, for better conformance to RFC 7232.
Similarly, when using the drop_body plugin, responses with status
code 205 now have a Content-Length header added with a value of 0.
jeremyevans-roda-4f30bb3/doc/release_notes/3.21.0.txt 0000664 0000000 0000000 00000000263 15167207754 0022270 0 ustar 00root root 0000000 0000000 = Improvements
* View rendering speed is significantly improved in development mode
by caching file-based templates until there has been a modification
to the template file.
jeremyevans-roda-4f30bb3/doc/release_notes/3.22.0.txt 0000664 0000000 0000000 00000002107 15167207754 0022270 0 ustar 00root root 0000000 0000000 = Improvements
* The render/view methods in the render plugin, when called with
a single string/symbol argument (the most common case), are now
up to 2.5x/4x faster by directly calling compiled template methods.
This works by extracting the UnboundMethod objects that Tilt
creates, and defining real methods for them, then calling those
methods using send. This avoids most of the overhead of the render
and view methods. The compiled template methods are defined inside
a module included in the Roda app's class, so this support works
even if the Roda app itself is frozen.
Some plugins, such as render_locals, do not work with this
optimization, and disable the use of it. The view_options plugin
does work with this optimization if you are using set_view_subdir or
append_view_subdir, but not if using set_view_options or
set_layout_options.
This optimization depends on Ruby 2.3+ and Tilt 1.2+, and will not
be used on earlier versions, or if an API change in Tilt is
detected.
* Session deserialization is now slightly faster in the sessions
plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/3.23.0.txt 0000664 0000000 0000000 00000002535 15167207754 0022276 0 ustar 00root root 0000000 0000000 = Improvements
* The render/view methods in the render plugin, when called with
a single string/symbol argument (the most common case), are now
up to 2x faster in cache: false mode by directly calling compiled
template methods. This takes the performance increase in 3.22.0
and applies it to cache: false mode in addition to cache: true
mode. If the template file has changed, the compiled method is
removed, and a new compiled method replaces it.
* Template modification detection in the render plugin now uses a
faster check for modification, which also avoids a race condition.
* The type_routing plugin now handles requests with nothing but the
extension in the request path. This fixes cases when you have
one app partially route a request, and send the request to another
app, and that app uses the type_routing plugin and has an r.is
call at the root level.
* The roda/session_middleware middleware now works correctly if the
type_routing plugin is loaded into Roda itself (as opposed to a
Roda subclass).
* The exception_page plugin now always shows the line number for
each line. Previously, it only showed the line number if it was
showing the content of the line, which complicated debugging in
cases where the content of the line was no longer retrievable
due to file system permissions or restrictions (e.g. chroot).
jeremyevans-roda-4f30bb3/doc/release_notes/3.24.0.txt 0000664 0000000 0000000 00000001155 15167207754 0022274 0 ustar 00root root 0000000 0000000 = Improvements
* The performance of the render_each plugin has been dramatically
improved by calling compiled template methods directly. For a simple
template, render_each performance with a single object can be about
2x faster, and render_each performance for 100 objects can be 3x
(cache: false) to 9x (cache: true) faster.
This optimization can be used if no options are provided to
render_each, or if :local and/or :locals options are provided. Use
of other options will disable this optimization.
* The module_include plugin no longer calls Proc.new without a
block, fixing a warning on Ruby 2.7.
jeremyevans-roda-4f30bb3/doc/release_notes/3.25.0.txt 0000664 0000000 0000000 00000001030 15167207754 0022265 0 ustar 00root root 0000000 0000000 = Improvements
* The new tilt 2.0.10 private API is now supported when using
compiled template methods, with up to a 33% performance increase.
The older tilt private API (back to tilt 1.2) is still supported.
* The performance of the render and view methods in the render plugin
when called with only the :locals option are now about 75% faster
by calling compiled template methods directly.
* Keyword argument separation issues are now handled on Ruby 2.7+
when defining methods with blocks that accept keyword arguments.
jeremyevans-roda-4f30bb3/doc/release_notes/3.26.0.txt 0000664 0000000 0000000 00000001240 15167207754 0022271 0 ustar 00root root 0000000 0000000 = New Features
* Asynchronous streaming is now supported in the streaming plugin,
using the :async option. When using this option, streaming
responses are temporarily buffered in a queue. By default, the
queue is a sized queue with a maximum of 10 elements, but the
queue can be specified manually via the :queue option, which
can be used with async libraries that support non-blocking
queues. This option is currently only supported on Ruby 2.3+.
= Other Improvements
* When combining multiple compiled assets into a single file, the
files are now separated by a newline, fixing issues when a
single line comment is used as the last line of a file.
jeremyevans-roda-4f30bb3/doc/release_notes/3.27.0.txt 0000664 0000000 0000000 00000001021 15167207754 0022267 0 ustar 00root root 0000000 0000000 = New Features
* A multibyte_string_matcher plugin has been added that supports
multibyte characters in strings used as matchers. It uses a slower
string matching implementation that supports multibyte characters.
As multibyte strings in paths must be escaped, this also loads the
unescape_path plugin.
= Other Improvements
* The json_parser plugin now returns expected results for invalid JSON
if the params_capturing plugin is used.
* lib/roda.rb has been split into multiple files for easier code
navigation.
jeremyevans-roda-4f30bb3/doc/release_notes/3.28.0.txt 0000664 0000000 0000000 00000000632 15167207754 0022277 0 ustar 00root root 0000000 0000000 = New Features
* The sessions plugin now supports RodaRequest#session_created_at
and RodaRequest#session_updated_at for the times of session
creation and last update.
= Other Improvements
* The json_parser plugin now correctly parses the request body even
if the request body has already been read.
* The sessions plugin now correctly handles upgrading rack cookie
sessions when using rack 2.0.8+.
jeremyevans-roda-4f30bb3/doc/release_notes/3.29.0.txt 0000664 0000000 0000000 00000000775 15167207754 0022310 0 ustar 00root root 0000000 0000000 = Improvements
* The common_logger plugin now includes the SCRIPT_NAME when
logging, for greater compatibility with typical web server
logs.
* The exception_page plugin now handles invalid POST data.
Previously, invalid POST data would cause the exception page
display to raise an exception.
* An error is now raised if trying to load a plugin that is not a
module or a recognized plugin symbol.
* Specs and older release notes are no longer shipped in the
gem, reducing gem size by over 35%.
jeremyevans-roda-4f30bb3/doc/release_notes/3.3.0.txt 0000664 0000000 0000000 00000026010 15167207754 0022206 0 ustar 00root root 0000000 0000000 = New Features
* A typecast_params plugin has been added for handling the
conversion of params to the expected type. This plugin is
recommended for all applications that deal with submitted
parameters.
Submitted parameters should be considered untrusted input, and in
standard use with browsers, parameters are submitted as strings
(or a hash/array containing strings). In most cases it makes sense
to explicitly convert the parameter to the desired type. While this
can be done via manual conversion:
key = request.params['key'].to_i
key = nil unless key > 0
the typecast_params plugin adds a friendlier interface:
key = typecast_params.pos_int('key')
As typecast_params is a fairly long method name, you may want to
consider aliasing it to something more terse in your application,
such as tp.
One advantage of using typecast_params is that access or conversion
errors are raised as a specific exception class
(Roda::RodaPlugins::TypecastParams::Error). This allows you to
handle this specific exception class globally and return an
appropriate 4xx response to the client. You can use the
Error#param_name and Error#reason methods to get more information
about the error.
typecast_params offers support for default values:
key = typecast_params.pos_int('key', 1)
The default value is only used if no value has been submitted for
the parameter, or if the conversion of the value results in nil.
Handling defaults for parameter conversion manually is more
difficult, since the parameter may not be present at all, or it may
be present but an empty string because the user did not enter a
value on the related form. Use of typecast_params for the
conversion handles both cases.
In many cases, parameters should be required, and if they aren't
submitted, that should be considered an error. typecast_params
handles this with ! methods:
key = typecast_params.pos_int!('key')
These ! methods raise an error instead of returning nil, and do not
allow defaults.
To make it easy to handle cases where many parameters need the same
conversion done, you can pass an array of keys to a conversion
method, and it will return an array of converted values:
key1, key2 = typecast_params.pos_int(['key1', 'key2'])
This is equivalent to:
key1 = typecast_params.pos_int('key1')
key2 = typecast_params.pos_int('key2')
The ! methods also support arrays of keys, ensuring that all
parameters have a value:
key1, key2 = typecast_params.pos_int!(['key1', 'key2'])
For handling of array parameters, where all entries in the array
use the same conversion, there is an array method which takes the
type as the first argument and the keys to convert as the second
argument:
keys = typecast_params.array(:pos_int, 'keys')
If you want to ensure that all entries in the array are converted
successfully and that there is a value for the array itself, you
can use array!:
keys = typecast_params.array!(:pos_int, 'keys')
This will raise an exception if any of the values in the array for
parameter keys cannot be converted to a positive integer.
Both array and array! support default values which are used if no
value is present for the parameter:
keys = typecast_params.array(:pos_int, 'keys', [])
keys = typecast_params.array!(:pos_int, 'keys', [])
You can also pass an array of keys to array or array!, if you would
like to perform the same conversion on multiple arrays:
foo_ids, bar_ids = typecast_params.array!(:pos_int, ['foo_ids', 'bar_ids'])
The previous examples have shown use of the pos_int method, which
uses to_i to convert the value to an integer, but returns nil if the
resulting integer is not positive. Unless you need to handle
negative numbers, it is recommended to use pos_int instead of int as
int will convert invalid values to 0 (since that is how
String#to_i works).
There are many built in methods for type conversion:
any :: Returns the value as is without conversion
str :: Raises if value is not already a string
nonempty_str :: Raises if value is not already a string, and
converts the empty string or string containing only
whitespace to nil
bool :: Converts entry to boolean if in one of the recognized
formats (case insensitive for strings):
nil :: nil, ''
true :: true, 1, '1', 't', 'true', 'yes', 'y', 'on'
false :: false, 0, '0', 'f', 'false', 'no', 'n', 'off'
If not in one of those formats, raises an error.
int :: Converts value to integer using to_i (note that invalid
input strings will be converted to 0)
pos_int :: Converts value using to_i, but non-positive values
are converted to nil
Integer :: Converts value to integer using
Kernel::Integer(value, 10)
float :: Converts value to float using to_f (note that invalid
input strings will be converted to 0.0)
Float :: Converts value to float using Kernel::Float(value)
Hash :: Raises if value is not already a hash
date :: Converts value to Date using Date.parse(value)
time :: Converts value to Time using Time.parse(value)
datetime :: Converts value to DateTime using DateTime.parse(value)
file :: Raises if value is not already a hash with a :tempfile key
whose value responds to read (this is the format rack uses
for uploaded files).
All of these methods also support ! methods (e.g. pos_int!), and all
of them can be used in the array and array! methods to support
arrays of values.
Since parameter hashes can be nested, the [] method can be used to
access nested
hashes:
# params: {'key'=>{'sub_key'=>'1'}}
typecast_params['key'].pos_int!('sub_key') # => 1
This works to an arbitrary depth:
# params: {'key'=>{'sub_key'=>{'sub_sub_key'=>'1'}}}
typecast_params['key']['sub_key'].pos_int!('sub_sub_key') # => 1
And also works with arrays at any depth, if those arrays contain
hashes:
# params: {'key'=>[{'sub_key'=>{'sub_sub_key'=>'1'}}]}
typecast_params['key'][0]['sub_key'].pos_int!('sub_sub_key') # => 1
# params: {'key'=>[{'sub_key'=>['1']}]}
typecast_params['key'][0].array!(:pos_int, 'sub_key') # => [1]
To allow easier access to nested data, there is a dig method:
typecast_params.dig(:pos_int, 'key', 'sub_key')
typecast_params.dig(:pos_int, 'key', 0, 'sub_key', 'sub_sub_key')
dig will return nil if any access while looking up the nested value
returns nil. There is also a dig! method, which will raise an Error
if dig would return nil:
typecast_params.dig!(:pos_int, 'key', 'sub_key')
typecast_params.dig!(:pos_int, 'key', 0, 'sub_key', 'sub_sub_key')
Note that none of these conversion methods modify request.params.
They purely do the conversion and return the converted value.
However, in some cases it is useful to do all the conversion up
front, and then pass a hash of converted parameters to an internal
method that expects to receive values in specific types. The
convert! method does this, and there is also a convert_each! method
designed for converting multiple values using the same block:
converted_params = typecast_params.convert! do |tp|
tp.int('page')
tp.pos_int!('artist_id')
tp.array!(:pos_int, 'album_ids')
tp.convert!('sales') do |stp|
tp.pos_int!(['num_sold', 'num_shipped'])
end
tp.convert!('members') do |mtp|
mtp.convert_each! do |stp|
stp.str!(['first_name', 'last_name'])
end
end
end
# converted_params:
# {
# 'page' => 1,
# 'artist_id' => 2,
# 'album_ids' => [3, 4],
# 'sales' => {
# 'num_sold' => 5,
# 'num_shipped' => 6
# },
# 'members' => [
# {'first_name' => 'Foo', 'last_name' => 'Bar'},
# {'first_name' => 'Baz', 'last_name' => 'Quux'}
# ]
# }
convert! and convert_each! only return values you explicitly specify
for conversion inside the passed block.
You can specify the :symbolize option to convert! or convert_each!,
which will symbolize the resulting hash keys:
converted_params = typecast_params.convert!(symbolize: true) do |tp|
tp.int('page')
tp.pos_int!('artist_id')
tp.array!(:pos_int, 'album_ids')
tp.convert!('sales') do |stp|
tp.pos_int!(['num_sold', 'num_shipped'])
end
tp.convert!('members') do |mtp|
mtp.convert_each! do |stp|
stp.str!(['first_name', 'last_name'])
end
end
end
# converted_params:
# {
# :page => 1,
# :artist_id => 2,
# :album_ids => [3, 4],
# :sales => {
# :num_sold => 5,
# :num_shipped => 6
# },
# :members => [
# {:first_name => 'Foo', :last_name => 'Bar'},
# {:first_name => 'Baz', :last_name => 'Quux'}
# ]
# }
Using the :symbolize option makes it simpler to transition from
untrusted external data (string keys), to trusted data that can be
used internally (trusted in the sense that the expected types are
used).
Note that if there are multiple conversion errors raised inside a
convert! or convert_each! block, they are recorded and a single
Roda::RodaPlugins::TypecastParams::Error instance is raised after
processing the block. TypecastParams::Error#param_names can be
called on the exception to get an array of all parameter names
with conversion issues, and TypecastParams::Error#all_errors
can be used to get an array of all Error instances.
Because of how convert! and convert_each! work, you should avoid
calling TypecastParams::Params#[] inside the block you pass to
these methods, because if the #[] call fails, it will skip the
reminder of the block.
Be aware that when you use convert! and convert_each!, the
conversion methods called inside the block may return nil if there
is a error raised, and nested calls to convert! and convert_each!
may not return values.
When loading the typecast_params plugin, a subclass of
TypecastParams::Params is created specific to the Roda application.
You can add support for custom types by passing a block when loading
the typecast_params plugin. This block is executed in the context
of the subclass, and calling handle_type in the block can be used to
add conversion methods. handle_type accepts a type name and the
block used to convert the type:
plugin :typecast_params do
handle_type(:album) do |value|
if id = convert_pos_int(val)
Album[id]
end
end
end
By default, the typecast_params conversion procs are passed the
parameter value directly from request.params without modification.
In some cases, it may be beneficial to strip leading and trailing
whitespace from parameter string values before processing, which
you can do by passing the strip: :all> option when loading the
plugin.
By design, typecast_params only deals with string keys, it is not
possible to use symbol keys as arguments to the conversion methods
and have them converted.
jeremyevans-roda-4f30bb3/doc/release_notes/3.30.0.txt 0000664 0000000 0000000 00000000771 15167207754 0022274 0 ustar 00root root 0000000 0000000 = New Features
* A :relative_paths plugin option has been added to the assets
plugin. This option makes the paths to the asset files in the
link and script tags relative paths instead of absolute paths.
= Other Improvements
* The :header matcher in the header_matchers plugin now works
correctly for the Content-Type and Content-Length headers, which
are not prefixed with HTTP_ in the rack environment.
* The run_append_slash and run_handler plugins now work correctly
when used together.
jeremyevans-roda-4f30bb3/doc/release_notes/3.31.0.txt 0000664 0000000 0000000 00000000745 15167207754 0022276 0 ustar 00root root 0000000 0000000 = New Features
* A relative_path plugin has been added, adding a relative_path
method that will take an absolute path and make it relative to the
current request by prepending an appropriate prefix. This is
helpful when using Roda as a static site generator to generate a
site that can be hosted at any subpath or directly from the
filesystem.
* In the path plugin, the path method now accepts a :relative
option for generating relative paths instead of absolute paths.
jeremyevans-roda-4f30bb3/doc/release_notes/3.32.0.txt 0000664 0000000 0000000 00000002432 15167207754 0022272 0 ustar 00root root 0000000 0000000 = New Features
* render_each in the render_each plugin now automatically handles
template names with subdirectories and extensions. Previously, these
caused issues unless the :local option was provided. So now you
can use:
render_each(foos, "items/foo")
instead of:
render_each(foos, "items/foo", :local=>:foo)
* each_partial has been added to the partials plugin. It operates
similarly to render_each, but uses the convention for partial
template naming. So this:
each_partial(foos, "items/foo")
is the same as:
render_each(foos, "items/_foo", :local=>:foo)
= Other Improvements
* The :dependencies option in the assets plugin now works correctly
with compiled templates in the render plugin in uncached mode
(the default in development). Previously, modifying a dependency
file would not result in recompiling the asset template when
requesting the main file.
* Method visibility issues in the following plugins have been fixed:
* content_security_policy
* default_headers
* indifferent_params
* placeholder_string_matchers
* symbol_matchers
Previously, these plugins made private methods public by mistake
when overriding them. Additionally, Roda.freeze no longer changes
the visibility of the set_default_headers private method.
jeremyevans-roda-4f30bb3/doc/release_notes/3.33.0.txt 0000664 0000000 0000000 00000000552 15167207754 0022274 0 ustar 00root root 0000000 0000000 = New Features
* The path plugin now supports a url method, allowing for returning
the entire URL instead of just the path for class-based paths.
* The public plugin now supports a :brotli option that will directly
serve brotli-compressed files (with .br extension) similar to how the
:gzip option directly serves gzipped files (with the .gz extension).
jeremyevans-roda-4f30bb3/doc/release_notes/3.34.0.txt 0000664 0000000 0000000 00000001104 15167207754 0022267 0 ustar 00root root 0000000 0000000 = Improvements
* Multiple unneeded conditionals have been removed.
* pre_content and post_context sections in backtraces are no longer
included in the exception_page plugin output if they would be
empty.
* The match_affix plugin can be loaded again with a single argument.
It was originally designed to accept a single argument, but a bug
introduced in 2.29.0 made it require two arguments.
* Core Roda and all plugins that ship with Roda now have 100% branch
coverage.
* The sinatra_helpers plugin no longer emits statement not reached
warnings in verbose mode.
jeremyevans-roda-4f30bb3/doc/release_notes/3.35.0.txt 0000664 0000000 0000000 00000000711 15167207754 0022273 0 ustar 00root root 0000000 0000000 = New Features
* An r plugin has been added. This plugin adds an r method for the
request, useful for allowing the use of r.halt and r.redirect even
in methods where the r local variable is not in scope.
= Other Improvements
* Attempting to load a plugin with an argument or block when the plugin
does not accept arguments or a block now warns. This is because a
future update to support a block or an optional argument could break
the call.
jeremyevans-roda-4f30bb3/doc/release_notes/3.36.0.txt 0000664 0000000 0000000 00000001155 15167207754 0022277 0 ustar 00root root 0000000 0000000 = New Features
* A multi_public plugin has been added, which allows serving static
files from multiple separate directories. This is especially
useful when there are different access control requirements per
directory.
* The content_security_policy now supports a
content_security_policy.report_to method to set the
report-to directive.
= Other Improvements
* When using the type_routing plugin and performing type routing
using the Accept request header, the Vary response header will be
added or updated so that http caches do not cache a response for one
type and serve it for a different type.
jeremyevans-roda-4f30bb3/doc/release_notes/3.37.0.txt 0000664 0000000 0000000 00000002415 15167207754 0022300 0 ustar 00root root 0000000 0000000 = New Features
* A custom_matchers plugin has been added, which allows using
arbitrary objects as matchers, as long as the matcher has been
registered. You can register matchers using the custom_matcher
class method, which takes the class of the matcher, and a block
which is yielded the matcher object. The block should return
nil or false if the matcher doesn't match, and any other value
if the matcher does match. Example:
plugin :custom_matchers
method_segment = Struct.new(:request_method, :next_segment)
custom_matcher(method_segment) do |matcher|
# self is the request instance ("r" yielded in the route block below)
if matcher.request_method == self.request_method
match(matcher.next_segment)
end
end
get_foo = method_segment.new('GET', 'foo')
post_any = method_segment.new('POST', String)
route do |r|
r.on('baz') do
r.on(get_foo) do
# GET method, /baz/foo prefix
end
r.is(post_any) do |seg|
# for POST /baz/bar, seg is "bar"
end
end
r.on('quux') do
r.is(get_foo) do
# GET method, /quux/foo route
end
r.on(post_any) do |seg|
# for POST /quux/xyz, seg is "xyz"
end
end
end
jeremyevans-roda-4f30bb3/doc/release_notes/3.38.0.txt 0000664 0000000 0000000 00000000313 15167207754 0022274 0 ustar 00root root 0000000 0000000 = Improvements
* The error_email and error_mail plugins now rescue invalid parameter
errors when preparing the email body, because you generally don't
want your error handler to raise an exception.
jeremyevans-roda-4f30bb3/doc/release_notes/3.39.0.txt 0000664 0000000 0000000 00000001225 15167207754 0022300 0 ustar 00root root 0000000 0000000 = Improvements
* The relative_path plugin is now faster if you are calling
relative_path or relative_prefix more than once when handling a
request.
* The typecast_params.convert! method in the typecast_params plugin
now handles explicit nil values the same as missing values.
Explicit nil values do not generally occur in normal Rack parameter
parsing, but they can occur when using the json_parser plugin to
parse JSON requests.
* Roda now avoids method redefinition warnings in verbose mode by
using a self alias. As Ruby 3 is dropping uninitialized instance
variable warnings, Roda will be verbose warning free if you are
using Ruby 3.
jeremyevans-roda-4f30bb3/doc/release_notes/3.4.0.txt 0000664 0000000 0000000 00000001450 15167207754 0022210 0 ustar 00root root 0000000 0000000 = New Features
* A middleware_stack plugin has been added for more detailed control
over middleware, allowing for the removal of middleware and the
insertion of middleware before existing middleware. Example:
plugin :middleware_stack
# Remove csrf middleware
middleware_stack.remove{|m, *args| m == Rack::Csrf}
# Insert csrf middleware before logger middleware
middleware_stack.before{|m, *args| m == Rack::CommonLogger}.
use(Rack::Csrf, raise: true)
# Insert csrf middleware after logger middleware
middleware_stack.after{|m, *args| m == Rack::CommonLogger}.
use(Rack::Csrf, raise: true)
= Other Improvements
* The head plugin now calls close on the response body if the body
responds to close. Previously an existing response body was
just ignored.
jeremyevans-roda-4f30bb3/doc/release_notes/3.40.0.txt 0000664 0000000 0000000 00000001774 15167207754 0022301 0 ustar 00root root 0000000 0000000 = New Features
* A precompile_views method has been added to the
precompile_templates plugin. This method works with Roda's
optimized compiled view methods, allowing additional memory
sharing between parent and child processes.
* A freeze_template_caches! method has been added to the
precompile_templates plugin. This freezes the template caches,
preventing the compilation of additional templates, useful for
enforcing that only precompiled templates are used. Additionally,
this speeds up access to the template caches.
* RodaCache#freeze now returns the frozen internal hash, which can
then be accessed without a mutex. Previously, freeze only froze
the receiver and not the internal hash, so it didn't have the
expected effect.
= Other Improvements
* The view method in the render plugin is now faster in most cases
when a single argument is used. When freezing the application,
an additional optimization is performed to increase the
performance of the view method even further.
jeremyevans-roda-4f30bb3/doc/release_notes/3.41.0.txt 0000664 0000000 0000000 00000000625 15167207754 0022274 0 ustar 00root root 0000000 0000000 = Improvements
* The performance of the render plugin's view method when passed the
:content option and no other options or arguments has been improved
by about 3x, by calling compiled template methods directly.
* The compiled template method for the layout is cleared when the
render plugin is loaded again, which can fix issues when it is
loaded with different options that affect the layout.
jeremyevans-roda-4f30bb3/doc/release_notes/3.42.0.txt 0000664 0000000 0000000 00000001426 15167207754 0022275 0 ustar 00root root 0000000 0000000 = New Features
* A recheck_precompiled_assets plugin has been added, which allows
for checking for updates to the precompiled asset metadata file,
and automatically using the updated data.
* The common_logger plugin now supports a :method plugin option to
specify the method to call on the logger.
= Other Improvements
* Plugins and middleware that use keyword arguments are now supported
in Ruby 3.
* The compile_assets class method in the assets plugin now uses an
atomic approach to writing the precompiled asset metadata file.
* Minor method visibility issues have been fixed. The custom_matchers
plugin no longer makes the unsupported_matcher request method
public, and the render plugin no longer makes the _layout_method
public when the application is frozen.
jeremyevans-roda-4f30bb3/doc/release_notes/3.43.0.txt 0000664 0000000 0000000 00000002107 15167207754 0022273 0 ustar 00root root 0000000 0000000 = New Features
* A host_authorization plugin has been added to verify the requested
Host header is authorized. Using it can prevent DNS rebinding
attacks in cases where the application can receive requests for
arbitrary hosts.
To check for authorized hosts in your routing tree, you call the
check_host_authorization! method. For example, if you want to
check for authorized hosts after serving requests for public
files, you could do:
plugin :public
plugin :host_authorization, 'my-domain-name.example.com'
route do |r|
r.public
check_host_authorization!
# ... rest of routing tree
end
In addition to handling single domain names via a string, you can
provide an array of domain names, a regexp to match again, or a
proc.
By default, requests using unauthorized hosts receive an empty 403
response. If you would like to customize the response, you can
pass a block when loading the plugin:
plugin :host_authorization, 'my-domain-name.example.com' do |r|
response.status = 403
"Response Body Here"
end
jeremyevans-roda-4f30bb3/doc/release_notes/3.44.0.txt 0000664 0000000 0000000 00000002011 15167207754 0022266 0 ustar 00root root 0000000 0000000 = New Features
* An optimized_segment_matchers plugin has been added that offers
very fast matchers for arbitrary segments (the same segments
that would be matched by the String class matcher). The
on_segment method it offers accepts no arguments and yields
the next segment if there is a segment. The is_segment method
is similar, but only yields if the next segment is the final
segment.
= Other Improvements
* The send_file and attachment methods in the sinatra_helpers plugin
now support RFC 5987 UTF-8 and ISO-8859-1 encoded filenames,
allowing modern browsers to save files with encoded chracters. For
older browsers that do not support RFC 5987, unsupported characters
in filenames are replaced with dashes. This is considered to be an
improvement over the previous behavior of using Ruby's inspect
output for the filename, which could contain backslashes (backslash
is not an allowed chracter in Windows filenames).
* The performance of the String class matcher has been slightly
improved.
jeremyevans-roda-4f30bb3/doc/release_notes/3.45.0.txt 0000664 0000000 0000000 00000001644 15167207754 0022302 0 ustar 00root root 0000000 0000000 = Improvements
* The typecast_params plugin checks now checks for null bytes by
default before typecasting. If null bytes are present, it raises
an error. Most applications do not require null bytes in
parameters, and in some cases allowing them can lead to security
issues, especially when parameters are passed to C extensions.
In general, the benefit of forbidding null bytes in parameters is
greater than the cost.
If you would like to continue allowing null bytes, use the
:allow_null_bytes option when loading the plugin.
Note that this change does not affect uploaded files, since those
are expected to contain null bytes.
= Backwards Compatibility
* The change to the typecast_params plugin to raise an error for
null bytes can break applications that are expecting null bytes
to be passed in parameters. Such applications should use the
:allow_null_bytes option when loading the plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/3.46.0.txt 0000664 0000000 0000000 00000001457 15167207754 0022305 0 ustar 00root root 0000000 0000000 = Improvements
* The r.on, r.is, r.get and r.post methods (and other verb methods
if using the all_verbs plugin) have now been optimized when using
a single string or regexp matcher, or the String or Integer class
matcher. Since those four matchers are the most common types of
matchers passed to the methods, this can significantly improve
routing performance (about 50% in the r10k benchmark).
This optimization is automatically applied when freezing
applications, if the related methods have not been modified by
plugins.
This optimization does come at the expense of a small decrease
in routing performance (3-4%) for unoptimized cases, but the
majority of applications will see a overall performance benefit
from this change.
* Other minor performance improvements have been made.
jeremyevans-roda-4f30bb3/doc/release_notes/3.47.0.txt 0000664 0000000 0000000 00000000563 15167207754 0022303 0 ustar 00root root 0000000 0000000 = Improvements
* The r.on optimization added in 3.46.0 has been extended to optimize
all single argument calls. This results in the following speedups
based on argument type:
* Hash matching: 10%
* Array/Symbol/Class matching: 15%
* Proc matching: 25%
* true matching: 45%
* false/nil matching: 65%
* Other minor performance improvements have been made.
jeremyevans-roda-4f30bb3/doc/release_notes/3.48.0.txt 0000664 0000000 0000000 00000001055 15167207754 0022301 0 ustar 00root root 0000000 0000000 = New Features
* A named_routes plugin has been added, for defining named route
blocks that you can dispatch to with r.route. This feature was
previously available as part of the multi_route plugin, but there
are cases where the r.route method and support for named routes is
helpful even when the multi_route plugin is not used (such as when
the hash_routes plugin is used instead of the multi_route plugin).
The multi_route plugin now depends on the named_routes plugin, so
this change should not cause any backwards compatibility issues.
jeremyevans-roda-4f30bb3/doc/release_notes/3.49.0.txt 0000664 0000000 0000000 00000001317 15167207754 0022303 0 ustar 00root root 0000000 0000000 = Improvements
* The r.is optimization added in 3.46.0 has been extended to optimize
all single argument calls. This results in the following speedups
based on argument type:
* Hash/Class matching: 20%
* Symbol matching: 25%
* Array matching: 35%
* Proc matching: 50%
* false/nil matching: 65%
* Roda now uses defined?(yield) instead of block_given? internally
for better performance on CRuby. defined?(yield) is faster as it is
built into the VM, while block_given? is a regular method and has
the overhead of calling a regular method. Note that defined?(yield)
is not implemented correctly on JRuby before 9.0.0.0, so this
release of Roda drops support for JRuby versions before 9.0.0.0.
jeremyevans-roda-4f30bb3/doc/release_notes/3.5.0.txt 0000664 0000000 0000000 00000002211 15167207754 0022205 0 ustar 00root root 0000000 0000000 = New Features
* A request_aref plugin has been added for configuring the behavior
of the [] and []= request methods. These methods are deprecated
in the current version of Rack, but Rack will only print a
deprecation warning in verbose mode. With this plugin, you can
choose to never warn, always warn, or raise an exception:
# Don't emit a warning, allowing for the historical Rack
# behavior
plugin :request_aref, :allow
# Always emit a warning when the method is called
plugin :request_aref, :warn
# Raise an exception if the method is called
plugin :request_aref, :raise
= Other Improvements
* When using the content_for plugin and calling content_for with a
block, convert the result of the block to a string before passing
the result to Tilt. This can fix issues when the template class
that Tilt uses does not handle non-String input.
* When using the public plugin with the :gzip option, do not add the
Content-Type or Content-Encoding headers if a 304 response is
returned.
* Add the spec/views/about directory to the gem, allowing the specs
to run correctly using just the files in the gem.
jeremyevans-roda-4f30bb3/doc/release_notes/3.50.0.txt 0000664 0000000 0000000 00000002112 15167207754 0022265 0 ustar 00root root 0000000 0000000 = New Features
* An inject_erb plugin has been added, adding an inject_erb method
that allows for injecting content directly into the template output
for the template currently being rendered. This allows you to more
easily wrap blocks in templates, by calling methods that accept
template blocks and injecting content before and after the block.
* A capture_erb plugin has been added, adding a capture_erb method
for capturing a template block in an erb template and returning
the content appended during the block as a string, instead of
having the content of the template block be included directly into
the template output. This can be combined with the inject_erb
plugin to inject modified versions of captured blocks into template
output.
* The hash_routes plugin now allows calling hash_branch and hash_path
without a block in order to remove the existing route handler. This
is designed to be used with code reloading libraries, so that if a
route file is deleted, the related hash branches/paths are also
removed, without having to reload all route files.
jeremyevans-roda-4f30bb3/doc/release_notes/3.51.0.txt 0000664 0000000 0000000 00000001306 15167207754 0022272 0 ustar 00root root 0000000 0000000 = New Features
* The named_routes plugin now allows calling route without a block
to remove the existing route handler. The multi_run plugin
now allows calling run without an app to remove an existing handler.
These changes are designed to better support code reloading
libraries, so that if the related file is deleted, the related
handlers are also removed, without having to reload the entire
application.
= Other Improvements
* The error_handler plugin now avoids a method redefinition warning
in verbose warning mode.
= Other
* Roda's primary discussion forum is now GitHub Discussions. The
ruby-roda Google Group is still available for users who would
prefer to use that instead.
jeremyevans-roda-4f30bb3/doc/release_notes/3.52.0.txt 0000664 0000000 0000000 00000001502 15167207754 0022271 0 ustar 00root root 0000000 0000000 = New Features
* The typecast_params plugin now supports a :date_parse_input_handler
option that will be called with all input that will be passed to
the date parsing methods. You can use this option to automatically
truncate input, if that is perferable to raising an error (which is
how recent versions of Ruby handle too-long input).
= Other Improvements
* The path helper methods added by the path plugin now support
blocks that use keyword arguments on Ruby 3+.
* The assets plugin now uses OpenSSL::Digest instead of Digest (if
available) for calculating SRI digests. This is faster on Ruby 3+,
where Digest no longer uses the faster OpenSSL::Digest automatically
if available.
* Roda.freeze now returns self when the multi_route plugin is used.
This was broken (not returning self) starting in 3.48.0.
jeremyevans-roda-4f30bb3/doc/release_notes/3.53.0.txt 0000664 0000000 0000000 00000001042 15167207754 0022271 0 ustar 00root root 0000000 0000000 = New Features
* An additional_view_directories plugin has been added, which allows
you to specify additional directories to look in for templates.
If the template path does not exist when using the default view
directory, then each additional view directory will be checked,
returning the first path that exists:
plugin :additional_view_directories, ['admin_views', 'public_views']
= Other Improvements
* The indifferent_params plugin now avoids a deprecation warning when
using the rack main branch, which will become Rack 3.
jeremyevans-roda-4f30bb3/doc/release_notes/3.54.0.txt 0000664 0000000 0000000 00000003710 15167207754 0022276 0 ustar 00root root 0000000 0000000 = New Features
* You can now override the type attribute for script tags produced
by the assets plugin, by providing a :type attribute when calling
the assets method.
= Other Improvements
* Reloading the render plugin after the additional_view_directories
plugin no longer removes the additional view directories from
the allowed paths for templates.
* When using Rack 3, Roda will now use an instance of Rack::Headers
instead of a plain hash for the headers, allowing for compliance
with the Rack 3 SPEC (which will require lowercase header keys).
* The public, multi_public, and sinatra_helpers plugin now use
Rack::Files instead of Rack::File if available, as Rack::File will
be deprecated in Rack 3.0.
* The json_parser plugin no longer rewinds the request body before
and after reading it when used with Rack 3.0, as Rack 3.0 has
dropped the requirement for rewindable input.
* The run_handler plugin now closes bodies for upstream 404 responses
when using the not_found: :pass option.
* The chunked plugin no longer uses Transfer-Encoding: chunked by
default. Requiring the use of Transfer-Encoding: chunked made the
plugin only work on HTTP 1.1, and not older or newer versions. The
plugin still allows for streaming template bodies as they are being
rendered. To get the previous behavior of forcing the use of
Transfer-Encoding: chunked, you can use the :force_chunked_encoding
plugin option
* Roda now supports testing with Rack::Lint. This found multiple
violations of the Rack SPEC which are fixed in this version, and
should ensure that Roda stays in compliance with the Rack SPEC going
forward.
= Backwards Compatibility
* Roda will no longer set the Content-Length header for 205 responses
when using Rack <2.0.2, as doing so violates the Rack SPEC for those
Rack versions.
* The drop_body plugin now drops response bodies for all 1xx responses,
not just for 100 and 101 responses, in compliance with the Rack SPEC.
jeremyevans-roda-4f30bb3/doc/release_notes/3.55.0.txt 0000664 0000000 0000000 00000001140 15167207754 0022272 0 ustar 00root root 0000000 0000000 = New Features
* A :forward_response_headers option has been added to the middleware
plugin, which uses the response headers added by the middleware
as default response headers even if the middleware does not handle
the response. Response headers set by the underlying application
take precedence over response headers set by the middleware.
* The render plugin view method now accepts a block and will pass the
block to the underlying render method call. This is useful for
rendering a template that yields inside of an existing layout.
Previously, you had to nest render calls to do that.
jeremyevans-roda-4f30bb3/doc/release_notes/3.56.0.txt 0000664 0000000 0000000 00000002731 15167207754 0022302 0 ustar 00root root 0000000 0000000 = New Features
* RodaRequest#http_version has been added for determining the HTTP
version the request was submitted with. This will be a string
such as "HTTP/1.0", "HTTP/1.1", "HTTP/2", etc. This will use the
SERVER_PROTOCOL and HTTP_VERSION entries from the environment to
determine which HTTP version is in use.
* The status_handler method in the status_handler plugin now supports
a :keep_headers option. The value for this option should be an
array of header names to keep. All other headers are removed. The
default behavior without the option is still to remove all headers.
* A run_require_slash plugin has been added, which will skip
dispatching to another rack application if the remaining path is not
empty and does not start with a slash.
= Other Improvements
* The status_303 plugin will use 303 as the default redirect status
for non-GET requests for HTTP/2 and higher HTTP versions. Previously,
it only used 303 for HTTP/1.1.
* The not_allowed plugin now overrides the r.root method to return
405 responses to non-GET requests to the root.
* The not_allowed plugin no longer sets the body when returning 405
responses using methods such as r.get and r.post. Previously, the
body was unintentionally set to the same value as the Allow header.
* When using the Rack master branch (what will become Rack 3), Roda
only requires the parts of rack that it uses, instead of requiring
rack and relying on autoload to load the parts of rack in use.
jeremyevans-roda-4f30bb3/doc/release_notes/3.57.0.txt 0000664 0000000 0000000 00000002453 15167207754 0022304 0 ustar 00root root 0000000 0000000 = New Features
* hash_branches and hash_paths plugins have been split off from the
hash_routes plugin, allowing you to use only those parts instead
of all of hash_routes.
The hash_branches plugin supports the hash_branch class method
and r.hash_branches routing method.
The hash_paths plugin supports the hash_path class method and
r.hash_paths routing method.
The hash_routes plugin functions as it did previously by
requiring the hash_branches and hash_paths plugins. It adds
the hash_routes DSL and r.hash_routes routing method.
* A hash_branch_view_subdir has been added. It builds on the
view_options plugin and new hash_branches plugin, automatically
appending a view subdirectory for each successful hash branch.
This can DRY up code that uses a separate view subdirectory for
each branch.
= Other Improvements
* Unprintable characters are now hex escaped in the output of the
common_logger plugin. This can protect users who use software
that respects shell escape sequences to view the logs.
= Backwards Compatibility
* The static_routing plugin now depends on the hash_paths plugin
instead of the hash_routes plugin, so you will need to update
your application to explicitly load the hash_routes plugin if
you were relying on static_routing to implicitly load it.
jeremyevans-roda-4f30bb3/doc/release_notes/3.58.0.txt 0000664 0000000 0000000 00000001142 15167207754 0022277 0 ustar 00root root 0000000 0000000 = New Features
* A filter_common_logger plugin has been added, allowing you to skip
logging of certain requests in the common_logger plugin. This
allows you to only log requests for certain paths, or only log
requests for certain types of responses.
= Other Improvements
* The heartbeat plugin is now compatible with recent changes in the
rack master branch (what will be rack 3).
* The exception_page plugin will now use Exception#detailed_message
on Ruby 3.2+, preserving the did_you_mean and error_highlight
information. Additionally, the display of exception messages
has been improved.
jeremyevans-roda-4f30bb3/doc/release_notes/3.59.0.txt 0000664 0000000 0000000 00000001201 15167207754 0022274 0 ustar 00root root 0000000 0000000 = New Features
* An additional_render_engines plugin has been added, for considering
multiple render engines for templates. If the template path does not
exist for the default render engine, then each additional render
engine will be checked, returning the first path that exists:
plugin :additional_render_engines, ['haml', 'str']
This is similar to the additional_view_directories plugin added in
3.53.0. Both plugins can be used if you want to consider multiple
view directories and multiple render engines.
= Other Improvements
* A typo in a private method name in the delete_empty_headers plugin
has been fixed.
jeremyevans-roda-4f30bb3/doc/release_notes/3.6.0.txt 0000664 0000000 0000000 00000001760 15167207754 0022216 0 ustar 00root root 0000000 0000000 = New Features
* An early_hints plugin has been added for senting 103 Early Hint
responses. This is currently only supported on puma 3.11+, and
can allow for improved performance by letting the requestor know
which related files will be needed by the request.
* An :early_hints option has been added to the assets plugin. If
given, calling the assets method will also issue an early hint
for the related assets.
* A :wrap option has been added to the json_parser plugin. If set
to :always, all uploaded json data will be stored using a hash
with a "_json" key. If set to :unless_hash, uploaded json data
will only be wrapped in such a matter if it is not already a hash.
Using the :wrap option can fix problems when using r.params when
the uploaded JSON data is an array and not a hash. However, it
does change the behavior of r.POST. It is possible to handle
uploaded JSON array data without the :wrap option by using r.GET
and r.POST directly instead of using r.params.
jeremyevans-roda-4f30bb3/doc/release_notes/3.60.0.txt 0000664 0000000 0000000 00000002733 15167207754 0022277 0 ustar 00root root 0000000 0000000 = New Features
* A link_to plugin has been added with a link_to method for
creating HTML links.
The simplest usage of link_to is passing the body and the location
to link to as strings:
# Instance level
link_to("body", "/path")
# => "body"
The link_to plugin depends on the path plugin, and allows you to
pass symbols for named paths:
# Class level
path :foo, "/path/to/too"
# Instance level
link_to("body", :foo)
# => "body"
It also allows you to pass instances of classes that you have
registered with the path plugin:
# Class level
A = Struct.new(:id)
path A do
"/path/to/a/#{id}"
end
# Instance level
link_to("body", A.new(1))
# => "body"
To set additional HTML attributes on the tag, you can pass them as
an options hash:
link_to("body", "/path", foo: "bar")
# => "body"
If the body is nil, it will be set to the same as the path:
link_to(nil, "/path")
# => "/path"
The plugin will automatically HTML escape the path and any HTML
attribute values, using the h plugin:
link_to("body", "/path?a=1&b=2", foo: '"bar"')
# => "body"
= Other Improvements
* Coverage testing has been expanded to multiple rack versions, instead
of just the current rack release.
jeremyevans-roda-4f30bb3/doc/release_notes/3.61.0.txt 0000664 0000000 0000000 00000001733 15167207754 0022277 0 ustar 00root root 0000000 0000000 = Improvements
* The typecast_params plugin now limits input bytesize for integer,
float, and date/time typecasts. If the input is over the allowed
bytesize, typecasting will fail. This prevents issues with trying
to typecast arbitrarily large input.
* The default Integer class matcher now limits integer segments to
100 characters by default, also to prevent issues with typecasting
arbitrarily large input. Segments larger than 100 characters will
no longer be matched by the Integer class matcher.
= Backwards Compatibility
* If the input bytesize limits in the typecast_params plugin cause
issues in your application, you can use the :skip_bytesize_checking
option when loading the plugin to disable the checks.
* If the default Integer class matcher limit causes problems in your
application, you can use the class_matchers plugin to override the
matcher to not use a limit:
plugin :class_matchers
class_matcher(Integer, /(\d+)/){|a| [a.to_i]}
jeremyevans-roda-4f30bb3/doc/release_notes/3.62.0.txt 0000664 0000000 0000000 00000003427 15167207754 0022302 0 ustar 00root root 0000000 0000000 = New Features
* An Integer_matcher_max plugin has been added for setting the
maximum value matched by the Integer matcher (the minimum is
always 0, since the Integer matcher does not match negative
integers). The default maximum value when using the plugin
is 2**63-1, the maximum value for a signed 64-bit integer.
You can specify a different maximum value by passing an argument
when loading the plugin.
* A typecast_params_sized_integers plugin has been added for
converting parameters to integers only if the integer is within a
specific size. By default, the plugin supports 8-bit, 16-bit,
32-bit, and 64-bit signed and unsigned integer types, with the
following typecast_params methods added by the plugin:
* int8, uint8, pos_int8, pos_uint8, Integer8, Integeru8
* int16, uint16, pos_int16, pos_uint16, Integer16, Integeru16
* int32, uint32, pos_int32, pos_uint32, Integer32, Integeru32
* int64, uint64, pos_int64, pos_uint64, Integer64, Integeru64
You can override what sizes are added by default by using the
:sizes option. You can also specify a :default_size option,
in which case the default int, pos_int, and Integer conversions
will use the given size. So if you want to change the default
typecast_params integer conversion behavior to only support
integer values that can fit in 64-bit signed integers, you can
use:
plugin :typecast_params_sized_integers, sizes: [64],
default_size: 64
= Other Improvements
* The block passed to the class_matcher method in the class_matchers
plugin can now return nil/false to signal that it should not match.
This is useful when the regexp argument provided matches segments
not valid for the class.
* RodaRequest#matched_path now works correctly when using the
unescape_path plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/3.63.0.txt 0000664 0000000 0000000 00000002765 15167207754 0022307 0 ustar 00root root 0000000 0000000 = New Features
* An autoload_hash_branches plugin has been added for autoloading
route files for each hash branch, instead of requiring the route
files be loaded up front. For example, to automatically load a
route file for a hash branch on the first request to that branch:
plugin :autoload_hash_branches
autoload_hash_branch('branch_name', '/path/to/file')
autoload_hash_branch('namespace', 'branch_name', '/path/to/file')
The route file loaded should define the expected hash branch.
It is common to have route files stored in a directory, with the
file name matching the branch name. In that case, you can set
autoloading for all route files in a given directory:
plugin :autoload_hash_branches
autoload_hash_branch_dir('/path/to/dir')
autoload_hash_branch_dir('namespace', '/path/to/dir')
Note that autoloading hash branches does not work if the application
is frozen. This plugin should only be used in development mode for
faster startup, or when running tests on a subset of the application
in order to avoid loading parts of the application unrelated to what
is being tested.
* The mailer plugin now supports a :terminal plugin option to make
the r.mail method force a terminal match, similar to how r.get
and other HTTP verb methods work in standard Roda. This behavior
will become the default in Roda 4.
= Other Improvements
* The mailer plugin now correctly sets the content_type of the body
for emails with attachments when using mail 2.8.0+.
jeremyevans-roda-4f30bb3/doc/release_notes/3.64.0.txt 0000664 0000000 0000000 00000002135 15167207754 0022277 0 ustar 00root root 0000000 0000000 = New Features
* An erb_h plugin has been added for faster HTML escaping using
erb/escape. erb 4 added erb/escape and it is included in Ruby 3.2.
The erb_h plugin is added as a separate plugin because it changes
the behavior of the h method. The h method added by the h plugin
will always return a new string, but the h method added by the
erb_h plugin will return the argument if the argument is a
string that does not need escaping. By avoiding unnecessary
string allocations, use of the erb_h plugin can speed up HTML
escaping.
= Other Improvements
* The autoload_hash_branches plugin added in Roda 3.63.0 will now
eagerly load the hash branches when freezing the application,
allowing the application to continue to work after being frozen.
Additionally, file paths for the hash branches will now be
automatically expanded, allowing the use of relative file paths.
= Backwards Compatibility
* The expanding of file paths in the autoload_hash_branches plugin
can break applications that were providing relative paths and
expecting them to be looked up using the Ruby load path.
jeremyevans-roda-4f30bb3/doc/release_notes/3.65.0.txt 0000664 0000000 0000000 00000000706 15167207754 0022302 0 ustar 00root root 0000000 0000000 = New Features
* An autoload_named_routes plugin has been added for autoloading files
for a named route setup by the named_routes plugin when there is a
request for that route.
= Other Improvements
* The path method in the path plugin now supports a :class_name option.
You can set this option to true and use a class name String/Symbol
to register paths for classes without referencing the related class,
useful when autoloading the class.
jeremyevans-roda-4f30bb3/doc/release_notes/3.66.0.txt 0000664 0000000 0000000 00000001614 15167207754 0022302 0 ustar 00root root 0000000 0000000 = New Features
* A render_coverage plugin has been added, which will cause compiled
template code to be saved to a folder and loaded using load instead
of eval. This allows for coverage to work for the compiled template
code in Ruby versions before 3.2. It can also allow for verbose
syntax warnings in compiled template code (ignored by eval), and
can also be useful for static analysis of compiled template code.
This plugin requires tilt 2.1+.
* The exception_page plugin now supports exception_page_{css,js}
instance methods for overriding the CSS and JavaScript on the
generated exception page.
= Other Improvements
* Using inline templates (render/view :inline option) no longer keeps
a reference to the Roda instance that caches the template.
= Backwards Compatibility
* The Render::TemplateMtimeWrapper API has changed. Any external
use of this class needs to be updated.
jeremyevans-roda-4f30bb3/doc/release_notes/3.67.0.txt 0000664 0000000 0000000 00000001441 15167207754 0022301 0 ustar 00root root 0000000 0000000 = New Feature
* A custom_block_results plugin has been added for custom handling
of block results. This allows routing blocks to return
arbitrary objects instead of just String, nil, and false, and
to have custom handling for them. For example, if you want to
be able to have your routing blocks return the status code to use,
you could do:
plugin :custom_block_results
handle_block_result Integer do |result|
response.status_code = result
end
route do |r|
200
end
While the expected use of the handle_block_result method is with
class arguments, you can use any argument that implements an
appropriate === method.
The symbol_views and json plugins, which support additional block
results, now use the custom_block_results plugin internally.
jeremyevans-roda-4f30bb3/doc/release_notes/3.68.0.txt 0000664 0000000 0000000 00000001160 15167207754 0022300 0 ustar 00root root 0000000 0000000 = New Feature
* Roda.run in the multi_run plugin now accepts blocks, to allow
autoloading of apps to dispatch to:
class App < Roda
plugin :multi_run
run("other_app"){OtherApp}
route do |r|
r.multi_run
end
end
With the above example, the block is not evaluated until a
request for the /other_app branch is received. If OtherApp is
autoloaded, this can speed up application startup and partial
testing. When freezing the application (for production use),
the block is eagerly loaded, so that requests to the
/other_app branch do not call the block on every request.
jeremyevans-roda-4f30bb3/doc/release_notes/3.69.0.txt 0000664 0000000 0000000 00000001776 15167207754 0022316 0 ustar 00root root 0000000 0000000 = New Feature
* The symbol_matcher method in the symbol_matchers plugin now
supports a block to allow for type conversion of matched
segments:
symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
[Date.new(y.to_i, m.to_i, d.to_i)]
end
route do |r|
r.on :date do |date|
# date is an instance of Date
end
end
As shown above, the block should return an array of objects to yield
to the match block.
If you have a segment match the passed regexp, but decide during block
processing that you do not want to treat it as a match, you can have the
block return nil or false. This is useful if you want to make sure you
are using valid data:
symbol_matcher(:date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
y = y.to_i
m = m.to_i
d = d.to_i
[Date.new(y, m, d)] if Date.valid_date?(y, m, d)
end
When providing a block when using the symbol_matchers method, that
symbol may not work with the params_capturing plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/3.7.0.txt 0000664 0000000 0000000 00000010140 15167207754 0022207 0 ustar 00root root 0000000 0000000 = New Features
* A content_security_policy plugin has been added for setting up an
appropriate Content-Security-Policy header. To configure the
default policy, load the plugin with a block:
plugin :content_security_policy do |csp|
csp.default_src :none
csp.img_src :self
csp.style_src :self, 'fonts.googleapis.com'
csp.script_src :self
csp.font_src :self, 'fonts.gstatic.com'
csp.form_action :self
csp.base_uri :none
csp.frame_ancestors :none
csp.block_all_mixed_content
end
It's recommended that use use a default_src of :none at the top
of the policy, then explicitly change other settings (e.g. img_src)
when you want to allow content.
Anywhere in the routing tree, you can use the content_security_policy
method to override the default policy. You can pass this method a
block:
r.get 'foo' do
content_security_policy do |csp|
csp.object_src :self
csp.add_style_src 'bar.com'
end
# ...
end
Or just call a method on it:
r.get 'foo' do
content_security_policy.script_src :self, 'example.com', [:nonce, 'foobarbaz']
# ...
end
The following methods exist for configuring the content security policy, they set
the appropriate directive, with the underscores replaced by a dash.
* base_uri
* child_src
* connect_src
* default_src
* font_src
* form_action
* frame_ancestors
* frame_src
* img_src
* manifest_src
* media_src
* object_src
* plugin_types
* report_uri
* require_sri_for
* sandbox
* script_src
* style_src
* worker_src
All of these methods support any number of arguments, and each argument
should be one of the following types:
String :: used verbatim
Symbol :: Substitutes underscore with dash and surrounds with single
quotes
Array :: only accepts 2 element arrays, joins elements with a dash
and surrounds them with single quotes
Example:
content_security_policy.script_src :self, :unsafe_eval,
'example.com', [:nonce, 'foobarbaz']
# script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
When calling a method with no arguments, the setting is removed from
the policy instead of being left empty, since all of these setting
require at least one value. Likewise, if the policy does not have
any settings, the header will not be added.
Calling the method overrides any previous setting. Each of the
methods has a add_* method (e.g. add_script_src) for appending to the
current setting, and a get_* method (e.g. get_script_src) for
retrieving the current value of the setting, or nil if it is not
defined.
content_security_policy.script_src :self, :unsafe_eval
# script-src 'self' 'unsafe-eval';
content_security_policy.add_script_src 'example.com', [:nonce, 'foobarbaz']
# script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
content_security_policy.get_script_src 'example.com', [:nonce, 'foobarbaz']
# => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
The clear method can be used to remove all settings from the policy.
The following methods to set boolean directives are also defined:
* block_all_mixed_content
* upgrade_insecure_requests
Calling these methods will turn on the related setting. To turn the
setting off again, you can call them with a false argument (e.g.
block_all_mixed_content(false)). Each method also an *? method
(e.g. block_all_mixed_content?) for returning whether the setting is
currently enabled.
Likewise there is also a report_only method for turning on report
only mode (the default is enforcement mode), or turning off report
only mode if a false argument is given. Also, there is a
report_only? method for returning whether report only mode is
enabled. In report only mode, the Content-Security-Policy-Report-Only
header is used.
= Other Improvements
* The response_request plugin now integrates with the error_handler and
class_level_routing plugins. Those plugins now reinitialize the
current response object instead of creating a new response object.
jeremyevans-roda-4f30bb3/doc/release_notes/3.70.0.txt 0000664 0000000 0000000 00000001567 15167207754 0022304 0 ustar 00root root 0000000 0000000 = New Features
* A plain_hash_response_headers plugin has been added. On Rack 3,
this changes Roda to use a plain hash for response headers (as it
does on Rack 2), instead of using Rack::Headers (the default on
Rack 3). For a minimal app, using this plugin can almost double
the performance on Rack 3. Before using this plugin, you should
make sure that all response headers set explictly in your
application are already lower-case.
= Improvements
* Roda now natively uses lower-case for all response headers set
implicitly when using Rack 3. Previously, Roda used mixed-case
response headers and had Rack::Headers handle the conversion to
lower-case (Rack 3 requires lower-case response headers). Note
that Rack::Headers is still used for response headers by default
on Rack 3, as applications may not have converted to using
lower-case response headers.
jeremyevans-roda-4f30bb3/doc/release_notes/3.71.0.txt 0000664 0000000 0000000 00000001752 15167207754 0022301 0 ustar 00root root 0000000 0000000 = New Feature
* A match_hook_args plugin has been added. This is similar to the
existing match_hook plugin, but passes through the matchers and
block arguments (values yielded to the match block). Example:
plugin :match_hook_args
add_match_hook do |matchers, block_args|
logger.debug("matchers: #{matchers.inspect}. #{block_args.inspect} yielded.")
end
# Term is an implicit matcher used for terminating matches, and
# will be included in the array of matchers yielded to the match hook
# if a terminating match is used.
term = self.class::RodaRequest::TERM
route do |r|
r.root do
# for a request for /
# matchers: nil, block_args: nil
end
r.on 'a', ['b', 'c'], Integer do |segment, id|
# for a request for /a/b/1
# matchers: ["a", ["b", "c"], Integer], block_args: ["b", 1]
end
r.get 'd' do
# for a request for /d
# matchers: ["d", term], block_args: []
end
end
jeremyevans-roda-4f30bb3/doc/release_notes/3.72.0.txt 0000664 0000000 0000000 00000003644 15167207754 0022304 0 ustar 00root root 0000000 0000000 = New Features
* An invalid_request_body plugin has been added for allowing custom
handling of invalid request bodies. Roda uses Rack's request body
parsing, and by default invalid request bodies can result in
different exceptions based on how the body is invalid and which
version of Rack is in use.
If you want to treat an invalid request body as the submission of
no parameters, you can use the :empty_hash argument when loading
the plugin:
plugin :invalid_request_body, :empty_hash
If you want to return a empty 400 (Bad Request) response if an
invalid request body is submitted, you can use the :empty_400
argument when loading the plugin:
plugin :invalid_request_body, :empty_400
If you want to raise a Roda::RodaPlugins::InvalidRequestBody::Error
exception if an invalid request body is submitted (which makes it
easier to handle these exceptions when using the error_handler
plugin), you can use the :raise argument when loading the plugin:
plugin :invalid_request_body, :raise
For custom behavior, you can pass a block when loading the plugin
The block is called with the exception Rack raised when parsing the
body. The block will be used to define a method in the application's
RodaRequest class. It can either return a hash of parameters, or
you can raise a different exception, or you can halt processing and
return a response:
plugin :invalid_request_body do |exception|
# To treat the exception raised as a submitted parameter
{body_error: exception}
end
= Other Improvements
* When using the check_arity: :warn Roda option, Roda now correctly
warns when defining a method that expects a single argument when
the provided block requires multiple arguments.
* The match_hooks plugin is now implemented using the match_hook_args
plugin, simplifying the implementation. This change should be
transparent unless you were reaching into the internals.
jeremyevans-roda-4f30bb3/doc/release_notes/3.73.0.txt 0000664 0000000 0000000 00000002040 15167207754 0022272 0 ustar 00root root 0000000 0000000 = New Features
* The middleware plugin now accepts a :next_if_not_found option.
This allows the middleware plugin to pass the request to the next
application if the current application handles the request but
ends up calling the not_found handler. With the following
middleware:
class Mid < Roda
plugin :middleware
route do |r|
r.on "foo" do
r.get "bar" do
'bar'
end
end
end
end
Requests for /x would be forwarded to the next application, since
the application doesn't handle the request, but requests for /foo/x
would not be, because the middleware is partially handling the
request in the r.on "foo" block. With the :next_if_not_found
option, only requests for /foo/bar would be handled by the
middleware, and all other requests would be forwarded to the next
application.
= Other Improvements
* The sessions and route_csrf plugins no longer depend on the base64
library. base64 will be removed from Ruby's standard library
starting in Ruby 3.4.
jeremyevans-roda-4f30bb3/doc/release_notes/3.74.0.txt 0000664 0000000 0000000 00000001760 15167207754 0022303 0 ustar 00root root 0000000 0000000 = New Features
* A redirect_http_to_https plugin has been added, redirecting HTTP
requests to the same path on an HTTPS site. Using the routing tree,
you can control where to do the redirection, which allows you to
easily have part of your site accessible via HTTP, with sensitive
sections requiring HTTPS:
plugin :redirect_http_to_https
route do |r|
# routes available via both HTTP and HTTPS
r.redirect_http_to_https
# routes available only via HTTPS
end
If you want to redirect to HTTPS for all routes in the routing tree, you
can have r.redirect_http_to_https as the very first method call in the
routing tree. Note that in Roda it is possible to handle routing before
the normal routing tree using before hooks. The static_routing and
heartbeat plugins use this feature. If you would like to handle routes
before the normal routing tree, you can setup a before hook:
plugin :hooks
before do
request.redirect_http_to_https
end
jeremyevans-roda-4f30bb3/doc/release_notes/3.75.0.txt 0000664 0000000 0000000 00000001762 15167207754 0022306 0 ustar 00root root 0000000 0000000 = New Features
* A cookie_flags plugin has been added, for overriding, warning, or
raising for incorrect cookie flags. The plugin by default checks
whether the secure, httponly, and samesite=strict flags are set.
The default behavior is to add the appropriate flags if they are
not set, and change the samesite flag to strict if it is set to
something else. You can configure the flag checking behavior
via the :httponly, :same_site, and :secure options.
You can configure the action the plugin takes via the :action option.
The default action is to modify the flags, but the :action option can
be set to :raise, :warn, or :warn_and_modify to override the behavior.
The recommended way to use the plugin is to use it during testing,
and specify action: :raise, so you can catch places where cookies
are set with the wrong flags. Then you can fix those places to
use the correct flags, which is better than relying on the plugin
at runtime in production to fix incorrect flags.
jeremyevans-roda-4f30bb3/doc/release_notes/3.76.0.txt 0000664 0000000 0000000 00000001446 15167207754 0022306 0 ustar 00root root 0000000 0000000 = New Features
* A break plugin has been added, allowing you to use break from
inside a routing block and continue routing after the block. This
offers the same feature as the pass plugin, but using the standard
break keyword instead of the r.pass method.
* The error_mail and error_email features now both accept a :filter
plugin option. The value should respond to call with two arguments.
The first arguments is the key, and the second is the value, and
should return a truthy value if the value should be filtered. This
will be used for filtering parameter values, ENV values, and session
values in the generated emails.
= Other Improvements
* On Ruby 3.3+, the middleware plugin sets a temporary class name for
the created middleware, based on the class name of the Roda app.
jeremyevans-roda-4f30bb3/doc/release_notes/3.77.0.txt 0000664 0000000 0000000 00000000641 15167207754 0022303 0 ustar 00root root 0000000 0000000 = New Features
* The route_csrf plugin now supports formaction/formmethod attributes
in forms. A csrf_formaction_tag method has been added for creating
a hidden input for a particular path and method. When a form is
submitted, the check_csrf! method will fix check for a path-specific
csrf token (set by the hidden tag added by the csrf_formaction_tag
method), before checking for the default csrf token.
jeremyevans-roda-4f30bb3/doc/release_notes/3.78.0.txt 0000664 0000000 0000000 00000006364 15167207754 0022314 0 ustar 00root root 0000000 0000000 = New Features
* A permissions_policy plugin has been added that allows you to easily set a
Permissions-Policy header for the application, which browsers can use to
determine whether to allow specific functionality on the returned page
(mainly related to which JavaScript APIs the page is allowed to use).
You would generally call the plugin with a block to set the default policy:
plugin :permissions_policy do |pp|
pp.camera :none
pp.fullscreen :self
pp.clipboard_read :self, 'https://example.com'
end
Then, anywhere in the routing tree, you can customize the policy for just that
branch or action using the same block syntax:
r.get 'foo' do
permissions_policy do |pp|
pp.camera :self
end
# ...
end
In addition to using a block, you can also call methods on the object returned
by the method:
r.get 'foo' do
permissions_policy.camera :self
# ...
end
You can use the :default plugin option to set the default for all settings.
For example, to disallow all access for each setting by default:
plugin :permissions_policy, default: :none
The following methods are available for configuring the permissions policy,
which specify the setting (substituting _ with -):
* accelerometer
* ambient_light_sensor
* autoplay
* bluetooth
* camera
* clipboard_read
* clipboard_write
* display_capture
* encrypted_media
* fullscreen
* geolocation
* gyroscope
* hid
* idle_detection
* keyboard_map
* magnetometer
* microphone
* midi
* payment
* picture_in_picture
* publickey_credentials_get
* screen_wake_lock
* serial
* sync_xhr
* usb
* web_share
* window_management
All of these methods support any number of arguments, and each argument should
be one of the following values:
:all :: Grants permission to all domains (must be only argument)
:none :: Does not allow permission at all (must be only argument)
:self :: Allows feature in current document and any nested browsing contexts
that use the same domain as the current document.
:src :: Allows feature in current document and any nested browsing contexts
that use the same domain as the src of the iframe.
String :: Specifies origin domain where access is allowed
When calling a method with no arguments, the setting is removed from the policy instead
of being left empty, since all of these setting require at least one value. Likewise,
if the policy does not have any settings, the header will not be added.
Calling the method overrides any previous setting. Each of the methods has +add_*+ and
+get_*+ methods defined. The +add_*+ method appends to any existing setting, and the +get_*+ method
returns the current value for the setting (this will be +:all+ if all domains are allowed, or
any array of strings/:self/:src).
permissions_policy.fullscreen :self, 'https://example.com'
# fullscreen (self "https://example.com")
permissions_policy.add_fullscreen 'https://*.example.com'
# fullscreen (self "https://example.com" "https://*.example.com")
permissions_policy.get_fullscreen
# => [:self, "https://example.com", "https://*.example.com"]
The clear method can be used to remove all settings from the policy.
jeremyevans-roda-4f30bb3/doc/release_notes/3.79.0.txt 0000664 0000000 0000000 00000015002 15167207754 0022302 0 ustar 00root root 0000000 0000000 = New Features
* The hmac_paths plugin allows protection of paths using an HMAC. This can be used
to prevent users enumerating paths, since only paths with valid HMACs will be
respected.
To use the plugin, you must provide a :secret option. This sets the secret for
the HMACs. Make sure to keep this value secret, as this plugin does not provide
protection against users who know the secret value. The secret must be at least
32 bytes.
plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes'
To generate a valid HMAC path, you call the hmac_path method:
hmac_path('/widget/1')
# => "/0c2feaefdfc80cc73da19b060c713d4193c57022815238c6657ce2d99b5925eb/0/widget/1"
The first segment in the returned path is the HMAC. The second segment is flags for
the type of paths (see below), and the rest of the path is as given.
To protect a path or any subsection in the routing tree, you wrap the related code
in an +r.hmac_path+ block.
route do |r|
r.hmac_path do
r.get 'widget', Integer do |widget_id|
# ...
end
end
end
If first segment of the remaining path contains a valid HMAC for the rest of the path (considering
the flags), then r.hmac_path will match and yield to the block, and routing continues inside
the block with the HMAC and flags segments removed.
In the above example, if you provide a user a link for widget with ID 1, there is no way
for them to guess the valid path for the widget with ID 2, preventing a user from
enumerating widgets, without relying on custom access control. Users can only access
paths that have been generated by the application and provided to them, either directly
or indirectly.
In the above example, r.hmac_path is used at the root of the routing tree. If you
would like to call it below the root of the routing tree, it works correctly, but you
must pass hmac_path the :root option specifying where r.hmac_paths will be called from.
Consider this example:
route do |r|
r.on 'widget' do
r.hmac_path do
r.get Integer do |widget_id|
# ...
end
end
end
r.on 'foobar' do
r.hmac_path do
r.get Integer do |foobar_id|
# ...
end
end
end
end
For security reasons, the hmac_path plugin does not allow an HMAC path designed for
widgets to be a valid match in the r.hmac_path call inside the "r.on 'foobar'"
block, preventing users who have a valid HMAC for a widget from looking at the page for
a foobar with the same ID. When generating HMAC paths where the matching r.hmac_path
call is not at the root of the routing tree, you must pass the :root option:
hmac_path('/1', root: '/widget')
# => "/widget/daccafce3ce0df52e5ce774626779eaa7286085fcbde1e4681c74175ff0bbacd/0/1"
hmac_path('/1', root: '/foobar')
# => "/foobar/c5fdaf482771d4f9f38cc13a1b2832929026a4ceb05e98ed6a0cd5a00bf180b7/0/1"
Note how the HMAC changes even though the path is the same.
In addition to the +:root+ option, there are additional options that further constrain
use of the generated paths.
The :method option creates a path that can only be called with a certain request
method:
hmac_path('/widget/1', method: :get)
# => "/d38c1e634ecf9a3c0ab9d0832555b035d91b35069efcbf2670b0dfefd4b62fdd/m/widget/1"
Note how this results in a different HMAC than the original hmac_path('/widget/1')
call. This sets the flags segment to "m", which means r.hmac_path will consider the
request mehod when checking the HMAC, and will only match if the provided request method
is GET. This allows you to provide a user the ability to submit a GET request for the
underlying path, without providing them the ability to submit a POST request for the
underlying path, with no other access control.
The :params option accepts a hash of params, converts it into a query string, and
includes the query string in the returned path. It sets the flags segment to +p+, which
means r.hmac_path will check for that exact query string. Requests with an empty query
string or a different string will not match.
hmac_path('/widget/1', params: {foo: 'bar'})
# => "/fe8d03f9572d5af6c2866295bd3c12c2ea11d290b1cbd016c3b68ee36a678139/p/widget/1?foo=bar"
For GET requests, which cannot have request bodies, that is sufficient to ensure that the
submitted params are exactly as specified. However, POST requests can have request bodies,
and request body params override query string params in r.params. So if you are using
this for POST requests (or other HTTP verbs that can have request bodies), use r.GET
instead of r.params to specifically check query string parameters.
You can use +:root+, +:method+, and +:params+ at the same time:
hmac_path('/1', root: '/widget', method: :get, params: {foo: 'bar'})
# => "/widget/9169af1b8f40c62a1c2bb15b1b377c65bda681b8efded0e613a4176387468c15/mp/1?foo=bar"
This gives you a path only valid for a GET request with a root of "/widget" and
a query string of "foo=bar".
To handle secret rotation, you can provide an :old_secret option when loading the
plugin.
plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes',
old_secret: 'previous-secret-value-with-at-least-32-bytes'
This will use :secret for constructing new paths, but will respect paths generated by
:old_secret.
= Other Improvements
* When not using cached templates in the render plugin, the render plugin
now has better handling when a template is modified and results in an
error. Previously, the error would be raised on the first request after
the template modification, but subsequent requests would use the
previous template value. The render plugin will no longer update the
last modified time in this case, so if a template is modified and
introduces an error (e.g. SyntaxError in an erb template), all future
requests that use the template will result in the error being raised,
until the template is fixed.
= Backwards Compatibility
* The internal TemplateMtimeWrapper API has been modified. As documented,
this is an internal class and the API can change in any Roda version.
However, if any code was relying on the previous implementation of
TemplateMtimeWrapper#modified?, it will need to be modified, as that
method has been replaced with TemplateMtimeWrapper#if_modified.
Additionally, the TemplateMtimeWrapper#compiled_method_lambda API has
also changed.
jeremyevans-roda-4f30bb3/doc/release_notes/3.8.0.txt 0000664 0000000 0000000 00000001767 15167207754 0022227 0 ustar 00root root 0000000 0000000 = New Features
* The convert_each! method in the typecast_params plugin now
accepts a Proc or Method value for the :keys option. The proc
or method is called with the current array or hash that
typecast params is operating on, and should return an
array of keys to use for the conversion.
* The convert_each! method in the typecast_params plugin will
now automatically handle hashes with keys from '0'..'N',
without a :keys option being provided.
This makes it possible to handle parameter names such as
foo[0][bar], foo[0][baz], foo[1][bar], and foo[1][baz], if you
want to avoid the issues related to rack's issues when parsing
array parameters.
= Other Improvements
* The Roda::RodaVersionNumber constant has been added for easier
version comparisons. It is 30080 for version 3.8.0.
= Backwards Compatibility
* When an unsupported type is given as value of the :keys option
to the convert_each! method in the typecast_params plugin, a
ProgrammerError exception is now raised.
jeremyevans-roda-4f30bb3/doc/release_notes/3.80.0.txt 0000664 0000000 0000000 00000003037 15167207754 0022277 0 ustar 00root root 0000000 0000000 = New Features
* The hmac_paths plugin now supports a :namespace option for both hmac_path and
r.hmac_path. The :namespace option makes the generated HMAC values unique
per namespace, allowing easy use of per user/group HMAC paths. This can
be useful if the same path will show different information to different
users/groups, and you want to prevent path enumeration for each user/group
(not allow paths enumerated by one user/group to be valid for a different
user/group). Example:
hmac_path('/widget/1', namespace: '1')
# => "/3793ac2a72ea399c40cbd63f154d19f0fe34cdf8d347772134c506a0b756d590/n/widget/1"
hmac_path('/widget/1', namespace: '2')
# => "/0e1e748860d4fd17fe9b7c8259b1e26996502c38e465f802c2c9a0a13000087c/n/widget/1"
The HMAC path created with namespace: '1' will only be valid when calling
r.hmac_path with namespace: '1' (similar for namespace: '2').
It is expected that the most common use of the :namespace option is to
reference session values, so the value of each path depends on the logged in
user. You can use the :namespace_session_key plugin option to set the
default namespace for both hmac_path and r.hmac_path:
plugin :hmac_paths, secret: 'some-secret-value-with-at-least-32-bytes',
namespace_session_key: 'account_id'
This will use session['account_id'] (converted to a string) as the namespace
for both hmac_path and r.hmac_path, unless a specific :namespace option is
given, making it simple to implement per user/group HMAC paths across an
application.
jeremyevans-roda-4f30bb3/doc/release_notes/3.81.0.txt 0000664 0000000 0000000 00000001640 15167207754 0022276 0 ustar 00root root 0000000 0000000 = New Features
* The hmac_paths plugin now supports :until and :seconds options for
hmac_path, to create a path that is only valid for a specific amount of
time. :until sets a specific time that the path will be valid until,
and :seconds makes the path only valid for the given number of seconds.
hmac_path('/widget/1', until: Time.utc(2100))
# => "/dc8b6e56e4cbe7815df7880d42f0e02956b2e4c49881b6060ceb0e49745a540d/t/4102444800/widget/1"
Requests for the path after the given time will not be matched by
r.hmac_path.
= Other Improvements
* The early_hints plugin now correctly follows the Rack 3 SPEC when
using Rack 3. This was not caught previously because Rack only
added official support for early_hints in the last month.
* Ruby 3.4 backtraces are now parsed correctly in the exception_page
plugin.
* Some plugins that accept a block no longer issue an unused block
warning on Ruby 3.4.
jeremyevans-roda-4f30bb3/doc/release_notes/3.82.0.txt 0000664 0000000 0000000 00000003415 15167207754 0022301 0 ustar 00root root 0000000 0000000 = New Features
* A :zstd option has been added to the public and multi_public
plugins to support serving zstd-compressed files with a .zst
extension. This option is similar to the existing :gzip and
:brotli plugin options. Chrome started supporting zstd encoding
in March.
* An :encodings option has been added to the public and multi_public
plugins, for more control over how encodings are handled. This
allows for changing the order in which encodings are attempted, the
use of custom encodings, and the use of different file extensions
for encodings. Example:
plugin :public, encodings: {'zstd'=>'.zst', 'deflate'=>'.deflate'}
If the :encodings option is not provided, the :zstd, :brotli, and
:gzip options are used to build an equivalent :encodings option.
= Other Improvements
* The capture_erb plugin now integrates better when using
erubi/capture_block for <%= method do %> support in ERB templates,
using the native capture method provided by the buffer object.
* Encoding handling has been more optimized in the public plugin.
Regexps for the encodings are precomputed, avoiding a regexp
allocation per request per encoding attempted. On Ruby 2.4+
Regexp#match? is used for better performance. If the
Accept-Encoding header is not present, no encoding matching
is attemped.
= Backwards Compatibility
* The private public_serve_compressed request method in the public
plugin now assumes it is called after the encoding is already
valid. If you are calling this method in your own code, you now
need to perform checks to make sure the client can accept the
encoding before calling this method.
* The :public_gzip and :public_brotli application options are no
longer set by the public plugin. The :public_encodings option
is now set.
jeremyevans-roda-4f30bb3/doc/release_notes/3.83.0.txt 0000664 0000000 0000000 00000000422 15167207754 0022275 0 ustar 00root root 0000000 0000000 = New Features
* An assume_ssl plugin has been added. This plugin is designed for
cases where the application is being fronted by an SSL-terminating
reverse proxy that does not set the X-Forwarded-Proto or similar
header to indicate it is forwarding an SSL request.
jeremyevans-roda-4f30bb3/doc/release_notes/3.84.0.txt 0000664 0000000 0000000 00000000616 15167207754 0022303 0 ustar 00root root 0000000 0000000 = New Features
* An hsts plugin has been added to easily add an appropriate
Strict-Transport-Security header:
plugin :hsts
# Strict-Transport-Security: max-age=63072000; includeSubDomains
plugin :hsts, preload: true
# Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
= Other Improvements
* The gem size has been reduced 25% by removing documentation.
jeremyevans-roda-4f30bb3/doc/release_notes/3.85.0.txt 0000664 0000000 0000000 00000006716 15167207754 0022313 0 ustar 00root root 0000000 0000000 = New Features
* The class_matchers and symbol_matchers plugins now allow building on
top of existing class and symbol matchers. This allows you to
simplify code such as:
r.on "employee", Integer do |emp_id|
next unless employee = Employee[emp_id]
# ...
end
by defining an appropriate class matcher:
class_matcher Employee, Integer do |emp_id|
Employee[emp_id]
end
and then changing the matcher in the route code:
r.on "employee", Employee do |employee|
# ...
end
This avoids the need to check for a valid employee in each route, by
having the check in the class_matcher block. If a request comes in
with a valid integer segment, but there is no employee assigned with
that integer, then the Employee matcher will not match.
Symbol matchers can build upon class matchers (and vice-versa):
symbol_matcher :ActiveEmployee, Employee do |employee|
employee if employee.active?
end
With the above :ActiveEmployee matcher, segments will only
match if they are an integer that is related to an employee,
and that employee is active.
= Other Improvements
* As shown in the above examples, class_matcher and symbol_matcher
blocks can now return non-arrays. This can reduce the number
of unnecessary allocations, and result in simpler code.
* The blocks passed to class_matcher and symbol_matcher are now
evaluated in route block context. That allows you to have
the matchers depend on request or session specific state. For
example, a Post class matcher such as:
class_matcher Post, Integer do |id|
Post.where(user_id: session['user_id']).with_pk(id)
end
will only match if the user for the related Post matches the
logged in user.
* Symbol matchers based on regexps are now faster by caching the
regexp at a higher level, avoiding the need to look up the
cached regexp for every request.
* The public plugin now avoids a deprecation warning when using
Ruby 3.4.0-preview2.
* The capture_erb plugin no longer breaks if ActiveSupport 4 is
loaded. ActiveSupport 4 defines Kernel#capture, which broke
the capture_erb plugin's assumption that calling capture was
safe if the method was defined. capture_erb does not call
capture on the buffer object if the buffer object is a String
instance. The use of capture is designed for usage with
erubi/capture_block, which does not use a String instance as
a buffer object.
= Backwards Compatibility
* Changing the class_matcher and symbol_matcher blocks to be
evaluated in route block context can break code that assumes
they were evaluated in the context in which they were called.
Generally, that context is application class context. For example,
the following type of code would break:
class App < Roda
plugin :class_matchers
def self.get_class(klass)
const_get(klass)
end
class_matcher Employee, Integer do |emp_id|
get_class(:Employee)[emp_id]
end
end
This worked previously, because get_class was defined as a
class method, and the block was evaluated in class context,
as that is the context in which it was defined. You would
have to define a get_class instance method to allow the
example to continue to work.
* The internals of the Integer_matcher_max plugin have been
updated, to integrate with the class_matchers and symbol_matchers
changes. The _match_class_convert_Integer and
_match_class_max_Integer private request methods have been removed.
jeremyevans-roda-4f30bb3/doc/release_notes/3.86.0.txt 0000664 0000000 0000000 00000002474 15167207754 0022311 0 ustar 00root root 0000000 0000000 = New Features
* A conditional_sessions plugin has been added. This allows you to
only support sessions for a subset of the application's requests.
You pass a block when loading the plugin, and sessions are only
supported if the block returns truthy. The block is evaluated
in request scope.
As an example, if you do not want to support sessions for request
paths starting with /static, you could use:
plugin :conditional_sessions, secret: ENV["SECRET"] do
!path_info.start_with?('/static')
end
With this example, if the request path starts with /static:
* The request methods +session+, +session_created_at+, and
+session_updated_at+ all raise an exception.
* The request +persist_session+ and route scope +clear_session+
methods do nothing and return nil.
Options passed when loading the plugin are passed to the sessions
plugin.
* In the content_security_policy plugin, you can now call
response.skip_content_security_policy! to skip the setting of the
response header.
* In the permissions_policy plugin, you can now call
response.skip_permissions_policy! to skip the setting of the
response header.
= Other Improvements
* When using the autoload_hash_branches and/or autoload_named_routes
plugins, Roda.freeze now works correctly if the Roda class is
already frozen.
jeremyevans-roda-4f30bb3/doc/release_notes/3.87.0.txt 0000664 0000000 0000000 00000002755 15167207754 0022314 0 ustar 00root root 0000000 0000000 = New Features
* A host_routing plugin has been added, for easier routing based on
the request host. Example:
plugin :host_routing do |hosts|
hosts.to :api, "api.example.com", "api2.example.com"
hosts.default :www
end
route do |r|
r.api do
# requests to api.example.com or api2.example.com
end
r.www do
# requests to other domains
end
end
The plugin also adds request predicate methods:
route do |r|
r.api? # true if the request is to api.example.com or api2.example.com
r.www? # true for request for other domains
end
If the :scope_predicates plugin option is given, these predicate
methods are also supported directly in block scope (no "r.").
For more advanced cases, such as prefix matches on the host, the
hosts.default method accepts a block. In this case, you should
also call hosts.register to notify the plugin about what hosts
the block could return:
plugin :host_routing do |hosts|
hosts.register :api
hosts.default :www do |host|
:api if host.end_with?(".api.example.com")
end
end
= Other Improvements
* In the custom_block_results plugin, if the block passed to
handle_block_result returns an object that is not a String,
nil, or false, Roda no longer attempts to write it to the response
body. Doing so is undesirable and would be a violation of the rack
spec.
* Minor performance improvements have been made to the header_matchers
plugin.
jeremyevans-roda-4f30bb3/doc/release_notes/3.88.0.txt 0000664 0000000 0000000 00000004651 15167207754 0022312 0 ustar 00root root 0000000 0000000 = New Features
* Fixed locals are now supported in templates when using Tilt 2.6+.
Without fixed locals, templates that support local variables can
be called with any locals, and a separate template method is
compiled for each combination of local variable names. This
causes multiple issues:
* It is inefficient, especially for large templates that are called with
many combinations of locals.
* It hides issues if unused local variable names are passed to the template
* It does not support default values for local variables
* It does not support required local variables
* It does not support cases where you want to pass values via a keyword splat
* It does not support named blocks
Fixed locals solve these problems by having the compiled methods
use keyword arguments instead of a single positional hash
argument. This allows you to use required keyword arguments,
provide default values for optional keyword arguments, and use keyword
splats and named blocks. See https://github.com/jeremyevans/tilt#fixed-locals
for details.
You can enable embedded fixed locals in templates using the `:extract_fixed_locals`
template option. The recommended template options when creating new Roda
applications that use the render plugin are now:
plugin :render, template_opts: {
scope_class: self, # Always uses current class as scope class for compiled templates
freeze: true, # Freeze string literals in templates
extract_fixed_locals: true, # Support embedded fixed locals in templates
default_fixed_locals: '()', # Default to templates not supporting local variables
escape: true, # For Erubi templates, escapes <%= by default (use <%== for unescaped
chain_appends: true, # For Erubi templates, improves performance
skip_compiled_encoding_detection: true, # Unless you need encodings explicitly specified
}
= Other Improvements
* The json_parser plugin now handles the case where
Rack::Request#POST has already been called on the env hash,
when using Rack 3+.
* The default_headers plugin now handles a mixed/upper case
Content-Type header, when using Rack 3+ (which requires
lower case headers).
* The render_coverage plugin now handles the case where both
:scope_class template option and fixed locals are used.
* Roda now avoids warnings when the -W:strict_unused_block Ruby
option is used.
jeremyevans-roda-4f30bb3/doc/release_notes/3.89.0.txt 0000664 0000000 0000000 00000002237 15167207754 0022311 0 ustar 00root root 0000000 0000000 = New Features
* The render plugin now supports an :assume_fixed_locals option,
which allows for better caching when all templates use fixed
locals, by using a simplified cache key, and avoiding duplicate
cache entries for templates rendered both with and without locals.
Additionally, when this plugin option is set, calling template
methods is now faster if the following are true:
* The application is frozen
* Template caching is enabled
* Ruby version is 3+
* A part plugin has been added, which simplifies rendering a template
with locals:
# render plugin
render(:template, locals: {foo: 'bar'})
# part plugin
part(:template, foo: 'bar')
In addition to offering a nicer API if you only need to provide
locals, the part method can also be faster if all of the
following are true:
* The application is frozen
* The :assume_fixed_locals render plugin option is set
* Template caching is enabled
* Ruby version is 3+ (even faster on Ruby 3.4+)
= Other Improvements
* The mailer plugin's mail and sendmail class methods now support
keyword arguments and pass them as keywords to the r.mail blocks
in the routing tree.
jeremyevans-roda-4f30bb3/doc/release_notes/3.9.0.txt 0000664 0000000 0000000 00000005667 15167207754 0022233 0 ustar 00root root 0000000 0000000 = New Features
* A route_csrf plugin has been added. This plugin allows for more
control over CSRF protection, since the user can choose where in
the routing tree to enforce the protection. Additionally, the
route_csrf plugin offers better security than the CSRF protection
used by the csrf plugin (which uses the rack_csrf library).
The route_csrf plugin defaults to allowing only CSRF tokens
specific to a given request method and request path, and not
allowing generic CSRF tokens (though it does offer optional support
for such tokens). Both request-specific and generic CSRF tokens
are designed to never leak the CSRF secret key, making it more
difficult to forge valid CSRF tokens. Additionally, the plugin
offers optional support for accepting rack_csrf tokens, which
should only be enabled during a short transition period.
Some differences between the route_csrf plugin and the older
csrf plugin:
* route_csrf supports and by default only allows CSRF tokens
specific to request method and request path, as mentioned
above. You can use the require_request_specific_tokens: false
option to allow generic CSRF tokens.
* route_csrf does not check the HTTP header by default, it
only checks the header if the :check_header option is set.
The :check_header option can be set to true to check both
the parameter and the header, or set to :only to only check
the header.
* route_csrf raises by default for invalid CSRF tokens. rack_csrf
returns an empty 403 response in that case. You can use the
error_handler plugin to handle the
Roda::RodaPlugins::RouteCsrf::InvalidToken exceptions, or you
can use the csrf_failure: :empty_403 option if you would like
the csrf plugin default behavior. The plugin also accepts a
block for configurable failure behavior.
* route_csrf does not use a middleware, as it is designed to give
more control. In order to enforce the CSRF protection, you need
to call check_csrf! in your routing tree at the appropriate
place. If you are not sure where to add it, add it to the top
of the routing tree, after the public or assets routes if you
are using those plugins:
route do
r.public
r.assets
check_csrf!
# ...
end
The check_csrf! method accepts an options hash, which can be used
to override the plugin options on a per-call basis.
* The csrf_token/csrf_tag methods take an optional path and method
arguments. If a path is given, the method defaults to POST, and
the resulting CSRF token can only be used to submit forms for the
path and method. If a path is not given, the resulting CSRF token
will be generic, but it will only work if the plugin has been
configured to allow generic CSRF tokens.
* A csrf_path method is available for easily taking a form action
string and returning an appropriate path to pass to the csrf_token
or csrf_tag methods.
jeremyevans-roda-4f30bb3/doc/release_notes/3.90.0.txt 0000664 0000000 0000000 00000000602 15167207754 0022273 0 ustar 00root root 0000000 0000000 = Improvements
* The send_file method in the sinatra_helpers plugin now returns
a response body that implements to_path.
* Roda now sets a temporary name for the remaining anonymous
modules and classes on Ruby 3.3+.
* The common_logger plugin now escapes embedded newlines. These
should only be present if the server is broken and including
newlines in things it shouldn't.
jeremyevans-roda-4f30bb3/doc/release_notes/3.91.0.txt 0000664 0000000 0000000 00000002210 15167207754 0022271 0 ustar 00root root 0000000 0000000 = New Features
* The render_each method in the render_each plugin now accepts a
block. If passed a block, instead of returning a concatenation
of the rendered template output, it yields each rendered template
output, and returns nil. This allows for use in the case where you
want to wrap the template output:
<% render_each([1,2,3], :foo) do |text| %>
<%= text %>
<% end %>
If can also be used to reduce memory usage even in the case where
you are not wrapping template output. Instead of:
<%= render_each([1,2,3], :foo) %>
You can do:
<% render_each([1,2,3], :foo) %><%= body %><% end %>
This will avoid building a potentially large unnecessary intermediate
string.
* The capture_erb plugin now supports a returns: :buffer method and
plugin option. When this option is provided, the capture_erb method
returns the buffer instead of the return value of the block passed
to it. This better handles cases where the template ends in a
conditional:
<% value = capture_erb do %>
Some content here.
<% if something %>
Some more content here.
<% end %>
<% end %>
jeremyevans-roda-4f30bb3/doc/release_notes/3.92.0.txt 0000664 0000000 0000000 00000001160 15167207754 0022275 0 ustar 00root root 0000000 0000000 = New Features
* An each_part plugin has been added, offering a simpler method for
using render_each with locals:
# With render_each:
render_each(array_of_foos, :foo, locals: {bar: 1})
# With each_part:
each_part(array_of_foos, :foo, bar: 1)
The each_part provides similar benefits to the render_each plugin
that the part plugin provides to the render plugin. The each_part
method has been optimized to work with the render plugin's
:assume_fixed_locals option.
= Other Improvements
* The render_each plugin has been optimized to work with the render
plugin's :assume_fixed_locals option.
jeremyevans-roda-4f30bb3/doc/release_notes/3.93.0.txt 0000664 0000000 0000000 00000003426 15167207754 0022305 0 ustar 00root root 0000000 0000000 = New Features
* The typecast_params plugin handle_type method now supports an
:invalid_value_message option, for a custom error message for the
type, explaining why the input is invalid. This error message is
used when there is a parameter given, but it cannot be converted to
the desired type:
plugin :typecast_params do
handle_type(:single_char, invalid_value_message: \
"value not a single character") do |v|
v if v.is_a?(String) && v.length == 1
end
end
Previously, the error message in this case was the same as when no
parameter was provided, which was misleading.
The types natively supported by the typecast_params and
typecast_params_sized_integers plugins now use
:invalid_value_message for better error reporting. You can override
the invalid value messages for these types using the
invalid_value_message method:
plugin :typecast_params do
invalid_value_message(:pos_int,
"value must be greater than 0 for parameter")
end
= Other Improvements
* Many minor performance improvements, mostly from rubocop-performance:
* flat_map instead of map.flatten(1)
* tr/delete instead of gsub
* symbol instead of string argument to method_defined?
* hoist literal arrays outside blocks
* end_with?/include? instead of =~
= Backwards Compatibility
* The fourth parameter in the process and process_arg private methods
in the typecast_params plugin has changed from being the max input
bytesize of the type, to the type symbol, and is now a required
parameter. External callers of these private methods will need to be
updated.
* Code rescuing Roda::RodaPlugins::TypecastParams::Error and handling
the reason may need to adjust to handling :invalid_value in cases
where it was handling :missing.
jeremyevans-roda-4f30bb3/doc/release_notes/3.94.0.txt 0000664 0000000 0000000 00000001412 15167207754 0022277 0 ustar 00root root 0000000 0000000 = New Features
* A view_subdir_leading_slash plugin has been added, for using the
current view subdirectory for all templates that do not start with
a slash. The default behavior when using view subdirectories
remains to use the current view subdirectory unless the template
name includes a slash. This makes it easier to use nested view
subdirectories, at the expense of making it slightly more difficult
to use templates outside the current view subdirectory.
= Other Improvements
* The render_each and each_part plugin template selection code is
now more optimized when the application is frozen and using the
:assume_fixed_locals render plugin option.
* The render_each and each_part plugin default local generation
is now more optimized on Ruby 3+.
jeremyevans-roda-4f30bb3/doc/release_notes/3.95.0.txt 0000664 0000000 0000000 00000003345 15167207754 0022307 0 ustar 00root root 0000000 0000000 = New Features
* A response_content_type plugin has been added for more easily
setting the content type of responses:
When setting the content-type, you can pass either a string, which
is used directly:
response.content_type = "text/html"
Or, if you have registered mime types when loading the plugin:
plugin :response_content_type, mime_types: {
plain: "text/plain",
html: "text/html",
pdf: "application/pdf"
}
You can use a symbol:
response.content_type = :html
If you would like to load all mime types supported by rack/mime,
you can use the mime_types: :from_rack_mime option:
plugin :response_content_type, mime_types: :from_rack_mime
Note that you are unlikely to be using all of these mime types,
so doing this will likely result in unnecessary memory usage. It
is recommended to use a hash with only the mime types your
application actually uses.
To prevent silent failures, if you attempt to set the response
type with a symbol, and the symbol is not recognized, a KeyError
is raised.
* The typecast_params plugin now includes typecast_query_params and
typecast_body_params methods in addition to typecast_params.
typecast_query_params deals with query string parameters (r.GET),
and typecast_body_params deals with request body parameters
(r.POST).
= Other Improvements
* The sessions plugin now raises
Roda::RodaPlugins::Sessions::CookieTooLarge if the total cookie
size is over 4K. Previously, it only raised the exception if the
cookie value was over 4K. Browsers enforce the limit on the total
cookie size, not just the value, so this change prevents Roda
from setting a cookie that a browser would ignore due to size
restrictions.
jeremyevans-roda-4f30bb3/doc/release_notes/3.96.0.txt 0000664 0000000 0000000 00000000727 15167207754 0022311 0 ustar 00root root 0000000 0000000 = New Features
* A redirect_path plugin has been added, which integrates the path
plugin with r.redirect:
Foo = Struct.new(:id)
foo = Foo.new(1)
plugin :redirect_path
path Foo do |foo|
"/foo/#{foo.id}"
end
route do |r|
r.get "example" do
# redirects to /foo/1
r.redirect(foo)
end
r.get "suffix-example" do
# redirects to /foo/1/status
r.redirect(foo, "/status")
end
end
jeremyevans-roda-4f30bb3/doc/release_notes/3.97.0.txt 0000664 0000000 0000000 00000002120 15167207754 0022277 0 ustar 00root root 0000000 0000000 = New Features
* A map_matcher plugin has been added, for matching the next segment
in the request path to a hash key, yielding the hash value. This
allows for a better approach for metaprogramming routes.
When dealing with many similar routes, it is common to use a hash
keyed by route segment, and then match against an array of the keys:
map = {
'album' => Album,
'artist' => Artist,
# ...
}.freeze
keys = map.keys.freeze
route do |r|
r.on "type", keys do |key|
value = map[key]
# ...
end
end
For large maps, this approach is suboptimal, since the array matcher
will iterate over each element in the array, checking whether it
matches.
The map_matcher plugin allows for:
plugin :map_matcher
route do |r|
r.on "type", map: map do |value|
# ...
end
end
This gets the next route segment and checks whether it is a key in
the map, instead of iterating over an array of hash keys, so it is
more efficient for large maps. It also results in simpler code.
jeremyevans-roda-4f30bb3/doc/release_notes/3.98.0.txt 0000664 0000000 0000000 00000001260 15167207754 0022304 0 ustar 00root root 0000000 0000000 = New Features
* The sessions plugin now supports an :env_key option, which allows
the use of a non-default env key. This allows you to maintain the
session for the Roda application separately from the default Rack
session. This can be useful in all of the following cases when you
want to have the Roda application use a separate session from other
applications or middleware:
* Using middleware in the Roda application.
* Using the Roda application as middleware in another Rack
application.
* Using r.run in the Roda application to call another Rack
application.
* Calling the Roda application from another Rack application using
the same env hash.
jeremyevans-roda-4f30bb3/doc/release_notes/3.99.0.txt 0000664 0000000 0000000 00000000641 15167207754 0022307 0 ustar 00root root 0000000 0000000 = New Features
* Set instances are now supported as matchers by default, as Set
will be a core class in Ruby 4.0. A Set matcher operates
similarly to an array matcher if all of the array elements are
strings, with the difference that the Set matcher will perform
better for large numbers of elements, since it will look for
a matching entry in the set, instead of iterating over the
elements of the set.
jeremyevans-roda-4f30bb3/lib/ 0000775 0000000 0000000 00000000000 15167207754 0016156 5 ustar 00root root 0000000 0000000 jeremyevans-roda-4f30bb3/lib/roda.rb 0000664 0000000 0000000 00000054712 15167207754 0017441 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require "thread"
require_relative "roda/request"
require_relative "roda/response"
require_relative "roda/plugins"
require_relative "roda/cache"
require_relative "roda/version"
# The main class for Roda. Roda is built completely out of plugins, with the
# default plugin being Roda::RodaPlugins::Base, so this class is mostly empty
# except for some constants.
class Roda
# Error class raised by Roda
class RodaError < StandardError; end
@app = nil
@inherit_middleware = true
@middleware = []
@opts = {}
@raw_route_block = nil
@route_block = nil
@rack_app_route_block = nil
module RodaPlugins
# The base plugin for Roda, implementing all default functionality.
# Methods are put into a plugin so future plugins can easily override
# them and call super to get the default behavior.
module Base
# Class methods for the Roda class.
module ClassMethods
# The rack application that this class uses.
def app
@app || build_rack_app
end
# Whether middleware from the current class should be inherited by subclasses.
# True by default, should be set to false when using a design where the parent
# class accepts requests and uses run to dispatch the request to a subclass.
attr_accessor :inherit_middleware
# The settings/options hash for the current class.
attr_reader :opts
# The route block that this class uses.
attr_reader :route_block
# Call the internal rack application with the given environment.
# This allows the class itself to be used as a rack application.
# However, for performance, it's better to use #app to get direct
# access to the underlying rack app.
def call(env)
app.call(env)
end
# Clear the middleware stack
def clear_middleware!
@middleware.clear
@app = nil
end
# Define an instance method using the block with the provided name and
# expected arity. If the name is given as a Symbol, it is used directly.
# If the name is given as a String, a unique name will be generated using
# that string. The expected arity should be either 0 (no arguments),
# 1 (single argument), or :any (any number of arguments).
#
# If the :check_arity app option is not set to false, Roda will check that
# the arity of the block matches the expected arity, and compensate for
# cases where it does not. If it is set to :warn, Roda will warn in the
# cases where the arity does not match what is expected.
#
# If the expected arity is :any, Roda must perform a dynamic arity check
# when the method is called, which can hurt performance even in the case
# where the arity matches. The :check_dynamic_arity app option can be
# set to false to turn off the dynamic arity checks. The
# :check_dynamic_arity app option can be to :warn to warn if Roda needs
# to adjust arity dynamically.
#
# Roda only checks arity for regular blocks, not lambda blocks, as the
# fixes Roda uses for regular blocks would not work for lambda blocks.
#
# Roda does not support blocks with required keyword arguments if the
# expected arity is 0 or 1.
def define_roda_method(meth, expected_arity, &block)
if meth.is_a?(String)
meth = roda_method_name(meth)
end
call_meth = meth
# RODA4: Switch to false # :warn in last Roda 3 version
if (check_arity = opts.fetch(:check_arity, true)) && !block.lambda?
required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(block)
if keyword == :required && (expected_arity == 0 || expected_arity == 1)
raise RodaError, "cannot use block with required keyword arguments when calling define_roda_method with expected arity #{expected_arity}"
end
case expected_arity
when 0
unless required_args == 0
if check_arity == :warn
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 0, but arguments required for #{block.inspect}"
end
b = block
block = lambda{instance_exec(&b)} # Fallback
end
when 1
if required_args == 0 && optional_args == 0 && !rest
if check_arity == :warn
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but no arguments accepted for #{block.inspect}"
end
temp_method = roda_method_name("temp")
class_eval("def #{temp_method}(_) #{meth =~ /\A\w+\z/ ? "#{meth}_arity" : "send(:\"#{meth}_arity\")"} end", __FILE__, __LINE__)
alias_method meth, temp_method
undef_method temp_method
private meth
alias_method meth, meth
meth = :"#{meth}_arity"
elsif required_args > 1
if check_arity == :warn
RodaPlugins.warn "Arity mismatch in block passed to define_roda_method. Expected Arity 1, but multiple arguments required for #{block.inspect}"
end
b = block
block = lambda{|r| instance_exec(r, &b)} # Fallback
end
when :any
if check_dynamic_arity = opts.fetch(:check_dynamic_arity, check_arity)
if keyword
# Complexity of handling keyword arguments using define_method is too high,
# Fallback to instance_exec in this case.
b = block
block = if RUBY_VERSION >= '2.7'
eval('lambda{|*a, **kw| instance_exec(*a, **kw, &b)}', nil, __FILE__, __LINE__) # Keyword arguments fallback
else
# :nocov:
lambda{|*a| instance_exec(*a, &b)} # Keyword arguments fallback
# :nocov:
end
else
arity_meth = meth
meth = :"#{meth}_arity"
end
end
else
raise RodaError, "unexpected arity passed to define_roda_method: #{expected_arity.inspect}"
end
end
define_method(meth, &block)
private meth
alias_method meth, meth
if arity_meth
required_args, optional_args, rest, keyword = _define_roda_method_arg_numbers(instance_method(meth))
max_args = required_args + optional_args
define_method(arity_meth) do |*a|
arity = a.length
if arity > required_args
if arity > max_args && !rest
if check_dynamic_arity == :warn
RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. At most #{max_args} arguments accepted, but #{arity} arguments given for #{block.inspect}"
end
a = a.slice(0, max_args)
end
elsif arity < required_args
if check_dynamic_arity == :warn
RodaPlugins.warn "Dynamic arity mismatch in block passed to define_roda_method. #{required_args} args required, but #{arity} arguments given for #{block.inspect}"
end
a.concat([nil] * (required_args - arity))
end
send(meth, *a)
end
private arity_meth
alias_method arity_meth, arity_meth
end
call_meth
end
# Expand the given path, using the root argument as the base directory.
def expand_path(path, root=opts[:root])
::File.expand_path(path, root)
end
# Freeze the internal state of the class, to avoid thread safety issues at runtime.
# It's optional to call this method, as nothing should be modifying the
# internal state at runtime anyway, but this makes sure an exception will
# be raised if you try to modify the internal state after calling this.
#
# Note that freezing the class prevents you from subclassing it, mostly because
# it would cause some plugins to break.
def freeze
return self if frozen?
unless opts[:subclassed]
# If the _roda_run_main_route instance method has not been overridden,
# make it an alias to _roda_main_route for performance
if instance_method(:_roda_run_main_route).owner == InstanceMethods
class_eval("alias _roda_run_main_route _roda_main_route")
end
self::RodaResponse.class_eval do
if instance_method(:set_default_headers).owner == ResponseMethods &&
instance_method(:default_headers).owner == ResponseMethods
private
alias set_default_headers set_default_headers
def set_default_headers
@headers[RodaResponseHeaders::CONTENT_TYPE] ||= 'text/html'
end
end
end
if @middleware.empty? && use_new_dispatch_api?
plugin :direct_call
end
if ([:on, :is, :_verb, :_match_class_String, :_match_class_Integer, :_match_string, :_match_regexp, :empty_path?, :if_match, :match, :_match_class]).all?{|m| self::RodaRequest.instance_method(m).owner == RequestMethods}
plugin :_optimized_matching
end
end
build_rack_app
@opts.freeze
@middleware.freeze
super
end
# Rebuild the _roda_before and _roda_after methods whenever a plugin might
# have added a _roda_before_* or _roda_after_* method.
def include(*a)
res = super
def_roda_before
def_roda_after
res
end
# When inheriting Roda, copy the shared data into the subclass,
# and setup the request and response subclasses.
def inherited(subclass)
raise RodaError, "Cannot subclass a frozen Roda class" if frozen?
# Mark current class as having been subclassed, as some optimizations
# depend on the class not being subclassed
opts[:subclassed] = true
super
subclass.instance_variable_set(:@inherit_middleware, @inherit_middleware)
subclass.instance_variable_set(:@middleware, @inherit_middleware ? @middleware.dup : [])
subclass.instance_variable_set(:@opts, opts.dup)
subclass.opts.delete(:subclassed)
subclass.opts.to_a.each do |k,v|
if (v.is_a?(Array) || v.is_a?(Hash)) && !v.frozen?
subclass.opts[k] = v.dup
end
end
if block = @raw_route_block
subclass.route(&block)
end
request_class = Class.new(self::RodaRequest)
request_class.roda_class = subclass
request_class.match_pattern_cache = RodaCache.new
subclass.const_set(:RodaRequest, request_class)
response_class = Class.new(self::RodaResponse)
response_class.roda_class = subclass
subclass.const_set(:RodaResponse, response_class)
end
# Load a new plugin into the current class. A plugin can be a module
# which is used directly, or a symbol representing a registered plugin
# which will be required and then used. Returns nil.
#
# Note that you should not load plugins into a Roda class after the
# class has been subclassed, as doing so can break the subclasses.
#
# Roda.plugin PluginModule
# Roda.plugin :csrf
def plugin(plugin, *args, &block)
raise RodaError, "Cannot add a plugin to a frozen Roda class" if frozen?
plugin = RodaPlugins.load_plugin(plugin) if plugin.is_a?(Symbol)
raise RodaError, "Invalid plugin type: #{plugin.class.inspect}" unless plugin.is_a?(Module)
if !plugin.respond_to?(:load_dependencies) && !plugin.respond_to?(:configure) && (!args.empty? || block)
# RODA4: switch from warning to error
RodaPlugins.warn("Plugin #{plugin} does not accept arguments or a block, but arguments or a block was passed when loading this. This will raise an error in Roda 4.")
end
plugin.load_dependencies(self, *args, &block) if plugin.respond_to?(:load_dependencies)
include(plugin::InstanceMethods) if defined?(plugin::InstanceMethods)
extend(plugin::ClassMethods) if defined?(plugin::ClassMethods)
self::RodaRequest.send(:include, plugin::RequestMethods) if defined?(plugin::RequestMethods)
self::RodaRequest.extend(plugin::RequestClassMethods) if defined?(plugin::RequestClassMethods)
self::RodaResponse.send(:include, plugin::ResponseMethods) if defined?(plugin::ResponseMethods)
self::RodaResponse.extend(plugin::ResponseClassMethods) if defined?(plugin::ResponseClassMethods)
plugin.configure(self, *args, &block) if plugin.respond_to?(:configure)
@app = nil
end
# :nocov:
ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
# :nocov:
# Setup routing tree for the current Roda application, and build the
# underlying rack application using the stored middleware. Requires
# a block, which is yielded the request. By convention, the block
# argument should be named +r+. Example:
#
# Roda.route do |r|
# r.root do
# "Root"
# end
# end
#
# This should only be called once per class, and if called multiple
# times will overwrite the previous routing.
def route(&block)
unless block
RodaPlugins.warn "no block passed to Roda.route"
return
end
@raw_route_block = block
@route_block = block = convert_route_block(block)
@rack_app_route_block = block = rack_app_route_block(block)
public define_roda_method(:_roda_main_route, 1, &block)
@app = nil
end
# Add a middleware to use for the rack application. Must be
# called before calling #route to have an effect. Example:
#
# Roda.use Rack::ShowExceptions
def use(*args, &block)
@middleware << [args, block].freeze
@app = nil
end
# :nocov:
ruby2_keywords(:use) if respond_to?(:ruby2_keywords, true)
# :nocov:
private
# Return the number of required argument, optional arguments,
# whether the callable accepts any additional arguments,
# and whether the callable accepts keyword arguments (true, false
# or :required).
def _define_roda_method_arg_numbers(callable)
optional_args = 0
rest = false
keyword = false
callable.parameters.map(&:first).each do |arg_type, _|
case arg_type
when :opt
optional_args += 1
when :rest
rest = true
when :keyreq
keyword = :required
when :key, :keyrest
keyword ||= true
end
end
arity = callable.arity
if arity < 0
arity = arity.abs - 1
end
required_args = arity
arity -= 1 if keyword == :required
if callable.is_a?(Proc) && !callable.lambda?
optional_args -= arity
end
[required_args, optional_args, rest, keyword]
end
# The base rack app to use, before middleware is added.
def base_rack_app_callable(new_api=true)
if new_api
lambda{|env| new(env)._roda_handle_main_route}
else
block = @rack_app_route_block
lambda{|env| new(env).call(&block)}
end
end
# Build the rack app to use
def build_rack_app
app = base_rack_app_callable(use_new_dispatch_api?)
@middleware.reverse_each do |args, bl|
mid, *args = args
app = mid.new(app, *args, &bl)
app.freeze if opts[:freeze_middleware]
end
@app = app
end
# Modify the route block to use for any route block provided as input,
# which can include route blocks that are delegated to by the main route block.
# Can be modified by plugins.
def convert_route_block(block)
block
end
# Build a _roda_before method that calls each _roda_before_* method
# in order, if any _roda_before_* methods are defined. Also, rebuild
# the route block if a _roda_before method is defined.
def def_roda_before
meths = private_instance_methods.grep(/\A_roda_before_\d\d/).sort
unless meths.empty?
plugin :_before_hook unless private_method_defined?(:_roda_before)
if meths.length == 1
class_eval("alias _roda_before #{meths.first}", __FILE__, __LINE__)
else
class_eval("def _roda_before; #{meths.join(';')} end", __FILE__, __LINE__)
end
private :_roda_before
alias_method :_roda_before, :_roda_before
end
end
# Build a _roda_after method that calls each _roda_after_* method
# in order, if any _roda_after_* methods are defined. Also, use
# the internal after hook plugin if the _roda_after method is defined.
def def_roda_after
meths = private_instance_methods.grep(/\A_roda_after_\d\d/).sort
unless meths.empty?
plugin :error_handler unless private_method_defined?(:_roda_after)
if meths.length == 1
class_eval("alias _roda_after #{meths.first}", __FILE__, __LINE__)
else
class_eval("def _roda_after(res); #{meths.map{|s| "#{s}(res)"}.join(';')} end", __FILE__, __LINE__)
end
private :_roda_after
alias_method :_roda_after, :_roda_after
end
end
# The route block to use when building the rack app (or other initial
# entry point to the route block).
# By default, modifies the rack app route block to support before hooks
# if any before hooks are defined.
# Can be modified by plugins.
def rack_app_route_block(block)
block
end
# Whether the new dispatch API should be used.
def use_new_dispatch_api?
# RODA4: remove this method
ancestors.each do |mod|
break if mod == InstanceMethods
meths = mod.instance_methods(false)
if meths.include?(:call) && !(meths.include?(:_roda_handle_main_route) || meths.include?(:_roda_run_main_route))
RodaPlugins.warn < 'GET'
def env
@_request.env
end
# The class-level options hash. This should probably not be
# modified at the instance level. Example:
#
# Roda.plugin :render
# Roda.route do |r|
# opts[:render_opts].inspect
# end
def opts
self.class.opts
end
attr_reader :_request # :nodoc:
alias request _request
remove_method :_request
attr_reader :_response # :nodoc:
alias response _response
remove_method :_response
# The session hash for the current request. Raises RodaError
# if no session exists. Example:
#
# session # => {}
def session
@_request.session
end
private
# Convert the segment matched by the Integer matcher to an integer.
def _convert_class_Integer(value)
value.to_i
end
end
end
end
extend RodaPlugins::Base::ClassMethods
plugin RodaPlugins::Base
end
jeremyevans-roda-4f30bb3/lib/roda/ 0000775 0000000 0000000 00000000000 15167207754 0017103 5 ustar 00root root 0000000 0000000 jeremyevans-roda-4f30bb3/lib/roda/cache.rb 0000664 0000000 0000000 00000001760 15167207754 0020477 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require "thread"
class Roda
# A thread safe cache class, offering only #[] and #[]= methods,
# each protected by a mutex.
class RodaCache
# Create a new thread safe cache.
def initialize
@mutex = Mutex.new
@hash = {}
end
# Make getting value from underlying hash thread safe.
def [](key)
@mutex.synchronize{@hash[key]}
end
# Make setting value in underlying hash thread safe.
def []=(key, value)
@mutex.synchronize{@hash[key] = value}
end
# Return the frozen internal hash. The internal hash can then
# be accessed directly since it is frozen and there are no
# thread safety issues.
def freeze
@hash.freeze
end
private
# Create a copy of the cache with a separate mutex.
def initialize_copy(other)
@mutex = Mutex.new
other.instance_variable_get(:@mutex).synchronize do
@hash = other.instance_variable_get(:@hash).dup
end
end
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins.rb 0000664 0000000 0000000 00000003701 15167207754 0021112 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require_relative "cache"
class Roda
# Module in which all Roda plugins should be stored. Also contains logic for
# registering and loading plugins.
module RodaPlugins
OPTS = {}.freeze
EMPTY_ARRAY = [].freeze
# Stores registered plugins
@plugins = RodaCache.new
class << self
# Make warn a public method, as it is used for deprecation warnings.
# Roda::RodaPlugins.warn can be overridden for custom handling of
# deprecation warnings.
public :warn
end
# If the registered plugin already exists, use it. Otherwise,
# require it and return it. This raises a LoadError if such a
# plugin doesn't exist, or a RodaError if it exists but it does
# not register itself correctly.
def self.load_plugin(name)
h = @plugins
unless plugin = h[name]
require "roda/plugins/#{name}"
raise RodaError, "Plugin #{name} did not register itself correctly in Roda::RodaPlugins" unless plugin = h[name]
end
plugin
end
# Register the given plugin with Roda, so that it can be loaded using #plugin
# with a symbol. Should be used by plugin files. Example:
#
# Roda::RodaPlugins.register_plugin(:plugin_name, PluginModule)
def self.register_plugin(name, mod)
@plugins[name] = mod
end
# Deprecate the constant with the given name in the given module,
# if the ruby version supports it.
def self.deprecate_constant(mod, name)
# :nocov:
if RUBY_VERSION >= '2.3'
mod.deprecate_constant(name)
end
# :nocov:
end
if RUBY_VERSION >= '3.3'
# Create a new module using the block, and set the temporary name
# on it using the given a containing module and name.
def self.set_temp_name(mod)
mod.set_temporary_name(yield)
mod
end
# :nocov:
else
def self.set_temp_name(mod)
mod
end
end
# :nocov:
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/ 0000775 0000000 0000000 00000000000 15167207754 0020564 5 ustar 00root root 0000000 0000000 jeremyevans-roda-4f30bb3/lib/roda/plugins/Integer_matcher_max.rb 0000664 0000000 0000000 00000003017 15167207754 0025057 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The Integer_matcher_max plugin sets the maximum integer value
# value that the Integer class matcher will match by default.
# By default, loading this plugin sets the maximum value to
# 2**63-1, the largest signed 64-bit integer value:
#
# plugin :Integer_matcher_max
# route do |r|
# r.is Integer do
# # Matches /9223372036854775807
# # Does not match /9223372036854775808
# end
# end
#
# To specify a different maximum value, you can pass a different
# maximum value when loading the plugin:
#
# plugin :Integer_matcher_max, 2**64-1
module IntegerMatcherMax
def self.configure(app, max=nil)
if max
app.class_eval do
meth = :_max_value_convert_class_Integer
define_method(meth){max}
alias_method meth, meth
private meth
end
end
end
module InstanceMethods
private
# Do not have the Integer matcher max when over the maximum
# configured Integer value.
def _convert_class_Integer(value)
value = super
value if value <= _max_value_convert_class_Integer
end
# Use 2**63-1 as the default maximum value for the Integer
# matcher.
def _max_value_convert_class_Integer
9223372036854775807
end
end
end
register_plugin(:Integer_matcher_max, IntegerMatcherMax)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/_after_hook.rb 0000664 0000000 0000000 00000000261 15167207754 0023370 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require_relative 'error_handler'
#
class Roda
module RodaPlugins
# RODA4: Remove
register_plugin(:_after_hook, ErrorHandler)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/_base64.rb 0000664 0000000 0000000 00000001156 15167207754 0022337 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
module Base64_
class << self
if RUBY_VERSION >= '2.4'
def decode64(str)
str.unpack1("m0")
end
# :nocov:
else
def decode64(str)
str.unpack("m0")[0]
end
# :nocov:
end
def urlsafe_encode64(bin)
str = [bin].pack("m0")
str.tr!("+/", "-_")
str
end
def urlsafe_decode64(str)
decode64(str.tr("-_", "+/"))
end
end
end
register_plugin(:_base64, Base64_)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/_before_hook.rb 0000664 0000000 0000000 00000002121 15167207754 0023526 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# Internal before hook module, not for external use.
# Allows for plugins to configure the order in which
# before processing is done by using _roda_before_*
# private instance methods that are called in sorted order.
# Loaded automatically by the base library if any _roda_before_*
# methods are defined.
module BeforeHook # :nodoc:
module InstanceMethods
# Run internal before hooks - Old Dispatch API.
def call(&block)
# RODA4: Remove
super do
_roda_before
instance_exec(@_request, &block) # call Fallback
end
end
# Run internal before hooks before running the main
# roda route.
def _roda_run_main_route(r)
_roda_before
super
end
private
# Default empty implementation of _roda_before, usually
# overridden by Roda.def_roda_before.
def _roda_before
end
end
end
register_plugin(:_before_hook, BeforeHook)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/_optimized_matching.rb 0000664 0000000 0000000 00000016126 15167207754 0025134 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The _optimized_matching plugin is automatically used internally to speed
# up matching when a single argument String instance, String class, Integer
# class, or Regexp matcher is passed to +r.on+, +r.is_+, or a verb method
# such as +r.get+ or +r.post+.
#
# The optimization works by avoiding the +if_match+ method if possible.
# Instead of clearing the captures array on every call, and having the
# matching append to the captures, it checks directly for the match,
# and on succesful match, it yields directly to the block without using
# the captures array.
module OptimizedMatching
TERM = Base::RequestMethods::TERM
module RequestMethods
# Optimize the r.is method handling of a single string, String, Integer,
# regexp, or true, argument.
def is(*args, &block)
case args.length
when 1
_is1(args, &block)
when 0
always(&block) if @remaining_path.empty?
else
if_match(args << TERM, &block)
end
end
# Optimize the r.on method handling of a single string, String, Integer,
# or regexp argument. Inline the related matching code to avoid the
# need to modify @captures.
def on(*args, &block)
case args.length
when 1
case matcher = args[0]
when String
always{yield} if _match_string(matcher)
when Class
if matcher == String
rp = @remaining_path
if rp.getbyte(0) == 47
if last = rp.index('/', 1)
@remaining_path = rp[last, rp.length]
always{yield rp[1, last-1]}
elsif (len = rp.length) > 1
@remaining_path = ""
always{yield rp[1, len]}
end
end
elsif matcher == Integer
if (matchdata = /\A\/(\d{1,100})(?=\/|\z)/.match(@remaining_path)) && (value = scope.send(:_convert_class_Integer, matchdata[1]))
@remaining_path = matchdata.post_match
always{yield(value)}
end
else
path = @remaining_path
captures = @captures.clear
meth = :"_match_class_#{matcher}"
if respond_to?(meth, true)
# Allow calling private methods, as match methods are generally private
if send(meth, &block)
block_result(yield(*captures))
throw :halt, response.finish
else
@remaining_path = path
false
end
else
unsupported_matcher(matcher)
end
end
when Regexp
if matchdata = self.class.cached_matcher(matcher){matcher}.match(@remaining_path)
@remaining_path = matchdata.post_match
always{yield(*matchdata.captures)}
end
when true
always(&block)
when false, nil
# nothing
else
path = @remaining_path
captures = @captures.clear
matched = case matcher
when Array
_match_array(matcher)
when Hash
_match_hash(matcher)
when Symbol
_match_symbol(matcher)
when Proc
matcher.call
else
unsupported_matcher(matcher)
end
if matched
block_result(yield(*captures))
throw :halt, response.finish
else
@remaining_path = path
false
end
end
when 0
always(&block)
else
if_match(args, &block)
end
end
private
# Optimize the r.get/r.post method handling of a single string, String, Integer,
# regexp, or true, argument.
def _verb(args, &block)
case args.length
when 0
always(&block)
when 1
_is1(args, &block)
else
if_match(args << TERM, &block)
end
end
# Internals of r.is/r.get/r.post optimization. Inline the related matching
# code to avoid the need to modify @captures.
def _is1(args, &block)
case matcher = args[0]
when String
rp = @remaining_path
if _match_string(matcher)
if @remaining_path.empty?
always{yield}
else
@remaining_path = rp
nil
end
end
when Class
if matcher == String
rp = @remaining_path
if rp.getbyte(0) == 47 && !rp.index('/', 1) && (len = rp.length) > 1
@remaining_path = ''
always{yield rp[1, len]}
end
elsif matcher == Integer
if (matchdata = /\A\/(\d{1,100})\z/.match(@remaining_path)) && (value = scope.send(:_convert_class_Integer, matchdata[1]))
@remaining_path = ''
always{yield(value)}
end
else
path = @remaining_path
captures = @captures.clear
meth = :"_match_class_#{matcher}"
if respond_to?(meth, true)
# Allow calling private methods, as match methods are generally private
if send(meth, &block) && @remaining_path.empty?
block_result(yield(*captures))
throw :halt, response.finish
else
@remaining_path = path
false
end
else
unsupported_matcher(matcher)
end
end
when Regexp
if (matchdata = self.class.cached_matcher(matcher){matcher}.match(@remaining_path)) && matchdata.post_match.empty?
@remaining_path = ''
always{yield(*matchdata.captures)}
end
when true
always(&block) if @remaining_path.empty?
when false, nil
# nothing
else
path = @remaining_path
captures = @captures.clear
matched = case matcher
when Array
_match_array(matcher)
when Hash
_match_hash(matcher)
when Symbol
_match_symbol(matcher)
when Proc
matcher.call
else
unsupported_matcher(matcher)
end
if matched && @remaining_path.empty?
block_result(yield(*captures))
throw :halt, response.finish
else
@remaining_path = path
false
end
end
end
end
end
register_plugin(:_optimized_matching, OptimizedMatching)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/_symbol_class_matchers.rb 0000664 0000000 0000000 00000007025 15167207754 0025634 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
module SymbolClassMatchers_
module ClassMethods
private
# Backend of symbol_matcher and class_matcher.
def _symbol_class_matcher(expected_class, obj, matcher, block, &request_class_block)
unless obj.is_a?(expected_class)
raise RodaError, "Invalid type passed to class_matcher or symbol_matcher: #{matcher.inspect}"
end
if obj.is_a?(Symbol)
type = "symbol"
meth = :"match_symbol_#{obj}"
else
type = "class"
meth = :"_match_class_#{obj}"
end
case matcher
when Regexp
regexp = matcher
consume_regexp = self::RodaRequest.send(:consume_pattern, regexp)
when Symbol
unless opts[:symbol_matchers]
raise RodaError, "cannot provide Symbol matcher to class_matcher unless using symbol_matchers plugin: #{matcher.inspect}"
end
regexp, consume_regexp, matcher_block = opts[:symbol_matchers][matcher]
unless regexp
raise RodaError, "unregistered symbol matcher given to #{type}_matcher: #{matcher.inspect}"
end
block = _merge_matcher_blocks(type, obj, block, matcher_block)
when Class
unless opts[:class_matchers]
raise RodaError, "cannot provide Class matcher to symbol_matcher unless using class_matchers plugin: #{matcher.inspect}"
end
regexp, consume_regexp, matcher_block = opts[:class_matchers][matcher]
unless regexp
raise RodaError, "unregistered class matcher given to #{type}_matcher: #{matcher.inspect}"
end
block = _merge_matcher_blocks(type, obj, block, matcher_block)
else
raise RodaError, "unsupported matcher given to #{type}_matcher: #{matcher.inspect}"
end
if block.is_a?(Symbol)
convert_meth = block
elsif block
convert_meth = :"_convert_#{type}_#{obj}"
define_method(convert_meth, &block)
private convert_meth
end
array = opts[:"#{type}_matchers"][obj] = [regexp, consume_regexp, convert_meth].freeze
self::RodaRequest.class_eval do
class_exec(meth, array, &request_class_block)
private meth
end
nil
end
# If both block and matche_meth are given,
# define a method for block, and then return a
# proc that calls matcher_meth first, and only calls
# the newly defined method with the return values of matcher_meth
# if matcher_method returns a truthy value.
# Otherwise, return matcher_meth or block.
def _merge_matcher_blocks(type, obj, block, matcher_meth)
if matcher_meth
if block
convert_meth = :"_convert_merge_#{type}_#{obj}"
define_method(convert_meth, &block)
private convert_meth
proc do |*a|
if captures = send(matcher_meth, *a)
if captures.is_a?(Array)
send(convert_meth, *captures)
else
send(convert_meth, captures)
end
end
end
else
matcher_meth
end
else
block
end
end
end
end
register_plugin(:_symbol_class_matchers, SymbolClassMatchers_)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/_symbol_regexp_matchers.rb 0000664 0000000 0000000 00000001157 15167207754 0026021 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The _symbol_regexp_matchers plugin is designed for internal use by other plugins,
# for the historical behavior of a symbol matching an arbitrary segment by default
# using a regexp.
module SymbolRegexpMatchers
module RequestMethods
private
# The regular expression to use for matching symbols. By default, any non-empty
# segment matches.
def _match_symbol_regexp(s)
"([^\\/]+)"
end
end
end
register_plugin(:_symbol_regexp_matchers, SymbolRegexpMatchers)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/additional_render_engines.rb 0000664 0000000 0000000 00000004032 15167207754 0026267 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The additional_render_engines plugin allows for specifying additional render
# engines to consider for templates. When rendering a template, it will
# first try the default template engine specified in the render plugin. If the
# template file to be rendered does not exist, it will try each additional render
# engine specified in this plugin, in order, using the path to the first
# template file that exists in the file system. If no such path is found, it
# uses the default path specified by the render plugin.
#
# Example:
#
# plugin :render # default engine is 'erb'
# plugin :additional_render_engines, ['haml', 'str']
#
# route do |r|
# # Will check the following in order, using path for first
# # template file that exists:
# # * views/t.erb
# # * views/t.haml
# # * views/t.str
# render :t
# end
module AdditionalRenderEngines
def self.load_dependencies(app, render_engines)
app.plugin :render
end
# Set the additional render engines to consider.
def self.configure(app, render_engines)
app.opts[:additional_render_engines] = render_engines.dup.freeze
end
module InstanceMethods
private
# If the template path does not exist, try looking for the template
# using each of the render engines, in order, returning
# the first path that exists. If no template path exists for the
# default any or any additional engines, return the original path.
def template_path(opts)
orig_path = super
unless File.file?(orig_path)
self.opts[:additional_render_engines].each do |engine|
path = super(opts.merge(:engine=>engine))
return path if File.file?(path)
end
end
orig_path
end
end
end
register_plugin(:additional_render_engines, AdditionalRenderEngines)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/additional_view_directories.rb 0000664 0000000 0000000 00000004570 15167207754 0026655 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The additional_view_directories plugin allows for specifying additional view
# directories to look in for templates. When rendering a template, it will
# first try the :views directory specified in the render plugin. If the template
# file to be rendered does not exist in that directory, it will try each additional
# view directory specified in this plugin, in order, using the path to the first
# template file that exists in the file system. If no such path is found, it
# uses the default path specified by the render plugin.
#
# Example:
#
# plugin :render, :views=>'dir'
# plugin :additional_view_directories, ['dir1', 'dir2', 'dir3']
#
# route do |r|
# # Will check the following in order, using path for first
# # template file that exists:
# # * dir/t.erb
# # * dir1/t.erb
# # * dir2/t.erb
# # * dir3/t.erb
# render :t
# end
module AdditionalViewDirectories
# Depend on the render plugin, since this plugin only makes
# sense when the render plugin is used.
def self.load_dependencies(app, view_dirs)
app.plugin :render
end
# Set the additional view directories to look in. Each additional view directory
# is also added as an allowed path.
def self.configure(app, view_dirs)
view_dirs = app.opts[:additional_view_directories] = view_dirs.map{|f| app.expand_path(f, nil)}.freeze
app.plugin :render, :allowed_paths=>(app.opts[:render][:allowed_paths] + view_dirs).uniq.freeze
end
module InstanceMethods
private
# If the template path does not exist, try looking for the template
# in each of the additional view directories, in order, returning
# the first path that exists. If no additional directory includes
# the template, return the original path.
def template_path(opts)
orig_path = super
unless File.file?(orig_path)
self.opts[:additional_view_directories].each do |view_dir|
path = super(opts.merge(:views=>view_dir))
return path if File.file?(path)
end
end
orig_path
end
end
end
register_plugin(:additional_view_directories, AdditionalViewDirectories)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/all_verbs.rb 0000664 0000000 0000000 00000002570 15167207754 0023066 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The all_verbs plugin adds methods for http verbs other than
# get and post. The following verbs are added, assuming
# rack handles them: delete, head, options, link, patch, put,
# trace, unlink.
#
# These methods operate just like Roda's default get and post
# methods, so using them without any arguments just checks for
# the request method, while using them with any arguments also
# checks that the arguments match the full path.
#
# Example:
#
# plugin :all_verbs
#
# route do |r|
# r.delete do
# # Handle DELETE
# end
# r.put do
# # Handle PUT
# end
# r.patch do
# # Handle PATCH
# end
# end
#
# The verb methods are defined via metaprogramming, so there
# isn't documentation for the individual methods created.
module AllVerbs
module RequestMethods
%w'delete head options link patch put trace unlink'.each do |verb|
if ::Rack::Request.method_defined?("#{verb}?")
class_eval(<<-END, __FILE__, __LINE__+1)
def #{verb}(*args, &block)
_verb(args, &block) if #{verb}?
end
END
end
end
end
end
register_plugin(:all_verbs, AllVerbs)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/assets.rb 0000664 0000000 0000000 00000107200 15167207754 0022413 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The assets plugin adds support for rendering your CSS and javascript
# asset files on the fly in development, and compiling them
# to a single, compressed file in production.
#
# This uses the render plugin for rendering the assets, and the render
# plugin uses tilt internally, so you can use any template engine
# supported by tilt for your assets. Tilt ships with support for
# the following asset template engines, assuming the necessary libraries
# are installed:
#
# css :: Less, Sass, Scss
# js :: CoffeeScript
#
# You can also use opal as a javascript template engine, assuming it is
# installed.
#
# == Usage
#
# When loading the plugin, use the :css and :js options
# to set the source file(s) to use for CSS and javascript assets:
#
# plugin :assets, css: 'some_file.scss', js: 'some_file.coffee'
#
# This will look for the following files:
#
# assets/css/some_file.scss
# assets/js/some_file.coffee
#
# The values for the :css and :js options can be arrays to load multiple
# files. If you want to change the paths where asset files are stored, see the
# Options section below.
#
# === Serving
#
# In your routes, call the +r.assets+ method to add a route to your assets,
# which will make your app serve the rendered assets:
#
# route do |r|
# r.assets
# end
#
# You should generally call +r.assets+ inside the route block itself, and not
# under any branches of the routing tree.
#
# === Views
#
# In your layout view, use the assets method to add links to your CSS and
# javascript assets:
#
# <%= assets(:css) %>
# <%= assets(:js) %>
#
# You can add attributes to the tags by using an options hash:
#
# <%= assets(:css, media: 'print') %>
#
# The assets method will respect the application's +:add_script_name+ option,
# if it is set it will automatically prefix the path with the +SCRIPT_NAME+ for
# the request.
#
# == Asset Paths
#
# If you just want the paths rather than the full tags, you can use
# assets_paths instead. This will return an array of the sources that
# the assets function would have put into tags:
#
# assets_paths(:css)
# # => ["/assets/css/foo.css", "/assets/css/app.css"]
#
# If compilation is turned on, it will return the path to the compiled
# asset:
#
# assets_paths(:css)
# # => ["/assets/app.5e7b06baa1a514d8473b0eca514b806c201073b9.css"]
#
# == Asset Groups
#
# The asset plugin supports groups for the cases where you have different
# css/js files for your front end and back end. To use asset groups, you
# pass a hash for the :css and/or :js options:
#
# plugin :assets, css: {frontend: 'some_frontend_file.scss',
# backend: 'some_backend_file.scss'}
#
# This expects the following directory structure for your assets:
#
# assets/css/frontend/some_frontend_file.scss
# assets/css/backend/some_backend_file.scss
#
# If you do not want to force that directory structure when using
# asset groups, you can use the group_subdirs: false option.
#
# In your view code use an array argument in your call to assets:
#
# <%= assets([:css, :frontend]) %>
#
# === Nesting
#
# Asset groups also support nesting, though that should only be needed
# in fairly large applications. You can use a nested hash when loading
# the plugin:
#
# plugin :assets,
# css: {frontend: {dashboard: 'some_frontend_file.scss'}}
#
# and an extra entry per nesting level when creating the tags.
#
# <%= assets([:css, :frontend, :dashboard]) %>
#
# == Caching
#
# The assets plugin uses the caching plugin internally, and will set the
# Last-Modified header to the modified timestamp of the asset source file
# when rendering the asset.
#
# If you have assets that include other asset files, such as using @import
# in a sass file, you need to specify the dependencies for your assets so
# that the assets plugin will correctly pick up changes. You can do this
# using the :dependencies option to the plugin, which takes a hash where
# the keys are paths to asset files, and values are arrays of paths to
# dependencies of those asset files:
#
# app.plugin :assets,
# dependencies: {'assets/css/bootstrap.scss'=>Dir['assets/css/bootstrap/' '**/*.scss']}
#
# == Asset Compilation
#
# In production, you are generally going to want to compile your assets
# into a single file, with you can do by calling compile_assets after
# loading the plugin:
#
# plugin :assets, css: 'some_file.scss', js: 'some_file.coffee'
# compile_assets
#
# After calling compile_assets, calls to assets in your views will default
# to a using a single link each to your CSS and javascript compiled asset
# files. By default the compiled files are written to the public directory,
# so that they can be served by the webserver.
#
# === Asset Compression
#
# If you have the yuicompressor gem installed and working, it will be used
# automatically to compress your javascript and css assets. For javascript
# assets, if yuicompressor is not available, the plugin will check for
# closure-compiler, uglifier, and minjs and use the first one that works.
# If no compressors are available, the assets will just be concatenated
# together and not compressed during compilation. You can use the
# :css_compressor and :js_compressor options to specify the compressor to use.
#
# It is also possible to use the built-in compression options in the CSS or JS
# compiler, assuming the compiler supports such options. For example, with
# sass/sassc, you can use:
#
# plugin :assets,
# css_opts: {style: :compressed}
#
# === Source Maps (CSS)
#
# The assets plugin does not have direct support for source maps, so it is
# recommended you use embedded source maps if supported by the CSS compiler.
# For sass/sassc, you can use:
#
# plugin :assets,
# css_opts: {:source_map_embed=>true, source_map_contents: true, source_map_file: "."}
#
# === With Asset Groups
#
# When using asset groups, a separate compiled file will be produced per
# asset group.
#
# === Unique Asset Names
#
# When compiling assets, a unique name is given to each asset file, using the
# a SHA1 hash of the content of the file. This is done so that clients do
# not attempt to use cached versions of the assets if the asset has changed.
#
# === Serving
#
# When compiling assets, +r.assets+ will serve the compiled asset
# files. However, it is recommended to have the main webserver (e.g. nginx)
# serve the compiled files, instead of relying on the application.
#
# Assuming you are using compiled assets in production mode that are served
# by the webserver, you can remove the serving of them by the application:
#
# route do |r|
# r.assets unless ENV['RACK_ENV'] == 'production'
# end
#
# If you do have the application serve the compiled assets, it will use the
# Last-Modified header to make sure that clients do not redownload compiled
# assets that haven't changed.
#
# === Asset Precompilation
#
# If you want to precompile your assets, so they do not need to be compiled
# every time you boot the application, you can provide a :precompiled option
# when loading the plugin. The value of this option should be the filename
# where the compiled asset metadata is stored.
#
# If the compiled asset metadata file does not exist when the assets plugin
# is loaded, the plugin will run in non-compiled mode. However, when you call
# compile_assets, it will write the compiled asset metadata file after
# compiling the assets.
#
# If the compiled asset metadata file already exists when the assets plugin
# is loaded, the plugin will read the file to get the compiled asset metadata,
# and it will run in compiled mode, assuming that the compiled asset files
# already exist.
#
# ==== On Heroku
#
# Heroku supports precompiling the assets when using Roda. You just need to
# add an assets:precompile task, similar to this:
#
# namespace :assets do
# desc "Precompile the assets"
# task :precompile do
# require './app'
# App.compile_assets
# end
# end
#
# == Postprocessing
#
# If you pass a callable object to the :postprocessor option, it will be called
# before an asset is served.
# If the assets are to be compiled, the object will be called at compilation time.
#
# It is passed three arguments; the name of the asset file, the type of the
# asset file (which is a symbol, either :css or :js), and the asset contents.
#
# It should return the new content for the asset.
#
# You can use this to call Autoprefixer on your CSS:
#
# plugin :assets, {
# css: [ 'style.scss' ],
# postprocessor: lambda do |file, type, content|
# type == :css ? AutoprefixerRails.process(content).css : content
# end
# }
#
# == External Assets/Assets from Gems
#
# The assets plugin only supports loading assets files underneath the assets
# path. You cannot pass an absolute path to an asset file and have it
# work. If you would like to reference asset files that are outside the assets
# path, you have the following options:
#
# * Copy, hard link, or symlink the external assets files into the assets path.
# * Use tilt-indirect or another method of indirection (such as an erb template that loads
# the external asset file) so that a file inside the assets path can reference files
# outside the assets path.
#
# == Plugin Options
#
# :add_suffix :: Whether to append a .css or .js extension to asset routes in non-compiled mode
# (default: false)
# :compiled_asset_host :: The asset host to use for compiled assets. Should include the protocol
# as well as the host (e.g. "https://cdn.example.com", "//cdn.example.com")
# :compiled_css_dir :: Directory name in which to store the compiled css file,
# inside :compiled_path (default: nil)
# :compiled_css_route :: Route under :prefix for compiled css assets (default: :compiled_css_dir)
# :compiled_js_dir :: Directory name in which to store the compiled javascript file,
# inside :compiled_path (default: nil)
# :compiled_js_route :: Route under :prefix for compiled javscript assets (default: :compiled_js_dir)
# :compiled_name :: Compiled file name prefix (default: 'app')
# :compiled_path:: Path inside public folder in which compiled files are stored (default: :prefix)
# :concat_only :: Whether to just concatenate instead of concatenating
# and compressing files (default: false)
# :css_compressor :: Compressor to use for compressing CSS, either :yui, :none, or nil (the default, which will try
# :yui if available, but not fail if it is not available)
# :css_dir :: Directory name containing your css source, inside :path (default: 'css')
# :css_headers :: A hash of additional headers for your rendered css files
# :css_opts :: Template options to pass to the render plugin (via :template_opts) when rendering css assets
# :css_route :: Route under :prefix for css assets (default: :css_dir)
# :dependencies :: A hash of dependencies for your asset files. Keys should be paths to asset files,
# values should be arrays of paths your asset files depends on. This is used to
# detect changes in your asset files.
# :early_hints :: Automatically send early hints for all assets. Requires the early_hints plugin.
# :group_subdirs :: Whether a hash used in :css and :js options requires the assets for the
# related group are contained in a subdirectory with the same name (default: true)
# :gzip :: Store gzipped compiled assets files, and serve those to clients who accept gzip encoding.
# :headers :: A hash of additional headers for both js and css rendered files
# :js_compressor :: Compressor to use for compressing javascript, either :yui, :closure, :uglifier, :minjs,
# :none, or nil (the default, which will try :yui, :closure, :uglifier, then :minjs, but
# not fail if any of them is not available)
# :js_dir :: Directory name containing your javascript source, inside :path (default: 'js')
# :js_headers :: A hash of additional headers for your rendered javascript files
# :js_opts :: Template options to pass to the render plugin (via :template_opts) when rendering javascript assets
# :js_route :: Route under :prefix for javascript assets (default: :js_dir)
# :path :: Path to your asset source directory (default: 'assets'). Relative
# paths will be considered relative to the application's :root option.
# :postprocessor :: A block which should accept three arguments (asset name, asset type,
# content). This block can be used to hook into the asset system and
# make your own modifications before the asset is served. If the asset
# is to be compiled, the block is called at compile time.
# :prefix :: Prefix for assets path in your URL/routes (default: 'assets')
# :precompiled :: Path to the compiled asset metadata file. If the file exists, will use compiled
# mode using the metadata in the file. If the file does not exist, will use
# non-compiled mode, but will write the metadata to the file if compile_assets is called.
# :public :: Path to your public folder, in which compiled files are placed (default: 'public'). Relative
# paths will be considered relative to the application's :root option.
# :relative_paths :: Use relative paths instead of absolute paths when setting up link and script tags for
# assets.
# :sri :: Enables subresource integrity when setting up references to compiled assets. The value should be
# :sha256, :sha384, or :sha512 depending on which hash algorithm you want to use. This changes the
# hash algorithm that Roda will use when naming compiled asset files. The default is :sha256, you
# can use nil to disable subresource integrity.
# :timestamp_paths :: Include the timestamp of assets in asset paths in non-compiled mode. Doing this can
# slow down development requests due to additional requests to get last modified times,
# but it will make sure the paths change in development when there are modifications,
# which can fix issues when using a caching proxy in non-compiled mode. This can also
# be specified as a string to use that string to separate the timestamp from the asset.
# By default, / is used as the separator if timestamp paths are enabled.
module Assets
DEFAULTS = {
:compiled_name => 'app'.freeze,
:js_dir => 'js'.freeze,
:css_dir => 'css'.freeze,
:prefix => 'assets'.freeze,
:concat_only => false,
:compiled => false,
:add_suffix => false,
:early_hints => false,
:timestamp_paths => false,
:group_subdirs => true,
:compiled_css_dir => nil,
:compiled_js_dir => nil,
:sri => :sha256
}.freeze
# Internal exception raised when a compressor cannot be found
CompressorNotFound = Class.new(RodaError)
# Load the render, caching, and h plugins, since the assets plugin
# depends on them.
def self.load_dependencies(app, opts = OPTS)
app.plugin :render
app.plugin :caching
app.plugin :h
if opts[:relative_paths]
app.plugin :relative_path
end
end
# Setup the options for the plugin. See the Assets module RDoc
# for a description of the supported options.
def self.configure(app, opts = {})
if app.assets_opts
prev_opts = app.assets_opts[:orig_opts]
orig_opts = app.assets_opts[:orig_opts].merge(opts)
[:headers, :css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s|
if prev_opts[s]
if opts[s]
orig_opts[s] = prev_opts[s].merge(opts[s])
else
orig_opts[s] = prev_opts[s].dup
end
end
end
app.opts[:assets] = orig_opts.dup
app.opts[:assets][:orig_opts] = orig_opts
else
app.opts[:assets] = opts.dup
app.opts[:assets][:orig_opts] = opts
end
opts = app.opts[:assets]
opts[:path] = app.expand_path(opts[:path]||"assets").freeze
opts[:public] = app.expand_path(opts[:public]||"public").freeze
# Combine multiple values into a path, ignoring trailing slashes
j = lambda do |*v|
opts.values_at(*v).
reject{|s| s.to_s.empty?}.
map{|s| s.chomp('/')}.
join('/').freeze
end
# Same as j, but add a trailing slash if not empty
sj = lambda do |*v|
s = j.call(*v)
s.empty? ? s : (s + '/').freeze
end
if opts[:precompiled] && !opts[:compiled] && ::File.exist?(opts[:precompiled])
require 'json'
opts[:compiled] = app.send(:_precompiled_asset_metadata, opts[:precompiled])
end
if opts[:early_hints]
app.plugin :early_hints
end
if opts[:timestamp_paths] && !opts[:timestamp_paths].is_a?(String)
opts[:timestamp_paths] = '/'
end
DEFAULTS.each do |k, v|
opts[k] = v unless opts.has_key?(k)
end
[
[:compiled_path, :prefix],
[:js_route, :js_dir],
[:css_route, :css_dir],
[:compiled_js_route, :compiled_js_dir],
[:compiled_css_route, :compiled_css_dir]
].each do |k, v|
opts[k] = opts[v] unless opts.has_key?(k)
end
[:css_headers, :js_headers, :css_opts, :js_opts, :dependencies].each do |s|
opts[s] ||= {}
end
expanded_deps = opts[:expanded_dependencies] = {}
opts[:dependencies].each do |file, deps|
expanded_deps[File.expand_path(file)] = Array(deps)
end
if headers = opts[:headers]
opts[:css_headers] = headers.merge(opts[:css_headers])
opts[:js_headers] = headers.merge(opts[:js_headers])
end
opts[:css_headers][RodaResponseHeaders::CONTENT_TYPE] ||= "text/css; charset=UTF-8".freeze
opts[:js_headers][RodaResponseHeaders::CONTENT_TYPE] ||= "application/javascript; charset=UTF-8".freeze
[:css_headers, :js_headers, :css_opts, :js_opts, :dependencies, :expanded_dependencies].each do |s|
opts[s].freeze
end
[:headers, :css, :js].each do |s|
opts[s].freeze if opts[s]
end
# Used for reading/writing files
opts[:js_path] = sj.call(:path, :js_dir)
opts[:css_path] = sj.call(:path, :css_dir)
opts[:compiled_js_path] = j.call(:public, :compiled_path, :compiled_js_dir, :compiled_name)
opts[:compiled_css_path] = j.call(:public, :compiled_path, :compiled_css_dir, :compiled_name)
# Used for URLs/routes
opts[:js_prefix] = sj.call(:prefix, :js_route)
opts[:css_prefix] = sj.call(:prefix, :css_route)
opts[:compiled_js_prefix] = j.call(:prefix, :compiled_js_route, :compiled_name)
opts[:compiled_css_prefix] = j.call(:prefix, :compiled_css_route, :compiled_name)
opts[:js_suffix] = (opts[:add_suffix] ? '.js' : '').freeze
opts[:css_suffix] = (opts[:add_suffix] ? '.css' : '').freeze
opts.freeze
end
module ClassMethods
# Return the assets options for this class.
def assets_opts
opts[:assets]
end
# Compile options for the given asset type. If no asset_type
# is given, compile both the :css and :js asset types. You
# can specify an array of types (e.g. [:css, :frontend]) to
# compile assets for the given asset group.
def compile_assets(type=nil)
require 'fileutils'
unless assets_opts[:compiled]
opts[:assets] = assets_opts.merge(:compiled => _compiled_assets_initial_hash).freeze
end
if type == nil
_compile_assets(:css)
_compile_assets(:js)
else
_compile_assets(type)
end
if precompile_file = assets_opts[:precompiled]
require 'json'
::FileUtils.mkdir_p(File.dirname(precompile_file))
tmp_file = "#{precompile_file}.tmp"
::File.open(tmp_file, 'wb'){|f| f.write((opts[:json_serializer] || :to_json.to_proc).call(assets_opts[:compiled]))}
::File.rename(tmp_file, precompile_file)
end
assets_opts[:compiled]
end
private
# The initial hash to use to store compiled asset metadata.
def _compiled_assets_initial_hash
{}
end
# Internals of compile_assets, handling recursive calls for loading
# all asset groups under the given type.
def _compile_assets(type)
type, *dirs = type if type.is_a?(Array)
dirs ||= []
files = assets_opts[type]
dirs.each{|d| files = files[d]}
case files
when Hash
files.each_key{|dir| _compile_assets([type] + dirs + [dir])}
else
files = Array(files)
compile_assets_files(files, type, dirs) unless files.empty?
end
end
# The precompiled asset metadata stored in the given file
def _precompiled_asset_metadata(file)
(opts[:json_parser] || ::JSON.method(:parse)).call(::File.read(file))
end
# Compile each array of files for the given type into a single
# file. Dirs should be an array of asset group names, if these
# are files in an asset group.
def compile_assets_files(files, type, dirs)
dirs = nil if dirs && dirs.empty?
o = assets_opts
app = allocate
content = files.map do |file|
file = "#{dirs.join('/')}/#{file}" if dirs && o[:group_subdirs]
file = "#{o[:"#{type}_path"]}#{file}"
app.read_asset_file(file, type)
end.join("\n")
unless o[:concat_only]
content = compress_asset(content, type)
end
suffix = ".#{dirs.join('.')}" if dirs
key = "#{type}#{suffix}"
unique_id = o[:compiled][key] = asset_digest(content)
path = "#{o[:"compiled_#{type}_path"]}#{suffix}.#{unique_id}.#{type}"
::FileUtils.mkdir_p(File.dirname(path))
::File.open(path, 'wb'){|f| f.write(content)}
if o[:gzip]
require 'zlib'
Zlib::GzipWriter.open("#{path}.gz") do |gz|
gz.write(content)
end
end
nil
end
# Compress the given content for the given type by using the
# configured compressor, or trying the supported compressors.
def compress_asset(content, type)
case compressor = assets_opts[:"#{type}_compressor"]
when :none
return content
when nil
# default, try different compressors
else
# Allow calling private compress methods
return send("compress_#{type}_#{compressor}", content)
end
compressors = if type == :js
[:yui, :closure, :uglifier, :minjs]
else
[:yui]
end
compressors.each do |comp|
begin
# Allow calling private compress methods
if c = send("compress_#{type}_#{comp}", content)
return c
end
rescue LoadError, CompressorNotFound
end
end
content
end
# Compress the CSS using YUI Compressor, requires java runtime
def compress_css_yui(content)
compress_yui(content, :compress_css)
end
# Compress the JS using Google Closure Compiler, requires java runtime
def compress_js_closure(content)
require 'closure-compiler'
begin
::Closure::Compiler.new.compile(content)
rescue ::Closure::Error => e
raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
end
end
# Compress the JS using MinJS, a pure ruby compressor
def compress_js_minjs(content)
require 'minjs'
Minjs::Compressor::Compressor.new(:debug => false).compress(content).to_js
end
# Compress the JS using Uglifier, requires javascript runtime
def compress_js_uglifier(content)
begin
require 'uglifier'
rescue => e
# :nocov:
raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
# :nocov:
end
Uglifier.compile(content)
end
# Compress the CSS using YUI Compressor, requires java runtime
def compress_js_yui(content)
compress_yui(content, :compress_js)
end
# Compress the CSS/JS using YUI Compressor, requires java runtime
def compress_yui(content, meth)
require 'yuicompressor'
::YUICompressor.public_send(meth, content, :munge => true)
rescue ::Errno::ENOENT => e
raise CompressorNotFound, "#{e.class}: #{e.message}", e.backtrace
end
# Return a unique id for the given content. By default, uses the
# SHA256 hash of the content. This method can be overridden to use
# a different digest type or to return a static string if you don't
# want to use a unique value.
def asset_digest(content)
algo = assets_opts[:sri] || :sha256
digest = begin
require 'openssl'
::OpenSSL::Digest
# :nocov:
rescue LoadError
require 'digest/sha2'
::Digest
# :nocov:
end
digest.const_get(algo.to_s.upcase).hexdigest(content)
end
end
module InstanceMethods
# Return an array of paths for the given asset type and optionally
# asset group. See the assets function documentation for details.
def assets_paths(type)
o = self.class.assets_opts
if type.is_a?(Array)
ltype, *dirs = type
else
ltype = type
end
stype = ltype.to_s
url_prefix = request.script_name if self.class.opts[:add_script_name]
relative_paths = o[:relative_paths]
paths = if o[:compiled]
relative_paths = false if o[:compiled_asset_host]
if ukey = _compiled_assets_hash(type, true)
["#{o[:compiled_asset_host]}#{url_prefix}/#{o[:"compiled_#{stype}_prefix"]}.#{ukey}.#{stype}"]
else
[]
end
else
asset_dir = o[ltype]
if dirs && !dirs.empty?
dirs.each{|f| asset_dir = asset_dir[f]}
prefix = "#{dirs.join('/')}/" if o[:group_subdirs]
end
Array(asset_dir).map do |f|
if ts = o[:timestamp_paths]
mtime = asset_last_modified(File.join(o[:"#{stype}_path"], *[prefix, f].compact))
mtime = "#{sprintf("%i%06i", mtime.to_i, mtime.usec)}#{ts}"
end
"#{url_prefix}/#{o[:"#{stype}_prefix"]}#{mtime}#{prefix}#{f}#{o[:"#{stype}_suffix"]}"
end
end
if relative_paths
paths.map! do |path|
"#{relative_prefix}#{path}"
end
end
paths
end
# Return a string containing html tags for the given asset type.
# This will use a script tag for the :js type and a link tag for
# the :css type.
#
# To return the tags for a specific asset group, use an array for
# the type, such as [:css, :frontend].
#
# You can specify custom attributes for the tag by passing a hash
# as the attrs argument.
#
# When the assets are not compiled, this will result in a separate
# tag for each asset file. When the assets are compiled, this will
# result in a single tag to the compiled asset file.
def assets(type, attrs = OPTS)
ltype = type.is_a?(Array) ? type[0] : type
o = self.class.assets_opts
if o[:compiled] && (algo = o[:sri]) && (hash = _compiled_assets_hash(type))
attrs = Hash[attrs]
attrs[:integrity] = "#{algo}-#{h([[hash].pack('H*')].pack('m').tr("\n", ''))}"
end
attributes = attrs.map{|k,v| "#{k}=\"#{h(v)}\""}.join(' ')
if ltype == :js
tag_start = ""
else
tag_start = ""
end
paths = assets_paths(type)
if o[:early_hints]
early_hint_as = ltype == :js ? 'script' : 'style'
early_hints = paths.map{|p| "<#{p}>; rel=preload; as=#{early_hint_as}"}
early_hints = early_hints.join("\n") if Rack.release < '3'
send_early_hints(RodaResponseHeaders::LINK=>early_hints)
end
paths.map{|p| "#{tag_start}#{h(p)}#{tag_end}"}.join("\n")
end
# Render the asset with the given filename. When assets are compiled,
# or when the file is already of the given type (no rendering necessary),
# this returns the contents of the compiled file.
# When assets are not compiled and the file is not already in the same format,
# this will render the asset using the render plugin.
# In both cases, if the file has not been modified since the last request,
# this will return a 304 response.
def render_asset(file, type)
o = self.class.assets_opts
if o[:compiled]
file = "#{o[:"compiled_#{type}_path"]}#{file}"
if o[:gzip] && env['HTTP_ACCEPT_ENCODING'] =~ /\bgzip\b/
@_response[RodaResponseHeaders::CONTENT_ENCODING] = 'gzip'
file += '.gz'
end
check_asset_request(file, type, ::File.stat(file).mtime)
::File.read(file)
else
file = "#{o[:"#{type}_path"]}#{file}"
check_asset_request(file, type, asset_last_modified(file))
read_asset_file(file, type)
end
end
# Return the content of the file if it is already of the correct type.
# Otherwise, render the file using the render plugin. +file+ should be
# the relative path to the file from the current directory.
def read_asset_file(file, type)
o = self.class.assets_opts
content = if file.end_with?(".#{type}")
::File.read(file)
else
render_asset_file(file, :template_opts=>o[:"#{type}_opts"], :dependencies=>o[:expanded_dependencies][file])
end
o[:postprocessor] ? o[:postprocessor].call(file, type, content) : content
end
private
def _compiled_assets_hash(type, return_ukey=false)
compiled = self.class.assets_opts[:compiled]
type, *dirs = type if type.is_a?(Array)
stype = type.to_s
if dirs && !dirs.empty?
key = dirs.join('.')
ckey = "#{stype}.#{key}"
if hash = ukey = compiled[ckey]
ukey = "#{key}.#{ukey}"
end
else
hash = ukey = compiled[stype]
end
return_ukey ? ukey : hash
end
# Return when the file was last modified. If the file depends on any
# other files, check the modification times of all dependencies and
# return the maximum.
def asset_last_modified(file)
if deps = self.class.assets_opts[:expanded_dependencies][file]
([file] + Array(deps)).map{|f| ::File.stat(f).mtime}.max
else
::File.stat(file).mtime
end
end
# If the asset hasn't been modified since the last request, return
# a 304 response immediately. Otherwise, add the appropriate
# type-specific headers.
def check_asset_request(file, type, mtime)
@_request.last_modified(mtime)
@_response.headers.merge!(self.class.assets_opts[:"#{type}_headers"])
end
# Render the given asset file using the render plugin, with the given options.
# +file+ should be the relative path to the file from the current directory.
def render_asset_file(file, options)
render_template({:path => file}, options)
end
end
module RequestClassMethods
# An array of asset type strings and regexps for that type, for all asset types
# handled.
def assets_matchers
@assets_matchers ||= [:css, :js].map do |t|
if regexp = assets_regexp(t)
[t, regexp].freeze
end
end.compact.freeze
end
private
# A string for the asset filename for the asset type, key, and digest.
def _asset_regexp(type, key, digest)
"#{key.sub(/\A#{type}/, '')}.#{digest}.#{type}"
end
# The regexp matcher to use for the given type. This handles any asset groups
# for the asset types.
def assets_regexp(type)
o = roda_class.assets_opts
if compiled = o[:compiled]
assets = compiled.
select{|k,_| k =~ /\A#{type}/}.
map{|k, md| _asset_regexp(type, k, md)}
return if assets.empty?
/#{o[:"compiled_#{type}_prefix"]}(#{Regexp.union(assets)})/
else
return unless assets = o[type]
assets = unnest_assets_hash(assets)
ts = o[:timestamp_paths]
/#{o[:"#{type}_prefix"]}#{"\\d+#{ts}" if ts}(#{Regexp.union(assets.uniq)})#{o[:"#{type}_suffix"]}/
end
end
# Recursively unnested the given assets hash, returning a single array of asset
# files for the given.
def unnest_assets_hash(h)
case h
when Hash
h.flat_map do |k,v|
assets = unnest_assets_hash(v)
assets = assets.map{|x| "#{k}/#{x}"} if roda_class.assets_opts[:group_subdirs]
assets
end
else
Array(h)
end
end
end
module RequestMethods
# Render the matching asset if this is a GET request for a supported asset.
def assets
if is_get?
self.class.assets_matchers.each do |type, matcher|
is matcher do |file|
scope.render_asset(file, type)
end
end
nil
end
end
end
end
register_plugin(:assets, Assets)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/assets_preloading.rb 0000664 0000000 0000000 00000006174 15167207754 0024627 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The assets_preloading plugin generates html tags or a header value
# to facilitate browser preloading of your assets. This allows
# compatible browsers to fetch assets before they are required,
# streamlining page rendering.
#
# For a list of compatible browsers, see
# http://caniuse.com/#search=link-rel-preload
#
# The plugin provides two functions - preload_assets_link_header and
# preload_assets_link_tags. The resulting preloading should be
# identical, it is up to you which system you prefer.
#
# preload_assets_link_header returns a string suitable for populating
# the response Link header:
#
# response.headers['Link'] = preload_assets_link_header(:css)
# # Link header will now contain something like:
# # ;rel="preload";as="style"
#
# preload_assets_link_tags returns a string to drop into your
# templates containing link tags:
#
# preload_assets_link_tags(:css)
# # returns
#
# Note that these link tags are different to the usual asset
# declarations in markup; this will only instruct a compatible browser
# to fetch the file and cache it for later; the browser will not parse
# the asset until it encounters a traditional link or script tag.
#
# You must still setup and link to your assets as you did previously.
#
# Both functions can be passed any combination of asset types or
# asset groups, as multiple arguments:
#
# # generate tags for css assets and the app js asset group
# preload_assets_link_tags(:css, [:js, :app], [:js, :bar])
#
# # generate Link header for css assets and js asset groups app and bar
# preload_assets_link_header(:css, [:js, :app])
#
module AssetsPreloading
TYPE_AS = {
:css => 'style'.freeze,
:js => 'script'.freeze,
}.freeze
# Depend on the assets plugin, as we'll be calling some functions in it.
def self.load_dependencies(app)
app.plugin :assets
end
module InstanceMethods
# Return a string of tags for the given asset
# types/groups.
def preload_assets_link_tags(*args)
_preload_assets_array(args).map{|path, as| ""}.join("\n")
end
# Return a string suitable for a Link header for the
# given asset types/groups.
def preload_assets_link_header(*args)
_preload_assets_array(args).map{|path, as| "<#{path}>;rel=preload;as=#{as}"}.join(",")
end
private
# Return an array of paths/as pairs for the given asset
# types and/or groups.
def _preload_assets_array(assets)
assets.flat_map do |type|
paths = assets_paths(type)
type = type[0] if type.is_a?(Array)
as = TYPE_AS[type]
paths.map{|path| [path, as]}
end
end
end
end
register_plugin(:assets_preloading, AssetsPreloading)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/assume_ssl.rb 0000664 0000000 0000000 00000001551 15167207754 0023271 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The assume_ssl plugin makes the request ssl? method always return
# true. This is useful when using an SSL-terminating reverse proxy
# that doesn't set the X-Forwarded-Proto or similar header to notify
# Rack that it is forwarding an SSL request.
#
# The sessions and sinatra_helpers plugins that ship with Roda both
# use the ssl? method internally and can be affected by use of the
# plugin. It's recommended that you use this plugin if you are
# using either plugin and an SSL-terminating proxy as described above.
#
# plugin :assume_ssl
module AssumeSSL
module RequestMethods
# Assume all requests are protected by SSL.
def ssl?
true
end
end
end
register_plugin(:assume_ssl, AssumeSSL)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/autoload_hash_branches.rb 0000664 0000000 0000000 00000006232 15167207754 0025574 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The autoload_hash_branches plugin builds on the hash_branches plugin and allows for
# delaying loading of a file containing a hash branch for an application until there
# is a request that uses the hash branch. This can be useful in development
# to improvement startup time by not loading all branches up front. It can also be
# useful in testing subsets of an application by only loading the hash branches being
# tested.
#
# You can specify a single hash branch for autoloading:
#
# plugin :autoload_hash_branches
# autoload_hash_branch('branch_name', '/absolute/path/to/file')
# autoload_hash_branch('namespace', 'branch_name', 'relative/path/to/file')
#
# You can also set the plugin to autoload load all hash branch files in a given directory.
# This will look at each .rb file in the directory, and add an autoload for it, using the
# filename without the .rb as the branch name:
#
# autoload_hash_branch_dir('/path/to/dir')
# autoload_hash_branch_dir('namespace', '/path/to/dir')
#
# In both cases, when the autoloaded file is required, it should redefine the same
# hash branch. If it does not, requests to the hash branch will result in a 404 error.
#
# When freezing an application, all hash branches are automatically loaded, because
# autoloading hash branches does not work for frozen applications.
module AutoloadHashBranches
def self.load_dependencies(app)
app.plugin :hash_branches
end
def self.configure(app)
app.opts[:autoload_hash_branch_files] ||= []
end
module ClassMethods
# Autoload the given file when there is request for the hash branch.
# The given file should configure the hash branch specified.
def autoload_hash_branch(namespace='', segment, file)
segment = "/#{segment}"
file = File.expand_path(file)
opts[:autoload_hash_branch_files] << file
routes = opts[:hash_branches][namespace] ||= {}
meth = routes[segment] = define_roda_method(routes[segment] || "hash_branch_#{namespace}_#{segment}", 1) do |r|
loc = method(routes[segment]).source_location
require file
# Avoid infinite loop in case method is not overridden
if method(meth).source_location != loc
send(meth, r)
end
end
nil
end
# For each .rb file in the given directory, add an autoloaded hash branch
# based on the file name.
def autoload_hash_branch_dir(namespace='', dir)
Dir.new(dir).entries.each do |file|
if file =~ /\.rb\z/i
autoload_hash_branch(namespace, file.sub(/\.rb\z/i, ''), File.join(dir, file))
end
end
end
# Eagerly load all hash branches when freezing the application.
def freeze
opts.delete(:autoload_hash_branch_files).each{|file| require file} unless opts.frozen?
super
end
end
end
register_plugin(:autoload_hash_branches, AutoloadHashBranches)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/autoload_named_routes.rb 0000664 0000000 0000000 00000005112 15167207754 0025465 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The autoload_named_routes plugin builds on the named_routes plugin and allows for
# delaying loading of a file containing a named route for an application until there
# is a request that uses the named route. This can be useful in development
# to improvement startup time by not loading all named routes up front. It can also be
# useful in testing subsets of an application by only loading the named routes being
# tested.
#
# You can specify a single hash branch for autoloading:
#
# plugin :autoload_named_route
# autoload_named_route(:route_name, '/absolute/path/to/file')
# autoload_named_route(:namespace, :route_name, 'relative/path/to/file')
#
# Note that unlike the +route+ method defined by the named_routes plugin, when providing
# a namespace, the namespace comes before the route name and not after.
#
# When the autoloaded file is required, it should redefine the same
# named route. If it does not, requests to the named route will be ignored (as if the
# related named route block was empty).
#
# When freezing an application, all named routes are automatically loaded, because
# autoloading named routes does not work for frozen applications.
module AutoloadNamedRoutes
def self.load_dependencies(app)
app.plugin :named_routes
end
def self.configure(app)
app.opts[:autoload_named_route_files] ||= []
end
module ClassMethods
# Autoload the given file when there is request for the named route.
# The given file should configure the named route specified.
def autoload_named_route(namespace=nil, name, file)
file = File.expand_path(file)
opts[:autoload_named_route_files] << file
routes = opts[:namespaced_routes][namespace] ||= {}
meth = routes[name] = define_roda_method(routes[name] || "named_routes_#{namespace}_#{name}", 1) do |r|
loc = method(routes[name]).source_location
require file
# Avoid infinite loop in case method is not overridden
if method(meth).source_location != loc
send(meth, r)
end
end
nil
end
# Eagerly load all autoloaded named routes when freezing the application.
def freeze
opts.delete(:autoload_named_route_files).each{|file| require file} unless opts.frozen?
super
end
end
end
register_plugin(:autoload_named_routes, AutoloadNamedRoutes)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/backtracking_array.rb 0000664 0000000 0000000 00000005350 15167207754 0024735 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The backtracking_array plugin changes the handling of array
# matchers such that if one of the array entries matches, but
# a later match argument fails, it will backtrack and try the
# next entry in the array. For example, the following match
# block does not match +/a/b+ by default:
#
# r.is ['a', 'a/b'] do |path|
# # ...
# end
#
# This is because the 'a' entry in the array matches, which
# makes the array match. However, the next matcher is the
# terminal matcher (since +r.is+ was used), and since the
# path is not terminal as it still contains +/b+ after
# matching 'a'.
#
# With the backtracking_array plugin, when the terminal matcher
# fails, matching will go on to the next entry in the array,
# 'a/b', which will also match. Since 'a/b'
# matches the path fully, the terminal matcher also matches,
# and the match block yields.
module BacktrackingArray
module RequestMethods
private
# When matching for a single array, after a successful
# array element match, attempt to match all remaining
# elements. If the remaining elements could not be
# matched, reset the state and continue to the next
# entry in the array.
def _match_array(arg, rest)
path = @remaining_path
captures = @captures
caps = captures.dup
arg.each do |v|
if match(v, rest)
if v.is_a?(String)
captures.push(v)
end
if match_all(rest)
return true
end
# Matching all remaining elements failed, reset state
captures.replace(caps)
@remaining_path = path
end
end
false
end
# If any of the args are an array, handle backtracking such
# that if a later matcher fails, we roll back to the current
# matcher and proceed to the next entry in the array.
def match_all(args)
args = args.dup
until args.empty?
arg = args.shift
if match(arg, args)
return true if arg.is_a?(Array)
else
return
end
end
true
end
# When matching an array, include the remaining arguments,
# otherwise, just match the single argument.
def match(v, rest = nil)
if v.is_a?(Array)
_match_array(v, rest)
else
super(v)
end
end
end
end
register_plugin(:backtracking_array, BacktrackingArray)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/bearer_token.rb 0000664 0000000 0000000 00000001506 15167207754 0023553 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The bearer_token plugin adds an +r.bearer_token+ method for retrieving
# a bearer token from the +Authorization+ HTTP header. Bearer tokens will
# in the authorization header will be recognized as long as they start
# with the case insensitive string "bearer ".
module BearerToken
# :nocov:
METHOD = RUBY_VERSION >= "2.4" ? :match? : :match
# :nocov:
module RequestMethods
# Return the bearer token for the request if there is one in the
# authorization HTTP header.
def bearer_token
if (auth = @env["HTTP_AUTHORIZATION"]) && auth.send(METHOD, /\Abearer /i)
auth[7, 100000000]
end
end
end
end
register_plugin(:bearer_token, BearerToken)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/branch_locals.rb 0000664 0000000 0000000 00000004161 15167207754 0023705 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The branch_locals plugin allows you to override view and layout
# locals for specific branches and routes.
#
# plugin :render
# plugin :render_locals, render: {footer: 'Default'}, layout: {title: 'Main'}
# plugin :branch_locals
#
# route do |r|
# r.on "users" do
# set_layout_locals title: 'Users'
# set_view_locals footer: '(c) Roda'
# end
# end
#
# The locals you specify in the set_layout_locals and set_view_locals methods
# have higher precedence than the render_locals plugin options, but lower precedence
# than options you directly pass to the view/render methods.
module BranchLocals
# Load the render_locals plugin before this plugin, since this plugin
# works by overriding methods in the render_locals plugin.
def self.load_dependencies(app)
app.plugin :render_locals
end
module InstanceMethods
# Update the default layout locals to use in this branch.
def set_layout_locals(opts)
if locals = @_layout_locals
@_layout_locals = locals.merge(opts)
else
@_layout_locals = opts
end
end
# Update the default view locals to use in this branch.
def set_view_locals(opts)
if locals = @_view_locals
@_view_locals = locals.merge(opts)
else
@_view_locals = opts
end
end
private
# Make branch specific view locals override render_locals plugin defaults.
def render_locals
locals = super
if @_view_locals
locals = Hash[locals].merge!(@_view_locals)
end
locals
end
# Make branch specific layout locals override render_locals plugin defaults.
def layout_locals
locals = super
if @_layout_locals
locals = Hash[locals].merge!(@_layout_locals)
end
locals
end
end
end
register_plugin(:branch_locals, BranchLocals)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/break.rb 0000664 0000000 0000000 00000002030 15167207754 0022170 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The break plugin supports calling break inside a match block, to
# return from the block and continue in the routing tree, restoring
# the remaining path so that future matchers operating on the path
# operate as expected.
#
# plugin :break
#
# route do |r|
# r.on "foo", :bar do |bar|
# break if bar == 'baz'
# "/foo/#{bar} (not baz)"
# end
#
# r.on "foo/baz" do
# "/foo/baz"
# end
# end
#
# This provides the same basic feature as the pass plugin, but
# uses Ruby's standard control flow primative instead of a
# separate method.
module Break
module RequestMethods
private
# Handle break inside match blocks, restoring remaining path.
def if_match(_)
rp = @remaining_path
super
ensure
@remaining_path = rp
end
end
end
register_plugin(:break, Break)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/caching.rb 0000664 0000000 0000000 00000017503 15167207754 0022513 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'time'
#
class Roda
module RodaPlugins
# The caching plugin adds methods related to HTTP caching.
#
# For proper caching, you should use either the +last_modified+ or
# +etag+ request methods.
#
# r.get 'albums', Integer do |album_id|
# @album = Album[album_id]
# r.last_modified @album.updated_at
# view('album')
# end
#
# # or
#
# r.get 'albums', Integer do |album_id|
# @album = Album[album_id]
# r.etag @album.sha1
# view('album')
# end
#
# Both +last_modified+ or +etag+ will immediately halt processing
# if there have been no modifications since the last time the
# client requested the resource, assuming the client uses the
# appropriate HTTP 1.1 request headers.
#
# This plugin also includes the +cache_control+ and +expires+
# response methods. The +cache_control+ method sets the
# Cache-Control header using the given hash:
#
# response.cache_control public: true, max_age: 60
# # Cache-Control: public, max-age=60
#
# The +expires+ method is similar, but in addition
# to setting the HTTP 1.1 Cache-Control header, it also sets
# the HTTP 1.0 Expires header:
#
# response.expires 60, public: true
# # Cache-Control: public, max-age=60
# # Expires: Mon, 29 Sep 2014 21:25:47 GMT
#
# The implementation was originally taken from Sinatra,
# which is also released under the MIT License:
#
# Copyright (c) 2007, 2008, 2009 Blake Mizerany
# Copyright (c) 2010, 2011, 2012, 2013, 2014 Konstantin Haase
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
# files (the "Software"), to deal in the Software without
# restriction, including without limitation the rights to use,
# copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the
# Software is furnished to do so, subject to the following
# conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
# OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
# HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
# OTHER DEALINGS IN THE SOFTWARE.
module Caching
module RequestMethods
# Set the last modified time of the resource using the Last-Modified header.
# The +time+ argument should be a Time instance.
#
# If the current request includes an If-Modified-Since header that is
# equal or later than the time specified, immediately returns a response
# with a 304 status.
#
# If the current request includes an If-Unmodified-Since header that is
# before than the time specified, immediately returns a response
# with a 412 status.
def last_modified(time)
return unless time
res = response
e = env
res[RodaResponseHeaders::LAST_MODIFIED] = time.httpdate
return if e['HTTP_IF_NONE_MATCH']
status = res.status
if (!status || status == 200) && (ims = time_from_header(e['HTTP_IF_MODIFIED_SINCE'])) && ims >= time.to_i
res.status = 304
halt
end
if (!status || (status >= 200 && status < 300) || status == 412) && (ius = time_from_header(e['HTTP_IF_UNMODIFIED_SINCE'])) && ius < time.to_i
res.status = 412
halt
end
end
# Set the response entity tag using the ETag header.
#
# The +value+ argument is an identifier that uniquely
# identifies the current version of the resource.
# Options:
# :weak :: Use a weak cache validator (a strong cache validator is the default)
# :new_resource :: Whether this etag should match an etag of * (true for POST, false otherwise)
#
# When the current request includes an If-None-Match header with a
# matching etag, immediately returns a response with a 304 or 412 status,
# depending on the request method.
#
# When the current request includes an If-Match header with a
# etag that doesn't match, immediately returns a response with a 412 status.
def etag(value, opts=OPTS)
# Before touching this code, please double check RFC 2616 14.24 and 14.26.
weak = opts[:weak]
new_resource = opts.fetch(:new_resource){post?}
res = response
e = env
res[RodaResponseHeaders::ETAG] = etag = "#{'W/' if weak}\"#{value}\""
status = res.status
if (!status || (status >= 200 && status < 300) || status == 304)
if etag_matches?(e['HTTP_IF_NONE_MATCH'], etag, new_resource)
res.status = (request_method =~ /\AGET|HEAD|OPTIONS|TRACE\z/i ? 304 : 412)
halt
end
if ifm = e['HTTP_IF_MATCH']
unless etag_matches?(ifm, etag, new_resource)
res.status = 412
halt
end
end
end
end
private
# Helper method checking if a ETag value list includes the current ETag.
def etag_matches?(list, etag, new_resource)
return unless list
return !new_resource if list == '*'
list.to_s.split(/\s*,\s*/).include?(etag)
end
# Helper method parsing a time value from an HTTP header, returning the
# time as an integer.
def time_from_header(t)
Time.httpdate(t).to_i if t
rescue ArgumentError
end
end
module ResponseMethods
# Specify response freshness policy for using the Cache-Control header.
# Options can can any non-value directives (:public, :private, :no_cache,
# :no_store, :must_revalidate, :proxy_revalidate), with true as the value.
# Options can also contain value directives (:max_age, :s_maxage).
#
# response.cache_control public: true, max_age: 60
# # => Cache-Control: public, max-age=60
#
# See RFC 2616 / 14.9 for more on standard cache control directives:
# http://tools.ietf.org/html/rfc2616#section-14.9.1
def cache_control(opts)
values = []
opts.each do |k, v|
next unless v
k = k.to_s.tr('_', '-')
values << (v == true ? k : "#{k}=#{v}")
end
@headers[RodaResponseHeaders::CACHE_CONTROL] = values.join(', ') unless values.empty?
end
# Set Cache-Control header with the max_age given. max_age should
# be an integer number of seconds that the current request should be
# cached for. Also sets the Expires header, useful if you have
# HTTP 1.0 clients (Cache-Control is an HTTP 1.1 header).
def expires(max_age, opts=OPTS)
cache_control(Hash[opts].merge!(:max_age=>max_age))
@headers[RodaResponseHeaders::EXPIRES] = (Time.now + max_age).httpdate
end
# Remove Content-Type and Content-Length for 304 responses.
def finish
a = super
if a[0] == 304
h = a[1]
h.delete(RodaResponseHeaders::CONTENT_TYPE)
h.delete(RodaResponseHeaders::CONTENT_LENGTH)
end
a
end
end
end
register_plugin(:caching, Caching)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/capture_erb.rb 0000664 0000000 0000000 00000007063 15167207754 0023412 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The capture_erb plugin allows you to capture the content of a block
# in an ERB template, and return it as a value, instead of
# injecting the template block into the template output.
#
# <% value = capture_erb do %>
# Some content here.
# <% end %>
#
# +capture_erb+ can be used inside other methods that are called
# inside templates. It can be combined with the inject_erb plugin
# to wrap template blocks with arbitrary output and then inject the
# wrapped output into the template.
#
# If the output buffer object responds to +capture+ and is not
# an instance of String (e.g. when +erubi/capture_block+ is being
# used as the template engine), this will call +capture+ on the
# output buffer object, instead of setting the output buffer object
# temporarily to a new object.
#
# By default, capture_erb returns the value of the block, converted
# to a string. However, that can cause issues with code such as:
#
# <% value = capture_erb do %>
# Some content here.
# <% if something %>
# Some more content here.
# <% end %>
# <% end %>
#
# In this case, the block may return nil, instead of the content of
# the template. To handle this case, you can provide the
# returns: :buffer option when calling the method (to handle
# that specific call, or when loading the plugin (to default to that
# behavior). Note that if the output buffer object responds to
# +capture+ and is not an instance of String, the returns: :buffer
# behavior is the default and cannot be changed.
module CaptureERB
def self.load_dependencies(app, opts=OPTS)
app.plugin :render
end
# Support returns: :buffer to default to returning buffer
# object.
def self.configure(app, opts=OPTS)
# RODA4: make returns: :buffer the default behavior
app.opts[:capture_erb_returns] = opts[:returns] if opts.has_key?(:returns)
end
module InstanceMethods
# Temporarily replace the ERB output buffer
# with an empty string, and then yield to the block.
# Return the value of the block, converted to a string.
# Restore the previous ERB output buffer before returning.
#
# Options:
# :returns :: If set to :buffer, returns the value of the
# template output variable, instead of the return
# value of the block converted to a string. This
# is the default behavior if the template output
# variable supports the +capture+ method and is not
# a String instance.
def capture_erb(opts=OPTS, &block)
outvar = render_opts[:template_opts][:outvar]
buf_was = instance_variable_get(outvar)
if buf_was.respond_to?(:capture) && !buf_was.instance_of?(String)
buf_was.capture(&block)
else
returns = opts.fetch(:returns) { self.opts[:capture_erb_returns] }
begin
instance_variable_set(outvar, String.new)
if returns == :buffer
yield
instance_variable_get(outvar).to_s
else
yield.to_s
end
ensure
instance_variable_set(outvar, buf_was) if outvar && buf_was
end
end
end
end
end
register_plugin(:capture_erb, CaptureERB)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/chunked.rb 0000664 0000000 0000000 00000026741 15167207754 0022544 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The chunked plugin allows you to stream rendered views to clients.
# This can significantly improve performance of page rendering on the
# client, as it flushes the headers and top part of the layout template
# (generally containing references to the stylesheet and javascript assets)
# before rendering the content template.
#
# This allows the client to fetch the assets while the template is still
# being rendered. Additionally, this plugin makes it easy to defer
# executing code required to render the content template until after
# the top part of the layout has been flushed, so the client can fetch the
# assets while the application is still doing the necessary processing in
# order to render the content template, such as retrieving values from a
# database.
#
# There are a couple disadvantages of streaming. First is that the layout
# must be rendered before the content, so any state changes made in your
# content template will not affect the layout template. Second, error
# handling is reduced, since if an error occurs while rendering a template,
# a successful response code has already been sent.
#
# To use chunked encoding for a response, just call the chunked method
# instead of view:
#
# r.root do
# chunked(:index)
# end
#
# If you want to execute code after flushing the top part of the layout
# template, but before rendering the content template, pass a block to
# chunked:
#
# r.root do
# chunked(:index) do
# # expensive calculation here
# end
# end
#
# You can also call delay manually with a block, and the execution of the
# block will be delayed until rendering the content template. This is
# useful if you want to delay execution for all routes under a branch:
#
# r.on 'albums', Integer do |album_id|
# delay do
# @album = Album[album_id]
# end
# r.get 'info' do
# chunked(:info)
# end
# r.get 'tracks' do
# chunked(:tracks)
# end
# end
#
# If you want to chunk all responses, pass the :chunk_by_default option
# when loading the plugin:
#
# plugin :chunked, chunk_by_default: true
#
# then you can just use the normal view method:
#
# r.root do
# view(:index)
# end
#
# and it will chunk the response. Note that you still need to call
# chunked if you want to pass a block of code to be executed after flushing
# the layout and before rendering the content template. Also, before you
# enable chunking by default, you need to make sure that none of your
# content templates make state changes that affect the layout template.
# Additionally, make sure nowhere in your app are you doing any processing
# after the call to view.
#
# If you use :chunk_by_default, but want to turn off chunking for a view,
# call no_chunk!:
#
# r.root do
# no_chunk!
# view(:index)
# end
#
# Inside your layout or content templates, you can call the flush method
# to flush the current result of the template to the user, useful for
# streaming large datasets.
#
# <% (1..100).each do |i| %>
# <%= i %>
# <% sleep 0.1 %>
# <% flush %>
# <% end %>
#
# Note that you should not call flush from inside subtemplates of the
# content or layout templates, unless you are also calling flush directly
# before rendering the subtemplate, and also directly injecting the
# subtemplate into the current template without modification. So if you
# are using the above template code in a subtemplate, in your content
# template you should do:
#
# <% flush %><%= render(:subtemplate) %>
#
# If you want to use chunked encoding when rendering a template, but don't
# want to use a layout, pass the layout: false option to chunked.
#
# r.root do
# chunked(:index, layout: false)
# end
#
# In order to handle errors in chunked responses, you can override the
# handle_chunk_error method:
#
# def handle_chunk_error(e)
# env['rack.logger'].error(e)
# end
#
# It is possible to set @_out_buf to an error notification and call
# flush to output the message to the client inside handle_chunk_error.
#
# In order for chunking to work, you must make sure that no proxies between
# the application and the client buffer responses.
#
# If you are using nginx and have it set to buffer proxy responses by
# default, you can turn this off on a per response basis using the
# X-Accel-Buffering header. To set this header or similar headers for
# all chunked responses, pass a :headers option when loading the plugin:
#
# plugin :chunked, headers: {'X-Accel-Buffering'=>'no'}
#
# By default, this plugin does not use Transfer-Encoding: chunked, it only
# returns a body that will stream the response in chunks. If you would like
# to force the use of Transfer-Encoding: chunked, you can use the
# :force_chunked_encoding plugin option. If using the
# :force_chunked_encoding plugin option, chunking will only be used for
# HTTP/1.1 requests since Transfer-Encoding: chunked is only supported
# in HTTP/1.1 (non-HTTP/1.1 requests will have behavior similar to
# calling no_chunk!).
#
# The chunked plugin requires the render plugin, and only works for
# template engines that store their template output variable in
# @_out_buf. Also, it only works if the content template is directly
# injected into the layout template without modification.
#
# If using the chunked plugin with the flash plugin, make sure you
# call the flash method early in your route block. If the flash
# method is not called until template rendering, the flash may not be
# rotated.
module Chunked
# Depend on the render plugin
def self.load_dependencies(app, opts=OPTS)
app.plugin :render
end
# Set plugin specific options. Options:
# :chunk_by_default :: chunk all calls to view by default
# :headers :: Set default additional headers to use when calling view
def self.configure(app, opts=OPTS)
app.opts[:chunk_by_default] = opts[:chunk_by_default]
app.opts[:force_chunked_encoding] = opts[:force_chunked_encoding]
if opts[:headers]
app.opts[:chunk_headers] = (app.opts[:chunk_headers] || {}).merge(opts[:headers]).freeze
end
end
# Rack response body instance for chunked responses using
# Transfer-Encoding: chunked.
class Body
# Save the scope of the current request handling.
def initialize(scope)
@scope = scope
end
# For each response chunk yielded by the scope,
# yield it it to the caller in chunked format, starting
# with the size of the request in ASCII hex format, then
# the chunk. After all chunks have been yielded, yield
# a 0 sized chunk to finish the response.
def each
@scope.each_chunk do |chunk|
next if !chunk || chunk.empty?
yield("%x\r\n" % chunk.bytesize)
yield(chunk)
yield("\r\n")
end
ensure
yield("0\r\n\r\n")
end
end
# Rack response body instance for chunked responses not
# using Transfer-Encoding: chunked.
class StreamBody
# Save the scope of the current request handling.
def initialize(scope)
@scope = scope
end
# Yield each non-empty chunk as the body.
def each(&block)
@scope.each_chunk do |chunk|
yield chunk if chunk && !chunk.empty?
end
end
end
module InstanceMethods
# Disable chunking for the current request. Mostly useful when
# chunking is turned on by default.
def no_chunk!
@_chunked = false
end
# If chunking by default, call chunked if it hasn't yet been
# called and chunking is not specifically disabled.
def view(*a)
if opts[:chunk_by_default] && !defined?(@_chunked) && !defined?(yield)
chunked(*a)
else
super
end
end
# Render a response to the user in chunks. See Chunked for
# an overview. If a block is given, it is passed to #delay.
def chunked(template, opts=OPTS, &block)
unless defined?(@_chunked)
@_chunked = !self.opts[:force_chunked_encoding] || @_request.http_version == "HTTP/1.1"
end
if block
delay(&block)
end
unless @_chunked
# If chunking is disabled, do a normal rendering of the view.
run_delayed_blocks
return view(template, opts)
end
if template.is_a?(Hash)
if opts.empty?
opts = template
else
opts = Hash[opts].merge!(template)
end
end
# Hack so that the arguments don't need to be passed
# through the response and body objects.
@_each_chunk_args = [template, opts]
res = response
headers = res.headers
if chunk_headers = self.opts[:chunk_headers]
headers.merge!(chunk_headers)
end
if self.opts[:force_chunked_encoding]
res[RodaResponseHeaders::TRANSFER_ENCODING] = 'chunked'
body = Body.new(self)
else
body = StreamBody.new(self)
end
throw :halt, res.finish_with_body(body)
end
# Delay the execution of the block until right before the
# content template is to be rendered.
def delay(&block)
raise RodaError, "must pass a block to Roda#delay" unless block
(@_delays ||= []) << block
end
# Yield each chunk of the template rendering separately.
def each_chunk
response.body.each{|s| yield s}
template, opts = @_each_chunk_args
# Use a lambda for the flusher, so that a call to flush
# by a template can result in this method yielding a chunk
# of the response.
@_flusher = lambda do
yield @_out_buf
@_out_buf = String.new
end
if layout_opts = view_layout_opts(opts)
@_out_buf = render_template(layout_opts) do
flush
run_delayed_blocks
yield opts[:content] || render_template(template, opts)
nil
end
else
run_delayed_blocks
yield view(template, opts)
end
flush
rescue => e
handle_chunk_error(e)
end
# By default, raise the exception.
def handle_chunk_error(e)
raise e
end
# Call the flusher if one is defined. If one is not defined, this
# is a no-op, so flush can be used inside views without breaking
# things if chunking is not used.
def flush
@_flusher.call if @_flusher
end
private
# Run all delayed blocks
def run_delayed_blocks
return unless @_delays
@_delays.each(&:call)
end
end
end
register_plugin(:chunked, Chunked)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/class_level_routing.rb 0000664 0000000 0000000 00000010074 15167207754 0025156 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The class_level_routing plugin adds routing methods at the class level, which can
# be used instead of or in addition to using the normal +route+ method to start the
# routing tree. If a request is not matched by the normal routing tree, the class
# level routes will be tried. This offers a more Sinatra-like API, while
# still allowing you to use a routing tree inside individual actions.
#
# Here's the first example from the README, modified to use the class_level_routing
# plugin:
#
# class App < Roda
# plugin :class_level_routing
#
# # GET / request
# root do
# request.redirect "/hello"
# end
#
# # GET /hello/world request
# get "hello/world" do
# "Hello world!"
# end
#
# # /hello request
# is "hello" do
# # Set variable for both GET and POST requests
# @greeting = 'Hello'
#
# # GET /hello request
# request.get do
# "#{@greeting}!"
# end
#
# # POST /hello request
# request.post do
# puts "Someone said #{@greeting}!"
# request.redirect
# end
# end
# end
#
# When using the class_level_routing plugin with nested routes, you may also want to use the
# delegate plugin to delegate certain instance methods to the request object, so you don't have
# to continually use +request.+ in your routing blocks.
#
# Note that class level routing is implemented via a simple array of routes, so routing performance
# will degrade linearly as the number of routes increases. For best performance, you should use
# the normal +route+ class method to define your routing tree. This plugin does make it simpler to
# add additional routes after the routing tree has already been defined, though.
module ClassLevelRouting
# Initialize the class_routes array when the plugin is loaded. Also, if the application doesn't
# currently have a routing block, setup an empty routing block so that things will still work if
# a routing block isn't added.
def self.configure(app)
app.opts[:class_level_routes] ||= []
end
module ClassMethods
# Define routing methods that will store class level routes.
[:root, :on, :is, :get, :post, :delete, :head, :options, :link, :patch, :put, :trace, :unlink].each do |request_meth|
define_method(request_meth) do |*args, &block|
meth = define_roda_method("class_level_routing_#{request_meth}", :any, &block)
opts[:class_level_routes] << [request_meth, args, meth].freeze
end
end
# Freeze the class level routes so that there can be no thread safety issues at runtime.
def freeze
opts[:class_level_routes].freeze
super
end
end
module InstanceMethods
def initialize(_)
super
@_original_remaining_path = @_request.remaining_path
end
private
# If the normal routing tree doesn't handle an action, try each class level route
# to see if it matches.
def _roda_after_10__class_level_routing(result)
if result && result[0] == 404 && (v = result[2]).is_a?(Array) && v.empty?
# Reset the response so it doesn't inherit the status or any headers from
# the original response.
@_response.send(:initialize)
@_response.status = nil
result.replace(_roda_handle_route do
r = @_request
opts[:class_level_routes].each do |request_meth, args, meth|
r.instance_variable_set(:@remaining_path, @_original_remaining_path)
r.public_send(request_meth, *args) do |*a|
send(meth, *a)
end
end
nil
end)
end
end
end
end
register_plugin(:class_level_routing, ClassLevelRouting)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/class_matchers.rb 0000664 0000000 0000000 00000011711 15167207754 0024105 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The class_matchers plugin allows you do define custom regexps and
# conversion procs to use for specific classes. For example, if you
# have multiple routes similar to:
#
# r.on /(\d\d\d\d)-(\d\d)-(\d\d)/ do |y, m, d|
# date = Date.new(y.to_i, m.to_i, d.to_i)
# # ...
# end
#
# You can register a Date class matcher for that regexp:
#
# class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
# Date.new(y.to_i, m.to_i, d.to_i)
# end
#
# And then use the Date class as a matcher, and it will yield a Date object:
#
# r.on Date do |date|
# # ...
# end
#
# This is useful to DRY up code if you are using the same type of pattern and
# type conversion in multiple places in your application. You can have the
# block return an array to yield multiple captures.
#
# If you have a segment match the passed regexp, but decide during block
# processing that you do not want to treat it as a match, you can have the
# block return nil or false. This is useful if you want to make sure you
# are using valid data:
#
# class_matcher(Date, /(\d\d\d\d)-(\d\d)-(\d\d)/) do |y, m, d|
# y = y.to_i
# m = m.to_i
# d = d.to_i
# Date.new(y, m, d) if Date.valid_date?(y, m, d)
# end
#
# The second argument to class_matcher can be a class already registered
# as a class matcher. This can DRY up code that wants a conversion
# performed by an existing class matcher:
#
# class_matcher Employee, Integer do |id|
# Employee[id]
# end
#
# With the above example, the Integer matcher performs the conversion to
# integer, so +id+ is yielded as an integer. The block then looks up the
# employee with that id. If there is no employee with that id, then
# the Employee matcher will not match.
#
# If using the symbol_matchers plugin, you can provide a recognized symbol
# matcher as the second argument to class_matcher, and it will work in
# a similar manner:
#
# symbol_matcher(:employee_id, /E-(\d{6})/) do |employee_id|
# employee_id.to_i
# end
# class_matcher Employee, :employee_id do |id|
# Employee[id]
# end
#
# Blocks passed to the class_matchers plugin are evaluated in route
# block context.
#
# This plugin does not work with the params_capturing plugin, as it does not
# offer the ability to associate block arguments with named keys.
module ClassMatchers
def self.load_dependencies(app)
app.plugin :_symbol_class_matchers
end
def self.configure(app)
app.opts[:class_matchers] ||= {
Integer=>[/(\d{1,100})/, /\A\/(\d{1,100})(?=\/|\z)/, :_convert_class_Integer].freeze,
String=>[/([^\/]+)/, nil, nil].freeze
}
end
module ClassMethods
# Set the matcher and block to use for the given class.
# The matcher can be a regexp, registered class matcher, or registered symbol
# matcher (if using the symbol_matchers plugin).
#
# If providing a regexp, the block given will be called with all regexp captures.
# If providing a registered class or symbol, the block will be called with the
# captures returned by the block for the registered class or symbol, or the regexp
# captures if no block was registered with the class or symbol. In either case,
# if a block is given, it should return an array with the captures to yield to
# the match block.
def class_matcher(klass, matcher, &block)
_symbol_class_matcher(Class, klass, matcher, block) do |meth, (_, regexp, convert_meth)|
if regexp
define_method(meth){consume(regexp, convert_meth)}
else
define_method(meth){_consume_segment(convert_meth)}
end
end
end
# Freeze the class_matchers hash when freezing the app.
def freeze
opts[:class_matchers].freeze
super
end
end
module RequestMethods
# Use faster approach for segment matching. This is used for
# matchers based on the String class matcher, and avoids the
# use of regular expressions for scanning.
def _consume_segment(convert_meth)
rp = @remaining_path
if _match_class_String
if convert_meth
if captures = scope.send(convert_meth, @captures.pop)
if captures.is_a?(Array)
@captures.concat(captures)
else
@captures << captures
end
else
@remaining_path = rp
nil
end
else
true
end
end
end
end
end
register_plugin(:class_matchers, ClassMatchers)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/common_logger.rb 0000664 0000000 0000000 00000005522 15167207754 0023744 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The common_logger plugin adds common logger support to Roda
# applications, similar to Rack::CommonLogger, with the following
# differences:
#
# * Better performance
# * Doesn't include middleware timing
# * Doesn't proxy the body
# * Doesn't support different capitalization of the Content-Length response header
# * Logs to +$stderr+ instead of env['rack.errors'] if explicit logger not passed
#
# Example:
#
# plugin :common_logger
# plugin :common_logger, $stdout
# plugin :common_logger, Logger.new('filename')
# plugin :common_logger, Logger.new('filename'), method: :debug
module CommonLogger
MUTATE_LINE = RUBY_VERSION < '2.3' || RUBY_VERSION >= '3'
private_constant :MUTATE_LINE
def self.configure(app, logger=nil, opts=OPTS)
app.opts[:common_logger] = logger || app.opts[:common_logger] || $stderr
app.opts[:common_logger_meth] = app.opts[:common_logger].method(opts.fetch(:method){logger.respond_to?(:write) ? :write : :<<})
end
if RUBY_VERSION >= '2.1'
# A timer object for calculating elapsed time.
def self.start_timer
Process.clock_gettime(Process::CLOCK_MONOTONIC)
end
else
# :nocov:
def self.start_timer # :nodoc:
Time.now
end
# :nocov:
end
module InstanceMethods
private
# Log request/response information in common log format to logger.
def _roda_after_90__common_logger(result)
return unless result && (status = result[0]) && (headers = result[1])
elapsed_time = if timer = @_request_timer
'%0.4f' % (CommonLogger.start_timer - timer)
else
'-'
end
env = @_request.env
line = "#{env['HTTP_X_FORWARDED_FOR'] || env["REMOTE_ADDR"] || "-"} - #{env["REMOTE_USER"] || "-"} [#{Time.now.strftime("%d/%b/%Y:%H:%M:%S %z")}] \"#{env["REQUEST_METHOD"]} #{env["SCRIPT_NAME"]}#{env["PATH_INFO"]}#{"?#{env["QUERY_STRING"]}" if ((qs = env["QUERY_STRING"]) && !qs.empty?)} #{@_request.http_version}\" #{status} #{((length = headers[RodaResponseHeaders::CONTENT_LENGTH]) && (length unless length == '0')) || '-'} #{elapsed_time} "
if MUTATE_LINE
line.gsub!(/[^[:print:]]/){|c| sprintf("\\x%x", c.ord)}
# :nocov:
else
line = line.gsub(/[^[:print:]]/){|c| sprintf("\\x%x", c.ord)}
# :nocov:
end
line[-1] = "\n"
opts[:common_logger_meth].call(line)
end
# Create timer instance used for timing
def _roda_before_05__common_logger
@_request_timer = CommonLogger.start_timer
end
end
end
register_plugin(:common_logger, CommonLogger)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/conditional_sessions.rb 0000664 0000000 0000000 00000004572 15167207754 0025352 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
class Roda
module RodaPlugins
# The conditional_sessions plugin loads the sessions plugin. However,
# it only allows sessions if the block passed to the plugin returns
# truthy. The block is evaluated in request context. This is designed for
# use in applications that want to use sessions for some requests,
# and want to be sure that sessions are not used for other requests.
# For example, if you want to make sure that sessions are not used for
# requests with paths starting with /static, you could do:
#
# plugin :conditional_sessions, secret: ENV["SECRET"] do
# !path_info.start_with?('/static')
# end
#
# The the request session, session_created_at, and session_updated_at methods
# raise a RodaError exception when sessions are not allowed. The request
# persist_session and route scope clear_session methods do nothing when
# sessions are not allowed.
module ConditionalSessions
# Pass all options to the sessions block, and use the block to define
# a request method for whether sessions are allowed.
def self.load_dependencies(app, opts=OPTS, &block)
app.plugin :sessions, opts
app::RodaRequest.class_eval do
define_method(:use_sessions?, &block)
alias use_sessions? use_sessions?
end
end
module InstanceMethods
# Do nothing if not using sessions.
def clear_session
super if @_request.use_sessions?
end
end
module RequestMethods
# Raise RodaError if not using sessions.
def session
raise RodaError, "session called on request not using sessions" unless use_sessions?
super
end
# Raise RodaError if not using sessions.
def session_created_at
raise RodaError, "session_created_at called on request not using sessions" unless use_sessions?
super
end
# Raise RodaError if not using sessions.
def session_updated_at
raise RodaError, "session_updated_at called on request not using sessions" unless use_sessions?
super
end
# Do nothing if not using sessions.
def persist_session(headers, session)
super if use_sessions?
end
end
end
register_plugin(:conditional_sessions, ConditionalSessions)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/content_for.rb 0000664 0000000 0000000 00000006401 15167207754 0023432 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The content_for plugin is designed to be used with the
# render plugin, allowing you to store content inside one
# template, and retrieve that content inside a separate
# template. Most commonly, this is so view templates
# can set content for the layout template to display outside
# of the normal content pane.
#
# In the template in which you want to store content, call
# content_for with a block:
#
# <% content_for :foo do %>
# Some content here.
# <% end %>
#
# or:
#
# <% content_for :foo do "Some content here." end %>
#
# You can also set the raw content as the second argument,
# instead of passing a block:
#
# <% content_for :foo, "Some content" %>
#
# In the template in which you want to retrieve content,
# call content_for without the block or argument:
#
# <%= content_for :foo %>
#
# Note that when storing content by calling content_for
# with a block and embedding template code, the return
# value of the block is used as the content (after being
# converted to a string). This can cause issues in some
# cases, such as:
#
# <% content_for :foo do %>
# <% [1,2,3].each do |i| %>
# Content <%= i %>
# <% end %>
# <% end %>
#
# In the above example, the return value of the block is
# [1,2,3], as Array#each returns the receiver.
# If whitespace is not important, you can work around this by
# adding an empty line before the end of the content_for block.
#
# If content_for is used multiple times with the same key,
# by default, the last call will append previous calls.
# If you want to overwrite the previous content, pass the
# append: false option when loading the plugin:
#
# plugin :content_for, append: false
module ContentFor
# Depend on the capture_erb plugin, since it uses capture_erb
# to capture the content.
def self.load_dependencies(app, _opts = OPTS)
app.plugin :capture_erb
end
# Configure whether to append or overwrite if content_for
# is called multiple times with the same key.
def self.configure(app, opts = OPTS)
app.opts[:append_content_for] = opts.fetch(:append, true)
end
module InstanceMethods
# If called with a block, store content enclosed by block
# under the given key. If called without a block, retrieve
# stored content with the given key, or return nil if there
# is no content stored with that key.
def content_for(key, value=nil, &block)
append = opts[:append_content_for]
if block || value
if block
value = capture_erb(&block)
end
@_content_for ||= {}
if append
(@_content_for[key] ||= []) << value
else
@_content_for[key] = value
end
elsif @_content_for && (value = @_content_for[key])
if append
value = value.join
end
value
end
end
end
end
register_plugin(:content_for, ContentFor)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/content_security_policy.rb 0000664 0000000 0000000 00000024526 15167207754 0026102 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The content_security_policy plugin allows you to easily set a Content-Security-Policy
# header for the application, which modern browsers will use to control access to specific
# types of page content.
#
# You would generally call the plugin with a block to set the default policy:
#
# plugin :content_security_policy do |csp|
# csp.default_src :none
# csp.img_src :self
# csp.style_src :self
# csp.script_src :self
# csp.font_src :self
# csp.form_action :self
# csp.base_uri :none
# csp.frame_ancestors :none
# csp.block_all_mixed_content
# end
#
# Then, anywhere in the routing tree, you can customize the policy for just that
# branch or action using the same block syntax:
#
# r.get 'foo' do
# content_security_policy do |csp|
# csp.object_src :self
# csp.add_style_src 'bar.com'
# end
# # ...
# end
#
# In addition to using a block, you can also call methods on the object returned
# by the method:
#
# r.get 'foo' do
# content_security_policy.script_src :self, 'example.com', [:nonce, 'foobarbaz']
# # ...
# end
#
# The following methods are available for configuring the content security policy,
# which specify the setting (substituting _ with -):
#
# * base_uri
# * child_src
# * connect_src
# * default_src
# * font_src
# * form_action
# * frame_ancestors
# * frame_src
# * img_src
# * manifest_src
# * media_src
# * object_src
# * plugin_types
# * report_to
# * report_uri
# * require_sri_for
# * sandbox
# * script_src
# * style_src
# * worker_src
#
# All of these methods support any number of arguments, and each argument should
# be one of the following types:
#
# String :: used verbatim
# Symbol :: Substitutes +_+ with +-+ and surrounds with '
# Array :: only accepts 2 element arrays, joins elements with +-+ and
# surrounds the result with '
#
# Example:
#
# content_security_policy.script_src :self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']
# # script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
#
# When calling a method with no arguments, the setting is removed from the policy instead
# of being left empty, since all of these setting require at least one value. Likewise,
# if the policy does not have any settings, the header will not be added.
#
# Calling the method overrides any previous setting. Each of the methods has +add_*+ and
# +get_*+ methods defined. The +add_*+ method appends to any existing setting, and the +get_*+ method
# returns the current value for the setting.
#
# content_security_policy.script_src :self, :unsafe_eval
# content_security_policy.add_script_src 'example.com', [:nonce, 'foobarbaz']
# # script-src 'self' 'unsafe-eval' example.com 'nonce-foobarbaz';
#
# content_security_policy.get_script_src
# # => [:self, :unsafe_eval, 'example.com', [:nonce, 'foobarbaz']]
#
# The clear method can be used to remove all settings from the policy. Empty policies
# do not set any headers. You can use +response.skip_content_security_policy!+ to skip
# setting a policy. This is faster than calling +content_security_policy.clear+, since
# it does not duplicate the default policy.
#
# The following methods to set boolean settings are also defined:
#
# * block_all_mixed_content
# * upgrade_insecure_requests
#
# Calling these methods will turn on the related setting. To turn the setting
# off again, you can call them with a +false+ argument. There is also a *? method
# for each setting for returning whether the setting is currently enabled.
#
# Likewise there is also a +report_only+ method for turning on report only mode (the
# default is enforcement mode), or turning off report only mode if a false argument
# is given. Also, there is a +report_only?+ method for returning whether report only
# mode is enabled.
module ContentSecurityPolicy
# Represents a content security policy.
class Policy
'
base-uri
child-src
connect-src
default-src
font-src
form-action
frame-ancestors
frame-src
img-src
manifest-src
media-src
object-src
plugin-types
report-to
report-uri
require-sri-for
sandbox
script-src
style-src
worker-src
'.split.each(&:freeze).each do |setting|
meth = setting.tr('-', '_').freeze
# Setting method name sets the setting value, or removes it if no args are given.
define_method(meth) do |*args|
if args.empty?
@opts.delete(setting)
else
@opts[setting] = args.freeze
end
nil
end
# add_* method name adds to the setting value, or clears setting if no values
# are given.
define_method("add_#{meth}") do |*args|
unless args.empty?
@opts[setting] ||= EMPTY_ARRAY
@opts[setting] += args
@opts[setting].freeze
end
nil
end
# get_* method always returns current setting value.
define_method("get_#{meth}") do
@opts[setting]
end
end
%w'block-all-mixed-content upgrade-insecure-requests'.each(&:freeze).each do |setting|
meth = setting.tr('-', '_').freeze
# Setting method name turns on setting if true or no argument given,
# or removes setting if false is given.
define_method(meth) do |arg=true|
if arg
@opts[setting] = true
else
@opts.delete(setting)
end
nil
end
# *? method returns true or false depending on whether setting is enabled.
define_method("#{meth}?") do
!!@opts[setting]
end
end
def initialize
clear
end
# Clear all settings, useful to remove any inherited settings.
def clear
@opts = {}
end
# Do not allow future modifications to any settings.
def freeze
@opts.freeze
header_value.freeze
super
end
# The header name to use, depends on whether report only mode has been enabled.
def header_key
@report_only ? RodaResponseHeaders::CONTENT_SECURITY_POLICY_REPORT_ONLY : RodaResponseHeaders::CONTENT_SECURITY_POLICY
end
# The header value to use.
def header_value
return @header_value if @header_value
s = String.new
@opts.each do |k, vs|
s << k
unless vs == true
vs.each{|v| append_formatted_value(s, v)}
end
s << '; '
end
@header_value = s
end
# Set whether the Content-Security-Policy-Report-Only header instead of the
# default Content-Security-Policy header.
def report_only(report=true)
@report_only = report
end
# Whether this policy uses report only mode.
def report_only?
!!@report_only
end
# Set the current policy in the headers hash. If no settings have been made
# in the policy, does not set a header.
def set_header(headers)
return if @opts.empty?
headers[header_key] ||= header_value
end
private
# Handle three types of values when formatting the header:
# String :: used verbatim
# Symbol :: Substitutes _ with - and surrounds with '
# Array :: only accepts 2 element arrays, joins them with - and
# surrounds them with '
def append_formatted_value(s, v)
case v
when String
s << ' ' << v
when Array
case v.length
when 2
s << " '" << v.join('-') << "'"
else
raise RodaError, "unsupported CSP value used: #{v.inspect}"
end
when Symbol
s << " '" << v.to_s.tr('_', '-') << "'"
else
raise RodaError, "unsupported CSP value used: #{v.inspect}"
end
end
# Make object copy use copy of settings, and remove cached header value.
def initialize_copy(_)
super
@opts = @opts.dup
@header_value = nil
end
end
# Yield the current Content Security Policy to the block.
def self.configure(app)
policy = app.opts[:content_security_policy] = if policy = app.opts[:content_security_policy]
policy.dup
else
Policy.new
end
yield policy if defined?(yield)
policy.freeze
end
module InstanceMethods
# If a block is given, yield the current content security policy. Returns the
# current content security policy.
def content_security_policy
policy = @_response.content_security_policy
yield policy if defined?(yield)
policy
end
end
module ResponseMethods
# Unset any content security policy when reinitializing
def initialize
super
@content_security_policy &&= nil
end
# The current content security policy to be used for this response.
def content_security_policy
@content_security_policy ||= roda_class.opts[:content_security_policy].dup
end
# Do not set a content security policy header for this response.
def skip_content_security_policy!
@skip_content_security_policy = true
end
private
# Set the appropriate content security policy header.
def set_default_headers
super
unless @skip_content_security_policy
(@content_security_policy || roda_class.opts[:content_security_policy]).set_header(headers)
end
end
end
end
register_plugin(:content_security_policy, ContentSecurityPolicy)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/cookie_flags.rb 0000664 0000000 0000000 00000013411 15167207754 0023536 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The cookie_flags plugin allows users to force specific cookie flags for
# all cookies set by the application. It can also be used to warn or
# raise for unexpected cookie flags.
#
# The cookie_flags plugin deals with the following cookie flags:
#
# httponly :: Disallows access to the cookie from client-side scripts.
# samesite :: Restricts to which domains the cookie is sent.
# secure :: Instructs the browser to only transmit the cookie over HTTPS.
#
# This plugin ships in secure-by-default mode, where it enforces
# secure, httponly, samesite=strict cookies. You can disable enforcing
# specific flags using the following options:
#
# :httponly :: Set to false to not enforce httponly flag.
# :same_site :: Set to symbol or string to enforce a different samesite
# setting, or false to not enforce a specific samesite setting.
# :secure :: Set to false to not enforce secure flag.
#
# For example, to enforce secure cookies and enforce samesite=lax, but not enforce
# an httponly flag:
#
# plugin :cookie_flags, httponly: false, same_site: 'lax'
#
# In general, overriding cookie flags using this plugin should be considered a
# stop-gap solution. Instead of overriding cookie flags, it's better to fix
# whatever is setting the cookie flags incorrectly. You can use the :action
# option to modify the behavior:
#
# # Issue warnings when modifying cookie flags
# plugin :cookie_flags, action: :warn_and_modify
#
# # Issue warnings for incorrect cookie flags without modifying cookie flags
# plugin :cookie_flags, action: :warn
#
# # Raise errors for incorrect cookie flags
# plugin :cookie_flags, action: :raise
#
# The recommended way to use the plugin is to use it only during testing with
# action: :raise. Then as long as you have fully covering tests, you
# can be sure the cookies set by your application use the correct flags.
#
# Note that this plugin only affects cookies set by the application, and does not
# affect cookies set by middleware the application is using.
module CookieFlags
# :nocov:
MATCH_METH = RUBY_VERSION >= '2.4' ? :match? : :match
# :nocov:
private_constant :MATCH_METH
DEFAULTS = {:secure=>true, :httponly=>true, :same_site=>'strict', :action=>:modify}.freeze
private_constant :DEFAULTS
# Error class raised for action: :raise when incorrect cookie flags are used.
class Error < RodaError
end
def self.configure(app, opts=OPTS)
previous = app.opts[:cookie_flags] || DEFAULTS
opts = app.opts[:cookie_flags] = previous.merge(opts)
case opts[:same_site]
when String, Symbol
opts[:same_site] = opts[:same_site].to_s.downcase.freeze
opts[:same_site_string] = "; samesite=#{opts[:same_site]}".freeze
opts[:secure] = true if opts[:same_site] == 'none'
end
opts.freeze
end
module InstanceMethods
private
def _handle_cookie_flags_array(cookies)
opts = self.class.opts[:cookie_flags]
needs_secure = opts[:secure]
needs_httponly = opts[:httponly]
if needs_same_site = opts[:same_site]
same_site_string = opts[:same_site_string]
same_site_regexp = /;\s*samesite\s*=\s*(\S+)\s*(?:\z|;)/i
end
action = opts[:action]
cookies.map do |cookie|
if needs_secure
add_secure = !/;\s*secure\s*(?:\z|;)/i.send(MATCH_METH, cookie)
end
if needs_httponly
add_httponly = !/;\s*httponly\s*(?:\z|;)/i.send(MATCH_METH, cookie)
end
if needs_same_site
has_same_site = same_site_regexp.match(cookie)
unless add_same_site = !has_same_site
update_same_site = needs_same_site != has_same_site[1].downcase
end
end
next cookie unless add_secure || add_httponly || add_same_site || update_same_site
case action
when :raise, :warn, :warn_and_modify
message = "Response contains cookie with unexpected flags: #{cookie.inspect}." \
"Expecting the following cookie flags: "\
"#{'secure ' if add_secure}#{'httponly ' if add_httponly}#{same_site_string[2..-1] if add_same_site || update_same_site}"
if action == :raise
raise Error, message
else
warn(message)
next cookie if action == :warn
end
end
if update_same_site
cookie = cookie.gsub(same_site_regexp, same_site_string)
else
cookie = cookie.dup
cookie << same_site_string if add_same_site
end
cookie << '; secure' if add_secure
cookie << '; httponly' if add_httponly
cookie
end
end
if Rack.release >= '3'
def _handle_cookie_flags(cookies)
cookies = [cookies] if cookies.is_a?(String)
_handle_cookie_flags_array(cookies)
end
else
def _handle_cookie_flags(cookie_string)
_handle_cookie_flags_array(cookie_string.split("\n")).join("\n")
end
end
# Handle cookie flags in response
def _roda_after_85__cookie_flags(res)
return unless res && (headers = res[1]) && (value = headers[RodaResponseHeaders::SET_COOKIE])
headers[RodaResponseHeaders::SET_COOKIE] = _handle_cookie_flags(value)
end
end
end
register_plugin(:cookie_flags, CookieFlags)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/cookies.rb 0000664 0000000 0000000 00000003505 15167207754 0022550 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'rack/utils'
#
class Roda
module RodaPlugins
# The cookies plugin adds response methods for handling cookies.
# Currently, you can set cookies with +set_cookie+ and delete cookies
# with +delete_cookie+:
#
# response.set_cookie('foo', 'bar')
# response.delete_cookie('foo')
#
# Pass a hash of cookie options when loading the plugin to set some
# defaults for all cookies upon setting and deleting. This is particularly
# useful for configuring the +domain+ and +path+ of all cookies.
#
# plugin :cookies, domain: 'example.com', path: '/api'
module Cookies
# Allow setting default cookie options when loading the cookies plugin.
def self.configure(app, opts={})
app.opts[:cookies_opts] = (app.opts[:cookies_opts]||{}).merge(opts).freeze
end
module ResponseMethods
# Modify the headers to include a Set-Cookie value that
# deletes the cookie. A value hash can be provided to
# override the default one used to delete the cookie.
# Example:
#
# response.delete_cookie('foo')
# response.delete_cookie('foo', domain: 'example.org')
def delete_cookie(key, value = {})
::Rack::Utils.delete_cookie_header!(@headers, key, roda_class.opts[:cookies_opts].merge(value))
end
# Set the cookie with the given key in the headers.
#
# response.set_cookie('foo', 'bar')
# response.set_cookie('foo', value: 'bar', domain: 'example.org')
def set_cookie(key, value)
value = { :value=>value } unless value.respond_to?(:keys)
::Rack::Utils.set_cookie_header!(@headers, key, roda_class.opts[:cookies_opts].merge(value))
end
end
end
register_plugin(:cookies, Cookies)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/csrf.rb 0000664 0000000 0000000 00000004713 15167207754 0022053 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'rack/csrf'
class Roda
module RodaPlugins
# This plugin is no longer recommended for use, it exists only for
# backwards compatibility. Consider using the route_csrf plugin
# instead, as that provides stronger CSRF protection.
#
# The csrf plugin adds CSRF protection using rack_csrf, along with
# some csrf helper methods to use in your views. To use it, load
# the plugin, with the options hash passed to Rack::Csrf:
#
# plugin :csrf, raise: true
#
# Optionally you can choose not to setup rack_csrf middleware on the
# roda app if you already have one configured:
#
# plugin :csrf, skip_middleware: true
#
# This adds the following instance methods:
#
# csrf_field :: The field name to use for the hidden/meta csrf tag.
# csrf_header :: The http header name to use for submitting csrf token via
# headers (useful for javascript).
# csrf_metatag :: An html meta tag string containing the token, suitable
# for placing in the page header
# csrf_tag :: An html hidden input tag string containing the token, suitable
# for placing in an html form.
# csrf_token :: The value of the csrf token, in case it needs to be accessed
# directly.
module Csrf
CSRF = ::Rack::Csrf
# Load the Rack::Csrf middleware into the app with the given options.
def self.configure(app, opts={})
return if opts[:skip_middleware]
app.instance_exec do
@middleware.each do |(mid, *rest), _|
if mid.equal?(CSRF)
rest[0].merge!(opts)
build_rack_app
return
end
end
use CSRF, opts
end
end
module InstanceMethods
# The name of the hidden/meta csrf tag.
def csrf_field
CSRF.field
end
# The http header name to use for submitting csrf token via headers.
def csrf_header
CSRF.header
end
# An html meta tag string containing the token.
def csrf_metatag(opts={})
CSRF.metatag(env, opts)
end
# An html hidden input tag string containing the token.
def csrf_tag
CSRF.tag(env)
end
# The value of the csrf token.
def csrf_token
CSRF.token(env)
end
end
end
register_plugin(:csrf, Csrf)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/custom_block_results.rb 0000664 0000000 0000000 00000005447 15167207754 0025370 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The custom_block_results plugin allows you to specify handling
# for different block results. By default, Roda only supports
# nil, false, and string block results, but using this plugin,
# you can support other block results.
#
# For example, if you wanted to support returning Integer
# block results, and have them set the response status code,
# you could do:
#
# plugin :custom_block_results
#
# handle_block_result Integer do |result|
# response.status_code = result
# end
#
# route do |r|
# 200
# end
#
# The expected use case for this is to customize behavior by
# class, but matching uses ===, so it is possible to use non-class
# objects that respond to === appropriately.
#
# Note that custom block result handling only occurs if the types
# are not handled by Roda itself. You cannot use this to modify
# the handling of nil, false, or string results. Additionally,
# if the response body has already been written to before the the
# route block exits, then the result of the block is ignored,
# and the related +handle_block_result+ block will not be called
# (this is standard Roda behavior).
#
# The return value of the +handle_block_result+ block is written
# to the body if the block return value is a String, similar to
# standard Roda handling of block results. Non-String return
# values are ignored.
module CustomBlockResults
def self.configure(app)
app.opts[:custom_block_results] ||= {}
end
module ClassMethods
# Freeze the configured custom block results when freezing the app.
def freeze
opts[:custom_block_results].freeze
super
end
# Specify a block that will be called when an instance of klass
# is returned as a block result. The block defines a method.
def handle_block_result(klass, &block)
opts[:custom_block_results][klass] = define_roda_method(opts[:custom_block_results][klass] || "custom_block_result_#{klass}", 1, &block)
end
end
module RequestMethods
private
# Try each configured custom block result, and call the related method
# to get the block result.
def unsupported_block_result(result)
roda_class.opts[:custom_block_results].each do |klass, meth|
if klass === result
result = scope.send(meth, result)
if String === result
return result
else
return
end
end
end
super
end
end
end
register_plugin(:custom_block_results, CustomBlockResults)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/custom_matchers.rb 0000664 0000000 0000000 00000005256 15167207754 0024321 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The custom_matchers plugin supports using arbitrary objects
# as matchers, as long as the application has been configured
# to accept such objects.
#
# After loading the plugin, support for custom matchers can be
# configured using the +custom_matcher+ class method. This
# method is generally passed the class of the object you want
# to use as a custom matcher, as well as a block. The block
# will be called in the context of the request instance
# with the specific matcher used in the match method.
#
# Blocks can append to the captures in order to yield the appropriate
# values to match blocks, or call request methods that append to the
# captures.
#
# Example:
#
# plugin :custom_matchers
# method_segment = Struct.new(:request_method, :next_segment)
# custom_matcher(method_segment) do |matcher|
# # self is the request instance ("r" yielded in the route block below)
# if matcher.request_method == self.request_method
# match(matcher.next_segment)
# end
# end
#
# get_foo = method_segment.new('GET', 'foo')
# post_any = method_segment.new('POST', String)
# route do |r|
# r.on('baz') do
# r.on(get_foo) do
# # GET method, /baz/foo prefix
# end
#
# r.is(post_any) do |seg|
# # for POST /baz/bar, seg is "bar"
# end
# end
#
# r.on('quux') do
# r.is(get_foo) do
# # GET method, /quux/foo route
# end
#
# r.on(post_any) do |seg|
# # for POST /quux/xyz, seg is "xyz"
# end
# end
# end
module CustomMatchers
def self.configure(app)
app.opts[:custom_matchers] ||= OPTS
end
module ClassMethods
def custom_matcher(match_class, &block)
custom_matchers = Hash[opts[:custom_matchers]]
meth = custom_matchers[match_class] = custom_matchers[match_class] || :"_custom_matcher_#{match_class}"
opts[:custom_matchers] = custom_matchers.freeze
self::RodaRequest.send(:define_method, meth, &block)
nil
end
end
module RequestMethods
private
# Try custom matchers before calling super
def unsupported_matcher(matcher)
roda_class.opts[:custom_matchers].each do |match_class, meth|
if match_class === matcher
return send(meth, matcher)
end
end
super
end
end
end
register_plugin(:custom_matchers, CustomMatchers)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/default_headers.rb 0000664 0000000 0000000 00000003757 15167207754 0024244 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The default_headers plugin accepts a hash of headers,
# and overrides the default_headers method in the
# response class to be a copy of the headers.
#
# Note that when using this module, you should not
# attempt to mutate of the values set in the default
# headers hash.
#
# Example:
#
# plugin :default_headers, 'Content-Type'=>'text/csv'
#
# You can modify the default headers later by loading the
# plugin again:
#
# plugin :default_headers, 'Foo'=>'bar'
# plugin :default_headers, 'Bar'=>'baz'
module DefaultHeaders
# Merge the given headers into the existing default headers, if any.
def self.configure(app, headers={})
app.opts[:default_headers] = (app.default_headers || app::RodaResponse::DEFAULT_HEADERS).merge(headers).freeze
end
module ClassMethods
# The default response headers to use for the current class.
def default_headers
opts[:default_headers]
end
# Optimize the response class set_default_headers method if it hasn't been
# overridden and all default headers are strings.
def freeze
if (headers = opts[:default_headers]).all?{|k, v| k.is_a?(String) && v.is_a?(String)} &&
(self::RodaResponse.instance_method(:set_default_headers).owner == Base::ResponseMethods)
self::RodaResponse.class_eval(<<-END, __FILE__, __LINE__+1)
private
def set_default_headers
h = @headers
#{headers.map{|k,v| "h[#{k.inspect}] ||= #{v.inspect}"}.join('; ')}
end
END
end
super
end
end
module ResponseMethods
# Get the default headers from the related roda class.
def default_headers
roda_class.default_headers
end
end
end
register_plugin(:default_headers, DefaultHeaders)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/default_status.rb 0000664 0000000 0000000 00000002232 15167207754 0024137 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The default_status plugin accepts a block which should
# return a response status integer. This integer will be used as
# the default response status (usually 200) if the body has been
# written to, and you have not explicitly set a response status.
#
# Example:
#
# # Use 201 default response status for all requests
# plugin :default_status do
# 201
# end
module DefaultStatus
def self.configure(app, &block)
raise RodaError, "default_status plugin requires a block" unless block
if check_arity = app.opts.fetch(:check_arity, true)
unless block.arity == 0
if check_arity == :warn
RodaPlugins.warn "Arity mismatch in block passed to plugin :default_status. Expected Arity 0, but arguments required for #{block.inspect}"
end
b = block
block = lambda{instance_exec(&b)} # Fallback
end
end
app::RodaResponse.send(:define_method, :default_status, &block)
end
end
register_plugin(:default_status, DefaultStatus)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/delay_build.rb 0000664 0000000 0000000 00000000545 15167207754 0023372 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
module DelayBuild
module ClassMethods
# No-op for backwards compatibility
def build!
end
end
end
# RODA4: Remove plugin
# Only available for backwards compatibility, no longer needed
register_plugin(:delay_build, DelayBuild)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/delegate.rb 0000664 0000000 0000000 00000004300 15167207754 0022660 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The delegate plugin allows you to easily setup instance methods in
# the scope of the route block that call methods on the related
# request, response, or class which may offer a simpler API in some cases.
# Roda doesn't automatically setup such delegate methods because
# it pollutes the application's method namespace, but this plugin
# allows the user to do so.
#
# Here's an example based on the README's initial example, using the
# request_delegate method to simplify the DSL:
#
# plugin :delegate
# request_delegate :root, :on, :is, :get, :post, :redirect
#
# route do |r|
# # GET / request
# root do
# redirect "/hello"
# end
#
# # /hello branch
# on "hello" do
# # Set variable for all routes in /hello branch
# @greeting = 'Hello'
#
# # GET /hello/world request
# get "world" do
# "#{@greeting} world!"
# end
#
# # /hello request
# is do
# # GET /hello request
# get do
# "#{@greeting}!"
# end
#
# # POST /hello request
# post do
# puts "Someone said #{@greeting}!"
# redirect
# end
# end
# end
# end
module Delegate
module ClassMethods
# Delegate the given methods to the class
def class_delegate(*meths)
meths.each do |meth|
define_method(meth){|*a, &block| self.class.public_send(meth, *a, &block)}
end
end
# Delegate the given methods to the request
def request_delegate(*meths)
meths.each do |meth|
define_method(meth){|*a, &block| @_request.public_send(meth, *a, &block)}
end
end
# Delegate the given methods to the response
def response_delegate(*meths)
meths.each do |meth|
define_method(meth){|*a, &block| @_response.public_send(meth, *a, &block)}
end
end
end
end
register_plugin(:delegate, Delegate)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/delete_empty_headers.rb 0000664 0000000 0000000 00000002042 15167207754 0025262 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The delete_empty_headers plugin deletes any headers whose
# value is set to the empty string. Because of how default headers are
# set in Roda, if you have a default header but don't want
# to set it for a specific request, you need to use this plugin
# and set the header value to the empty string, and Roda will automatically
# delete the header.
module DeleteEmptyHeaders
module ResponseMethods
# Delete any empty headers when calling finish
def finish
delete_empty_headers(super)
end
# Delete any empty headers when calling finish_with_body
def finish_with_body(_)
delete_empty_headers(super)
end
private
# Delete any empty headers from response
def delete_empty_headers(res)
res[1].delete_if{|_, v| v.is_a?(String) && v.empty?}
res
end
end
end
register_plugin(:delete_empty_headers, DeleteEmptyHeaders)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/direct_call.rb 0000664 0000000 0000000 00000001641 15167207754 0023360 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The direct_call plugin makes the call class method skip the middleware stack
# (app.call will still call the middleware).
# This can be used as an optimization, as the Roda class itself can be used
# as the callable, which is faster than using a lambda.
module DirectCall
def self.configure(app)
app.send(:build_rack_app)
end
module ClassMethods
# Call the application without middlware.
def call(env)
new(env)._roda_handle_main_route
end
private
# If new_api is true, use the receiver as the base rack app for better
# performance.
def base_rack_app_callable(new_api=true)
if new_api
self
else
super
end
end
end
end
register_plugin(:direct_call, DirectCall)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/disallow_file_uploads.rb 0000664 0000000 0000000 00000002701 15167207754 0025455 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
raise LoadError, "disallow_file_uploads plugin not supported on Rack <1.6" if Rack.release < '1.6'
#
class Roda
module RodaPlugins
# The disallow_file_uploads plugin raises a Roda::RodaPlugins::DisallowFileUploads::Error
# if there is an attempt to upload a file. This plugin is useful for applications where
# multipart file uploads are not expected and you want to remove the ability for rack
# to create temporary files. Example:
#
# plugin :disallow_file_uploads
#
# This plugin is only supported on Rack 1.6+. This plugin does not technically
# block users from uploading files, it only blocks the parsing of request bodies containing
# multipart file uploads. So if you do not call +r.POST+ (or something that calls it such as
# +r.params+), then Roda will not attempt to parse the request body, and an exception will not
# be raised.
module DisallowFileUploads
# Exception class used when a multipart file upload is attempted.
class Error < RodaError; end
NO_TEMPFILE = lambda{|_,_| raise Error, "Support for uploading files has been disabled"}
module RequestMethods
# HTML escape the input and return the escaped version.
def initialize(_, env)
env['rack.multipart.tempfile_factory'] = NO_TEMPFILE
super
end
end
end
register_plugin(:disallow_file_uploads, DisallowFileUploads)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/drop_body.rb 0000664 0000000 0000000 00000002536 15167207754 0023100 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The drop_body plugin automatically drops the body and
# Content-Type/Content-Length headers from the response if
# the response status indicates that the response should
# not include a body (response statuses 100, 101, 102, 204,
# and 304). For response status 205, the body and Content-Type
# headers are dropped, but the Content-length header is set to
# '0' instead of being dropped.
module DropBody
module ResponseMethods
DROP_BODY_STATUSES = [100, 101, 102, 204, 205, 304].freeze
RodaPlugins.deprecate_constant(self, :DROP_BODY_STATUSES)
DROP_BODY_RANGE = 100..199
private_constant :DROP_BODY_RANGE
# If the response status indicates a body should not be
# returned, use an empty body and remove the Content-Length
# and Content-Type headers.
def finish
r = super
case r[0]
when DROP_BODY_RANGE, 204, 304
r[2] = EMPTY_ARRAY
h = r[1]
h.delete(RodaResponseHeaders::CONTENT_LENGTH)
h.delete(RodaResponseHeaders::CONTENT_TYPE)
when 205
r[2] = EMPTY_ARRAY
empty_205_headers(r[1])
end
r
end
end
end
register_plugin(:drop_body, DropBody)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/each_part.rb 0000664 0000000 0000000 00000005277 15167207754 0023052 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The each_part plugin adds an each_part method, which is a
# render_each-like method that treats all keywords as locals.
#
# # Can replace this:
# render_each(enum, :template, locals: {foo: 'bar'})
#
# # With this:
# each_part(enum, :template, foo: 'bar')
#
# On Ruby 2.7+, the part method takes a keyword splat, so you
# must pass keywords and not a positional hash for the locals.
#
# If you are using the :assume_fixed_locals render plugin option,
# template caching is enabled, you are using Ruby 3+, and you
# are freezing your Roda application, in addition to providing a
# simpler API, this also provides a performance improvement.
module EachPart
def self.load_dependencies(app)
app.plugin :render_each
end
module ClassMethods
# When freezing, optimize the part method if assuming fixed locals
# and caching templates.
def freeze
if render_opts[:assume_fixed_locals] && !render_opts[:check_template_mtime]
include AssumeFixedLocalsInstanceMethods
end
super
end
end
module InstanceMethods
if RUBY_VERSION >= '2.7'
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
def each_part(enum, template, **locals, &block)
render_each(enum, template, :locals=>locals, &block)
end
RUBY
# :nocov:
else
def each_part(enum, template, locals=OPTS, &block)
render_each(enum, template, :locals=>locals, &block)
end
end
# :nocov:
end
module AssumeFixedLocalsInstanceMethods
# :nocov:
if RUBY_VERSION >= '3.0'
# :nocov:
class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
def each_part(enum, template, **locals, &block)
if optimized_method = _cached_render_each_template_method(template)
optimized_method = optimized_method[0]
as = render_each_default_local(template)
if defined?(yield)
enum.each do |v|
locals[as] = v
yield send(optimized_method, **locals)
end
nil
else
enum.map do |v|
locals[as] = v
send(optimized_method, **locals)
end.join
end
else
render_each(enum, template, :locals=>locals, &block)
end
end
RUBY
end
end
end
register_plugin(:each_part, EachPart)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/early_hints.rb 0000664 0000000 0000000 00000001455 15167207754 0023437 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The early_hints plugin allows sending 103 Early Hints responses
# using the rack.early_hints environment variable.
# Early hints allow clients to preload necessary files before receiving
# the response.
module EarlyHints
module InstanceMethods
# Send given hash of Early Hints using the rack.early_hints environment variable,
# currenly only supported by puma. hash given should generally have the single
# key 'Link', and a string or array of strings for each of the early hints.
def send_early_hints(hash)
if eh_proc = env['rack.early_hints']
eh_proc.call(hash)
end
end
end
end
register_plugin(:early_hints, EarlyHints)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/empty_root.rb 0000664 0000000 0000000 00000002500 15167207754 0023307 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The empty_root plugin makes +r.root+ match both on +/+ and
# on the empty string. This is mostly useful when using multiple
# rack applications, where the initial PATH_INFO has been moved
# to SCRIPT_NAME. For example, if you have the following
# applications:
#
# class App1 < Roda
# on "albums" do
# run App2
# end
# end
#
# class App2 < Roda
# plugin :empty_root
#
# route do |r|
# r.root do
# "root"
# end
# end
# end
#
# Then requests for both +/albums/+ and +/albums+ will return
# "root". Without this plugin loaded into App2, only requests
# for +/albums/+ will return "root", since by default, +r.root+
# matches only when the current PATH_INFO is +/+ and not when
# it is empty.
module EmptyRoot
module RequestMethods
# Match when the remaining path is the empty string,
# in addition to the default behavior of matching when
# the remaining path is +/+.
def root(&block)
super
if remaining_path.empty? && is_get?
always(&block)
end
end
end
end
register_plugin(:empty_root, EmptyRoot)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/environments.rb 0000664 0000000 0000000 00000004614 15167207754 0023645 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
#
class Roda
module RodaPlugins
# The environments plugin adds a environment class accessor to get
# the environment for the application, 3 predicate class methods
# to check for the current environment (development?, test? and
# production?), and a class configure method that takes environment(s)
# and yields to the block if the given environment(s) match the
# current environment.
#
# The default environment for the application is based on
# ENV['RACK_ENV'].
#
# Example:
#
# class Roda
# plugin :environments
#
# environment # => :development
# development? # => true
# test? # => false
# production? # => false
#
# # Set the environment for the application
# self.environment = :test
# test? # => true
#
# configure do
# # called, as no environments given
# end
#
# configure :development, :production do
# # not called, as no environments match
# end
#
# configure :test do
# # called, as environment given matches current environment
# end
# end
module Environments
# Set the environment to use for the app. Default to ENV['RACK_ENV']
# if no environment is given. If ENV['RACK_ENV'] is not set and
# no environment is given, assume the development environment.
def self.configure(app, env=ENV["RACK_ENV"])
app.environment = (env || 'development').to_sym
end
module ClassMethods
# If no environments are given or one of the given environments
# matches the current environment, yield the receiver to the block.
def configure(*envs)
if envs.empty? || envs.any?{|s| s == environment}
yield self
end
end
# The current environment for the application, which should be stored
# as a symbol.
def environment
opts[:environment]
end
# Override the environment for the application, instead of using
# RACK_ENV.
def environment=(v)
opts[:environment] = v
end
[:development, :test, :production].each do |env|
define_method("#{env}?"){environment == env}
end
end
end
register_plugin(:environments, Environments)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/erb_h.rb 0000664 0000000 0000000 00000002401 15167207754 0022165 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'erb/escape'
#
class Roda
module RodaPlugins
# The erb_h plugin adds an +h+ instance method that will HTML
# escape the input and return it. This is similar to the h
# plugin, but it uses erb/escape to implement the HTML escaping,
# which offers faster performance.
#
# To make sure that this speeds up applications using the h
# plugin, this depends on the h plugin, and overrides the
# h method.
#
# The following example will return "<foo>" as the body.
#
# plugin :erb_h
#
# route do |r|
# h('')
# end
#
# The faster performance offered by the erb_h plugin is due
# to erb/escape avoiding allocations if not needed (returning the
# input object if no escaping is needed). That behavior change
# can cause problems if you mutate the result of the h method
# (which can mutate the input), or mutate the input of the h
# method after calling it (which can mutate the result).
module ErbH
def self.load_dependencies(app)
app.plugin :h
end
module InstanceMethods
define_method(:h, ERB::Escape.instance_method(:html_escape))
end
end
register_plugin(:erb_h, ErbH)
end
end
jeremyevans-roda-4f30bb3/lib/roda/plugins/error_email.rb 0000664 0000000 0000000 00000012016 15167207754 0023411 0 ustar 00root root 0000000 0000000 # frozen-string-literal: true
require 'net/smtp'
class Roda
module RodaPlugins
# The error_email plugin adds an +error_email+ instance method that
# send an email related to the exception. This is most useful if you are
# also using the error_handler plugin:
#
# plugin :error_email, to: 'to@example.com', from: 'from@example.com'
# plugin :error_handler do |e|
# error_email(e)
# 'Internal Server Error'
# end
#
# It is similar to the error_mail plugin, except that it uses net/smtp
# directly instead of using the mail library. If you are not already using the
# mail library in your application, it makes sense to use error_email
# instead of error_mail.
#
# Options:
#
# :filter :: Callable called with the key and value for each parameter, environment
# variable, and session value. If it returns true, the value of the
# parameter is filtered in the email.
# :from :: The From address to use in the email (required)
# :headers :: A hash of additional headers to use in the email (default: empty hash)
# :host :: The SMTP server to use to send the email (default: localhost)
# :prefix :: A prefix to use in the email's subject line (default: no prefix)
# :to :: The To address to use in the email (required)
#
# The subject of the error email shows the exception class and message.
# The body of the error email shows the backtrace of the error and the
# request environment, as well the request params and session variables (if any).
# You can also call error_email with a plain string instead of an exception,
# in which case the string is used as the subject, and no backtrace is included.
#
# Note that emailing on every error as shown above is only appropriate
# for low traffic web applications. For high traffic web applications,
# use an error reporting service instead of this plugin.
module ErrorEmail
DEFAULTS = {
:filter=>lambda{|k,v| false},
:headers=>OPTS,
:host=>'localhost',
# :nocov:
:emailer=>lambda{|h| Net::SMTP.start(h[:host]){|s| s.send_message(h[:message], h[:from], h[:to])}},
# :nocov:
:default_headers=>lambda do |h, e|
subject = if e.respond_to?(:message)
"#{e.class}: #{e.message}"
else
e.to_s
end
{'From'=>h[:from], 'To'=>h[:to], 'Subject'=>"#{h[:prefix]}#{subject}"}
end,
:body=>lambda do |s, e|
filter = s.opts[:error_email][:filter]
format = lambda do |h|
h = h.map{|k, v| "#{k.inspect} => #{filter.call(k, v) ? 'FILTERED' : v.inspect}"}
h.sort!
h.join("\n")
end
begin
params = s.request.params
params = (format[params] unless params.empty?)
rescue
params = 'Invalid Parameters!'
end
message = String.new
message << <