Flask Syntax for Mustermann
This gem implements the flask
pattern type for Mustermann. It is compatible with Flask and Werkzeug.
Overview
Supported options:
capture
, except
, greedy
, space_matches_plus
, uri_decode
, converters
and ignore_unknown_options
External documentation: Werkzeug: URL Routing
require 'mustermann/flask'
Mustermann.new('/<prefix>/<path:page>', type: :flask).params('/a/b/c') # => { prefix: 'a', page: 'b/c' }
pattern = Mustermann.new('/<name>', type: :flask)
pattern.respond_to? :expand # => true
pattern.(name: 'foo') # => '/foo'
pattern.respond_to? :to_templates # => true
pattern.to_templates # => ['/{name}']
Syntax
Syntax Element | Description |
---|---|
<name> | Captures anything but a forward slash in a semi-greedy fashion. Capture is named name. Capture behavior can be modified with capture and greedy option. |
<converter:name> | Captures depending on the converter constraint. Capture is named name. Capture behavior can be modified with capture and greedy option. See below. |
<converter(arguments):name> | Captures depending on the converter constraint. Capture is named name. Capture behavior can be modified with capture and greedy option. Arguments are separated by comma. An argument can be a simple string, a string enclosed in single or double quotes, or a key value pair (keys and values being separated by an equal sign). See below. |
/ | Matches forward slash. Does not match URI encoded version of forward slash. |
any other character | Matches exactly that character or a URI encoded version of it. |
Converters
Builtin Converters
string
Possible arguments: minlength
, maxlength
, length
Captures anything but a forward slash in a semi-greedy fashion. Capture behavior can be modified with capture and greedy option.
This is also the default converter.
Examples:
<name>
<string:name>
<string(minlength=3,maxlength=10):slug>
<string(length=10):slug>
int
Possible arguments: min
, max
, fixed_digits
Captures digits. Captured value will be converted to an Integer.
Examples:
<int:id>
<int(min=1,max=5):page>
<int(fixed_digits=16):uuid>
float
Possible arguments: min
, max
Captures digits with a dot. Captured value will be converted to an Float.
Examples:
<float:precision>
<float(min=0,max=1):precision>
path
Captures anything in a non-greedy fashion.
Example:
<path:rest>
any
Possible arguments: List of accepted strings.
Captures anything that matches one of the arguments exactly.
Example:
<any(home,search,contact):page>
Custom Converters
Flask patterns support registering custom converters.
A converter object may implement any of the following methods:
convert
: Should return a block converting a string value to whatever value should end up in theparams
hash.constraint
: Should return a regular expression limiting which input string will match the capture.new
: Returns an object that may respond toconvert
and/orconstraint
as described above. Any arguments used for the converter inside the pattern will be passed tonew
.
require 'mustermann/flask'
SimpleConverter = Struct.new(:constraint, :convert)
id_converter = SimpleConverter.new(/\d/, -> s { s.to_i })
class NumConverter
def initialize(base: 10)
@base = Integer(base)
end
def convert
-> s { s.to_i(@base) }
end
def constraint
@base > 10 ? /[\da-#{(@base-1).to_s(@base)}]/ : /[0-#{@base-1}]/
end
end
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>',
type: :flask, converters: { id: id_converter, num: NumConverter})
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
Global Converters
It is also possible to register a converter for all flask patterns, using register_converter
:
Mustermann::Flask.register_converter(:id, id_converter)
Mustermann::Flask.register_converter(:num, NumConverter)
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>', type: :flask)
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
There is a handy syntax for quickly creating new converter classes: When you pass a block instead of a converter object, it will yield a generic converter with setters and getters for convert
and constraint
, and any arguments passed to the converter.
require 'mustermann/flask'
Mustermann::Flask.register_converter(:id) do |converter|
converter.constraint = /\d/
converter.convert = -> s { s.to_i }
end
Mustermann::Flask.register_converter(:num) do |converter, base: 10|
converter.constraint = base > 10 ? /[\da-#{(@base-1).to_s(base)}]/ : /[0-#{base-1}]/
converter.convert = -> s { s.to_i(base) }
end
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>', type: :flask)
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
Subclassing
Registering global converters will make these available for all Flask patterns. It might even override already registered converters. This global state might break unrelated code.
It is therefore recommended that, if you don't want to pass in the converters option for every pattern, you create your own subclass of Mustermann::Flask
.
require 'mustermann/flask'
MyFlask = Class.new(Mustermann::Flask)
MyFlask.register_converter(:id) do |converter|
converter.constraint = /\d/
converter.convert = -> s { s.to_i }
end
MyFlask.register_converter(:num) do |converter, base: 10|
converter.constraint = base > 10 ? /[\da-#{(@base-1).to_s(base)}]/ : /[0-#{base-1}]/
converter.convert = -> s { s.to_i(base) }
end
pattern = MyFlask.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>')
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}
You can even register this type for usage with Mustermann.new
:
Mustermann.register(:my_flask, MyFlask)
pattern = Mustermann.new('/<id:id>/<num(base=8):oct>/<num(base=16):hex>', type: :my_flask)
pattern.params('/10/12/f1') # => {"id"=>10, "oct"=>10, "hex"=>241}