Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,30 +10,21 @@ on:

jobs:
test:
name: ruby ${{ matrix.ruby }}, sinatra ${{ matrix.sinatra }}
name: ruby ${{ matrix.ruby }}
runs-on: ubuntu-latest
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
ruby:
- '2.7'
- '3.0'
- '3.1'
- '3.2'
- '3.3'
- '3.4'
sinatra:
- ~> 4.0.0 # current stable
include:
- { ruby: 3.4, sinatra: head }
- { ruby: head, sinatra: head }
- { ruby: jruby, sinatra: ~> 4.0.0 }
- { ruby: jruby-head, sinatra: head }
- { ruby: truffleruby, sinatra: ~> 4.0.0 }
- { ruby: truffleruby-head, sinatra: head }
env:
sinatra: ${{ matrix.sinatra }}
- '4.0'
- head
- jruby
- jruby-head
- truffleruby
- truffleruby-head
steps:
- uses: actions/checkout@v4

Expand Down
5 changes: 0 additions & 5 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,3 @@ path '.' do
Support::Projects.each { |name| gem(name) }
gem 'support', group: :development
end

sinatra_version = ENV['sinatra'].to_s
sinatra_version = nil if sinatra_version.empty? || (sinatra_version == 'stable')
sinatra_version = { github: 'sinatra/sinatra' } if sinatra_version == 'head'
gem 'sinatra', sinatra_version
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Any software using Mustermann is obviously compatible with at least one of the a

## Requirements

Ruby 2.7+ compatible Ruby implementation (MRI, JRuby, and TruffleRuby are tested).
Ruby 3.3+ compatible Ruby implementation (MRI, JRuby, and TruffleRuby are tested).

## Release History

Expand Down
83 changes: 83 additions & 0 deletions mustermann-contrib/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,52 @@ Mustermann::FileUtils.cp_r(':base.:ext' => ':base.bak.:ext')
Mustermann::FileUtils.ln_s('lib/:name.rb' => 'bin/:name')
```

<a name="-mustermann-mapper"></a>
# Mapper for Mustermann

## Overview

`Mustermann::Mapper` transforms strings according to a set of pattern mappings. Each mapping pairs an input pattern (used to extract parameters) with one or more output patterns (used to expand the result). All mappings that match are applied in insertion order.

``` ruby
require 'mustermann/mapper'

mapper = Mustermann::Mapper.new("/:page(.:format)?" => ["/:page/view.:format", "/:page/view.html"])
mapper['/foo'] # => "/foo/view.html"
mapper['/foo.xml'] # => "/foo/view.xml"
mapper['/foo/bar'] # => "/foo/bar"
```

You can also pass additional values at conversion time to supplement or override captured parameters:

``` ruby
mapper = Mustermann::Mapper.new("/:example" => "(/:prefix)?/:example.html")
mapper['/foo', prefix: 'en'] # => "/en/foo.html"
```

## Building a Mapper

Mappings can be supplied as a hash, added via `[]=`, or built with a block:

``` ruby
# Hash argument
mapper = Mustermann::Mapper.new("/:a" => "/:a.html", "/:a/:b" => "/:b/:a")

# Block (zero-argument, returns a hash)
mapper = Mustermann::Mapper.new { { "/:a" => "/:a.html" } }

# Block (one-argument, imperative)
mapper = Mustermann::Mapper.new do |m|
m["/:a"] = "/:a.html"
end

# Incremental
mapper = Mustermann::Mapper.new
mapper["/:a"] = "/:a.html"
```

The output value may be a String, a `Mustermann::Pattern`, an `Array` of either (tried in order until one expands successfully), or a `Mustermann::Expander` directly.

<a name="-mustermann-flask"></a>
# Flask Syntax for Mustermann

Expand Down Expand Up @@ -759,6 +805,43 @@ You can also pass in default options for ad hoc patterns when creating the scann
scanner = Mustermann::StringScanner.new(input, type: :shell)
```

<a name="-mustermann-to-pattern"></a>
# `to_pattern` for Mustermann

## Overview

`mustermann/to_pattern` adds a `to_pattern` method to `String`, `Symbol`, `Regexp`, `Array`, and `Mustermann::Pattern`, and provides the `Mustermann::ToPattern` mixin so you can add the same method to your own classes.

``` ruby
require 'mustermann/to_pattern'

"/foo".to_pattern # => #<Mustermann::Sinatra:"/foo">
"/foo".to_pattern(type: :rails) # => #<Mustermann::Rails:"/foo">
%r{/foo}.to_pattern # => #<Mustermann::Regular:"\\/foo">
"/foo".to_pattern.to_pattern # => #<Mustermann::Sinatra:"/foo">
```

## `Mustermann::ToPattern` mixin

Include `Mustermann::ToPattern` in any class to get a `to_pattern` method driven by its `to_s` output:

``` ruby
require 'mustermann/to_pattern'

class MyRoute
include Mustermann::ToPattern

def to_s
"/users/:id"
end
end

MyRoute.new.to_pattern # => #<Mustermann::Sinatra:"/users/:id">
MyRoute.new.to_pattern(type: :rails) # => #<Mustermann::Rails:"/users/:id">
```

If your class wraps another object (via `__getobj__`, as in `Delegator` subclasses), `to_pattern` will unwrap it before converting.

<a name="-mustermann-uri-template"></a>
# URI Template Syntax for Mustermann

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require 'mustermann'
require 'mustermann/expander'
require 'mustermann/set'

module Mustermann
# A mapper allows mapping one string to another based on pattern parsing and expanding.
Expand Down Expand Up @@ -42,9 +43,9 @@ class Mapper
# require 'mustermann/mapper'
# Mustermann::Mapper.new({"/:foo" => "/:foo.html"}, type: :rails)
def initialize(map = {}, additional_values: :ignore, **options, &block)
@map = []
@options = options
@additional_values = additional_values
@set = Set.new(use_trie: false, use_cache: false, **options)
block.arity == 0 ? update(yield) : yield(self) if block
update(map) if map
end
Expand All @@ -54,27 +55,23 @@ def initialize(map = {}, additional_values: :ignore, **options, &block)
# @param map [Hash{String, Pattern: String, Pattern, Arry<String, Pattern>, Expander}] the mapping
def update(map)
map.to_h.each_pair do |input, output|
input = Mustermann.new(input, **@options)
output = Expander.new(*output, additional_values: @additional_values, **@options) unless output.is_a? Expander
@map << [input, output]
@set.add(input, output)
end
end

# @return [Hash{Patttern: Expander}] Hash version of the mapper.
def to_h
Hash[@map]
end
def to_h = @set.patterns.to_h { [_1, @set[_1]] }

# Convert a string according to mappings. You can pass in additional params.
#
# @example mapping with and without additional parameters
# mapper = Mustermann::Mapper.new("/:example" => "(/:prefix)?/:example.html")
#
def convert(input, values = {})
@map.inject(input) do |current, (pattern, expander)|
params = pattern.params(current)
params &&= Hash[values.merge(params).map { |k,v| [k.to_s, v] }]
expander.expandable?(params) ? expander.expand(params) : current
@set.match_all(input).inject(input) do |current, m|
params = Hash[values.merge(m.params).map { |k, v| [k.to_s, v] }]
m.value.expandable?(params) ? m.value.expand(params) : current
end
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ module Mustermann
class PatternCache
# @param [Hash] pattern_options default options used for {#create_pattern}
def initialize(**pattern_options)
@cached = Set.new
@cached = ::Set.new
@mutex = Mutex.new
@pattern_options = pattern_options
end
Expand Down
1 change: 0 additions & 1 deletion mustermann-contrib/lib/mustermann/shell.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require 'mustermann'
require 'mustermann/pattern'
require 'mustermann/simple_match'

module Mustermann
# Matches strings that are identical to the pattern.
Expand Down
2 changes: 1 addition & 1 deletion mustermann-contrib/mustermann-contrib.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Gem::Specification.new do |s|
s.summary = %q{Collection of extensions for Mustermann}
s.description = %q{Adds many plugins to Mustermann}
s.license = 'MIT'
s.required_ruby_version = '>= 2.7.0'
s.required_ruby_version = '>= 3.3.0'
s.files = `git ls-files lib`.split("\n") + ['LICENSE', 'README.md']

s.add_dependency 'mustermann', Mustermann::VERSION
Expand Down
10 changes: 5 additions & 5 deletions mustermann-contrib/spec/cake_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
it { should match('/foo') .capturing foo: 'foo' }
it { should match('/bar') .capturing foo: 'bar' }
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
it { should match('/%0Afoo') .capturing foo: "\nfoo" }
it { should match('/foo%2Fbar') .capturing foo: 'foo/bar' }

it { should_not match('/foo?') }
it { should_not match('/foo/bar') }
Expand Down Expand Up @@ -81,9 +81,9 @@
end

pattern '/**' do
it { should match('/') .capturing splat: '' }
it { should match('/foo') .capturing splat: 'foo' }
it { should match('/foo/bar') .capturing splat: 'foo/bar' }
it { should match('/') .capturing splat: [''] }
it { should match('/foo') .capturing splat: ['foo'] }
it { should match('/foo/bar') .capturing splat: ['foo/bar'] }

example { pattern.params('/foo/bar') .should be == {"splat" => ["foo/bar"]} }
it { should generate_template('/{+splat}') }
Expand Down
16 changes: 8 additions & 8 deletions mustermann-contrib/spec/express_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
it { should match('/foo') .capturing foo: 'foo' }
it { should match('/bar') .capturing foo: 'bar' }
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
it { should match('/%0Afoo') .capturing foo: "\nfoo" }
it { should match('/foo%2Fbar') .capturing foo: 'foo/bar' }

it { should_not match('/foo?') }
it { should_not match('/foo/bar') }
Expand Down Expand Up @@ -90,8 +90,8 @@
it { should match('/foo') .capturing foo: 'foo' }
it { should match('/bar') .capturing foo: 'bar' }
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
it { should match('/%0Afoo') .capturing foo: "\nfoo" }
it { should match('/foo%2Fbar') .capturing foo: 'foo/bar' }
it { should match('/') }

it { should_not match('/foo?') }
Expand Down Expand Up @@ -174,15 +174,15 @@

pattern '/(.+)' do
it { should_not match('/') }
it { should match('/foo') .capturing splat: 'foo' }
it { should match('/foo/bar') .capturing splat: 'foo/bar' }
it { should match('/foo') .capturing splat: ['foo'] }
it { should match('/foo/bar') .capturing splat: ['foo/bar'] }
it { should generate_template('/{+splat}') }
end

pattern '/(foo(a|b))' do
it { should_not match('/') }
it { should match('/fooa') .capturing splat: 'fooa' }
it { should match('/foob') .capturing splat: 'foob' }
it { should match('/fooa') .capturing splat: ['fooa'] }
it { should match('/foob') .capturing splat: ['foob'] }
it { should generate_template('/{+splat}') }
end

Expand Down
24 changes: 12 additions & 12 deletions mustermann-contrib/spec/flask_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@
it { should match('/foo') .capturing foo: 'foo' }
it { should match('/bar') .capturing foo: 'bar' }
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
it { should match('/%0Afoo') .capturing foo: "\nfoo" }
it { should match('/foo%2Fbar') .capturing foo: 'foo/bar' }

it { should_not match('/foo?') }
it { should_not match('/foo/bar') }
Expand All @@ -75,8 +75,8 @@
it { should match('/foo') .capturing foo: 'foo' }
it { should match('/bar') .capturing foo: 'bar' }
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
it { should match('/%0Afoo') .capturing foo: "\nfoo" }
it { should match('/foo%2Fbar') .capturing foo: 'foo/bar' }

it { should_not match('/foo?') }
it { should_not match('/foo/bar') }
Expand All @@ -102,8 +102,8 @@
it { should match('/foo') .capturing foo: 'foo' }
it { should match('/bar') .capturing foo: 'bar' }
it { should match('/foo.bar') .capturing foo: 'foo.bar' }
it { should match('/%0Afoo') .capturing foo: '%0Afoo' }
it { should match('/foo%2Fbar') .capturing foo: 'foo%2Fbar' }
it { should match('/%0Afoo') .capturing foo: "\nfoo" }
it { should match('/foo%2Fbar') .capturing foo: 'foo/bar' }

it { should_not match('/f') }
it { should_not match('/foo?') }
Expand Down Expand Up @@ -147,7 +147,7 @@
end

pattern '/<int:foo>' do
it { should match('/42').capturing foo: '42' }
it { should match('/42').capturing foo: 42 }

it { should_not match('/1.0') }
it { should_not match('/.5') }
Expand All @@ -168,7 +168,7 @@
end

pattern '/<int:foo>' do
it { should match('/42').capturing foo: '42' }
it { should match('/42').capturing foo: 42 }

it { should_not match('/1.0') }
it { should_not match('/.5') }
Expand Down Expand Up @@ -289,9 +289,9 @@
end

pattern '/<prefix>/<float:foo>/<int:bar>' do
it { should match('/foo/42/42') .capturing foo: '42', bar: '42' }
it { should match('/foo/1.0/1') .capturing foo: '1.0', bar: '1' }
it { should match('/foo/.5/0') .capturing foo: '.5', bar: '0' }
it { should match('/foo/42/42') .capturing foo: 42.0, bar: 42 }
it { should match('/foo/1.0/1') .capturing foo: 1.0, bar: 1 }
it { should match('/foo/.5/0') .capturing foo: 0.5, bar: 0 }

it { should_not match('/foo/1/1.0') }
it { should_not match('/foo/1.0/1.0') }
Expand Down Expand Up @@ -324,7 +324,7 @@

converter = Struct.new(:convert).new(:upcase.to_proc)
pattern '/<foo:bar>', converters: { foo: converter } do
it { should match('/foo').capturing bar: 'foo' }
it { should match('/foo').capturing bar: 'FOO' }
example { pattern.params('/foo').should be == {"bar" => "FOO"} }
end

Expand Down
Loading
Loading