mustermann 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9a623efd27ea171ba18818550616788aeca18ded
4
- data.tar.gz: f8e7956944902e37495c44085662cb39cedea8a9
3
+ metadata.gz: 6b65ce2df601cc3924de9e697d1cde09019b4185
4
+ data.tar.gz: f562a51c35fdcb064b2901ccfcffe87ee6417be5
5
5
  SHA512:
6
- metadata.gz: f8e52f5e109283a3f6d9d2823b1410e3e41e3d8c3d3787e767c70fa8e7a018ff53738f994d856947c2473f8ef94c65eb56de79f51132aa729ca26a4d582ada2f
7
- data.tar.gz: f3ff266371da60a7da88f492ad2ee3a46389a1a4806c2e57886f07b7b702408388ffaf3ff18175d59c1b67fdfafb2eb5a07d496ce8219cdccc3ae5fd90f06203
6
+ metadata.gz: 700b01ee1c08b312d6914d386e0c3abcda85d4c630ef4c1393e57730d85f5e9debe3ce15c7f28670f51cedf2247e1e2ac6dff72d592ca1f009962fcc30a4248a
7
+ data.tar.gz: 85b14c218fedd69b702fa45cefdad5f54bfe87d3eb13d8c74b717caee1e219ef64252524e2ca608b1b044447ed746b049f5a48e534612ee40ef0fa503d9c8f33
@@ -1,6 +1,6 @@
1
1
  rvm:
2
2
  - 2.0.0
3
- - ruby-head
3
+ # - ruby-head
4
4
  # - jruby-head
5
5
  #matrix:
6
6
  # allow_failures:
data/README.md CHANGED
@@ -27,14 +27,6 @@ pattern = Mustermann.new('/:prefix/*.*')
27
27
  pattern.params('/a/b.c') # => { "prefix" => "a", splat => ["b", "c"] }
28
28
  ```
29
29
 
30
- Similarly, it is also possible to generate a string from a pattern by expanding it with such a hash:
31
-
32
- ``` ruby
33
- pattern = Mustermann.new('/:file(.:ext)?')
34
- pattern.expand(file: 'pony') # => "/pony"
35
- pattern.expand(file: 'pony', ext: 'jpg') # => "/pony.jpg"
36
- ```
37
-
38
30
  It's generally a good idea to reuse pattern objects, since as much computation as possible is happening during object creation, so that the actual matching or expanding is quite fast.
39
31
 
40
32
  ## Types and Options
@@ -60,6 +52,7 @@ The available types are:
60
52
  <th>Description</th>
61
53
  <th>Example</th>
62
54
  <th>Available Options</th>
55
+ <th>Additional Features</th>
63
56
  </tr>
64
57
  </thead>
65
58
  <tbody>
@@ -71,6 +64,7 @@ The available types are:
71
64
  <a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
72
65
  <a href="#uri_decode"><tt>uri_decode</tt></a>
73
66
  </td>
67
+ <td></td>
74
68
  </tr>
75
69
  <tr>
76
70
  <th><a href="#rails"><tt>rails</tt></a></th>
@@ -84,6 +78,9 @@ The available types are:
84
78
  <a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
85
79
  <a href="#uri_decode"><tt>uri_decode</tt></a>
86
80
  </td>
81
+ <td>
82
+ <a href="#pattern_expanding">Expanding</a>
83
+ </td>
87
84
  </tr>
88
85
  <tr>
89
86
  <th><a href="#shell"><tt>shell</tt></th>
@@ -93,6 +90,7 @@ The available types are:
93
90
  <a href="#ignore_unknown_options"><tt>ignore_unknown_options</tt></a>,
94
91
  <a href="#uri_decode"><tt>uri_decode</tt></a>
95
92
  </td>
93
+ <td></td>
96
94
  </tr>
97
95
  <tr>
98
96
  <th><a href="#simple"><tt>simple</tt></a></th>
@@ -104,6 +102,7 @@ The available types are:
104
102
  <a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
105
103
  <a href="#uri_decode"><tt>uri_decode</tt></a>
106
104
  </td>
105
+ <td></td>
107
106
  </tr>
108
107
  <tr>
109
108
  <th><a href="#sinatra"><tt>sinatra</tt></a></th>
@@ -117,6 +116,9 @@ The available types are:
117
116
  <a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
118
117
  <a href="#uri_decode"><tt>uri_decode</tt></a>
119
118
  </td>
119
+ <td>
120
+ <a href="#pattern_expanding">Expanding</a>
121
+ </td>
120
122
  </tr>
121
123
  <tr>
122
124
  <th><a href="#template"><tt>template</tt></a></th>
@@ -130,6 +132,9 @@ The available types are:
130
132
  <a href="#space_matches_plus"><tt>space_matches_plus</tt></a>,
131
133
  <a href="#uri_decode"><tt>uri_decode</tt></a>
132
134
  </td>
135
+ <td>
136
+ <a href="#pattern_expanding">Expanding</a>
137
+ </td>
133
138
  </tr>
134
139
  </tbody>
135
140
  </table>
@@ -257,6 +262,89 @@ end
257
262
  * It would introduce breaking changes, even though these would be minor
258
263
  * Like Sinatra 2.0, Mustermann requires Ruby 2.0 or newer
259
264
 
265
+ <a name="pattern_expanding"></a>
266
+ ## Expanding
267
+
268
+ Similarly to parsing, it is also possible to generate a string from a pattern by expanding it with a hash.
269
+ For simple expansions, you can use `Pattern#expand`.
270
+
271
+ ``` ruby
272
+ pattern = Mustermann.new('/:file(.:ext)?')
273
+ pattern.expand(file: 'pony') # => "/pony"
274
+ pattern.expand(file: 'pony', ext: 'jpg') # => "/pony.jpg"
275
+ pattern.expand(ext: 'jpg') # raises Mustermann::ExpandError
276
+ ```
277
+
278
+ Expanding can be useful for instance when implementing link helpers.
279
+
280
+ ### Expander Objects
281
+
282
+ To get fine-grained control over expansion, you can use `Mustermann::Expander` directly.
283
+
284
+ You can create an expander object directly from a string:
285
+
286
+ ``` ruby
287
+ require 'mustermann/expander'
288
+ expander = Mustermann::Expander("/:file.jpg")
289
+ expander.expand(file: 'pony') # => "/pony.jpg"
290
+
291
+ expander = Mustermann::Expander(":file(.:ext)", type: :rails)
292
+ expander.expand(file: 'pony', ext: 'jpg') # => "/pony.jpg"
293
+ ```
294
+
295
+ Or you can pass it a pattern instance:
296
+
297
+ ``` ruby
298
+ require 'mustermann'
299
+ pattern = Mustermann.new("/:file")
300
+
301
+ require 'mustermann/expander'
302
+ expander = Mustermann::Expander.new(pattern)
303
+ ```
304
+
305
+ ### Expanding Multiple Patterns
306
+
307
+ You can add patterns to an expander object via `<<`:
308
+
309
+ ``` ruby
310
+ expander = Mustermann::Expander.new
311
+ expander << "/users/:user_id"
312
+ expander << "/pages/:page_id"
313
+
314
+ expander.expand(user_id: 15) # => "/users/15"
315
+ expander.expand(page_id: 58) # => "/pages/58"
316
+ ```
317
+
318
+ You can set pattern options when creating the expander:
319
+
320
+ ``` ruby
321
+ expander = Mustermann::Expander.new(type: :template)
322
+ expander << "/users/{user_id}"
323
+ expander << "/pages/{page_id}"
324
+ ```
325
+
326
+ Additionally, it is possible to combine patterns of different types:
327
+
328
+ ``` ruby
329
+ expander = Mustermann::Expander.new
330
+ expander << Mustermann.new("/users/{user_id}", type: :template)
331
+ expander << Mustermann.new("/pages/:page_id", type: :rails)
332
+ ```
333
+
334
+ ### Handling Additional Values
335
+
336
+ The handling of additional values passed in to `expand` can be changed by setting the `additional_values` option:
337
+
338
+ ``` ruby
339
+ expander = Mustermann::Expander.new("/:slug", additional_values: :raise)
340
+ expander.expand(slug: "foo", value: "bar") # raises Mustermann::ExpandError
341
+
342
+ expander = Mustermann::Expander.new("/:slug", additional_values: :ignore)
343
+ expander.expand(slug: "foo", value: "bar") # => "/foo"
344
+
345
+ expander = Mustermann::Expander.new("/:slug", additional_values: :append)
346
+ expander.expand(slug: "foo", value: "bar") # => "/foo?value=bar"
347
+ ```
260
348
 
261
349
  ## Partial Loading and Thread Safety
262
350
 
@@ -645,13 +733,56 @@ you should set `uri_decode` to `false` in order to conform with the specificatio
645
733
 
646
734
  If you are looking for an alternative implementation that also supports expanding, check out [addressable](http://addressable.rubyforge.org/).
647
735
 
736
+ ## Routers
737
+
738
+ Mustermann comes with basic router implementations that will call certain callbacks depending on the input.
739
+
740
+ ### Simple Router
741
+
742
+ The simple router chooses callbacks based on an input string.
743
+
744
+ ``` ruby
745
+ require 'mustermann/router/simple'
746
+
747
+ router = Mustermann::Router::Simple.new(default: 42)
748
+ router.on(':name', capture: :digit) { |string| string.to_i }
749
+ router.call("23") # => 23
750
+ router.call("example") # => 42
751
+ ```
752
+
753
+ ### Rack Router
754
+
755
+ This is not a full replacement for Rails, Sinatra, Cuba, etc, as it only cares about path based routing.
756
+
757
+ ``` ruby
758
+ require 'mustermann/router/rack'
759
+
760
+ router = Mustermann::Router::Rack do
761
+ on '/' do |env|
762
+ [200, {'Content-Type' => 'text/plain'}, ['Hello World!']]
763
+ end
764
+
765
+ on '/:name' do |env|
766
+ name = env['mustermann.params']['name']
767
+ [200, {'Content-Type' => 'text/plain'}, ["Hello #{name}!"]]
768
+ end
769
+
770
+ on '/something/*', call: SomeApp
771
+ end
772
+
773
+ # in a config.ru
774
+ run router
775
+ ```
776
+
648
777
  ## Requirements
649
778
 
650
779
  Mustermann has no dependencies besides a Ruby 2.0 compatible Ruby implementation.
651
780
 
652
781
  It is known to work on **MRI 2.0** and **MRI trunk**.
653
- **JRuby** is not yet supported, but is likely to follow soon (see [issue #2](https://github.com/rkh/mustermann/issues/2) for up to date information on JRuby).
654
- **Rubinius** is not yet able to parse the Mustermann source code.
782
+
783
+ **JRuby** is not yet fully supported. It is possible to run large parts of Mustermann by passing in `--2.0 -X-C` starting from JRuby 1.7.4. See [issue #2](https://github.com/rkh/mustermann/issues/2) for up to date information.
784
+
785
+ **Rubinius** is not yet able to parse the Mustermann source code. See [issue #14](https://github.com/rkh/mustermann/issues/14) for up to date information.
655
786
 
656
787
  ## Release History
657
788
 
@@ -664,13 +795,26 @@ As there has been no stable release yet, the API might still change, though I co
664
795
 
665
796
  ### Development Releases
666
797
 
667
- * **Mustermann 0.0.1** (2013-04-27)
798
+ * **Mustermann 0.2.0** (2013-08-24)
668
799
  * More Infos:
669
- [RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.0.1),
670
- [RubyDoc.info](rubydoc.info/gems/mustermann/0.0.1/frames),
671
- [GitHub.com](https://github.com/rkh/mustermann/tree/v0.0.1)
672
- * Initial Release.
800
+ [RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.2.0),
801
+ [RubyDoc.info](http://rubydoc.info/gems/mustermann/0.2.0/frames),
802
+ [GitHub.com](https://github.com/rkh/mustermann/tree/v0.2.0)
803
+ * Add first class expander objects.
804
+ * Add params casting for expander.
805
+ * Add simple router and rack router.
806
+ * Add weak equality map to significantly improve performance.
807
+ * Fix Ruby warnings.
808
+ * Improve documentation.
809
+ * Refactor pattern validation, AST transformations.
810
+ * Increase test coverage (from 100%+ to 100%++).
811
+ * Improve JRuby compatibility.
812
+ * Work around bug in 2.0.0-p0.
673
813
  * **Mustermann 0.1.0** (2013-05-12)
814
+ * More Infos:
815
+ [RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.1.0),
816
+ [RubyDoc.info](http://rubydoc.info/gems/mustermann/0.1.0/frames),
817
+ [GitHub.com](https://github.com/rkh/mustermann/tree/v0.1.0)
674
818
  * Add `Pattern#expand` for generating strings from patterns.
675
819
  * Add better internal API for working with the AST.
676
820
  * Improved documentation.
@@ -679,9 +823,15 @@ As there has been no stable release yet, the API might still change, though I co
679
823
  * Better handling of edge cases around extend.
680
824
  * More specs to ensure API stability.
681
825
  * Largely rework internals of Sinatra, Rails and Template patterns.
826
+ * **Mustermann 0.0.1** (2013-04-27)
827
+ * More Infos:
828
+ [RubyGems.org](http://rubygems.org/gems/mustermann/versions/0.0.1),
829
+ [RubyDoc.info](http://rubydoc.info/gems/mustermann/0.0.1/frames),
830
+ [GitHub.com](https://github.com/rkh/mustermann/tree/v0.0.1)
831
+ * Initial Release.
682
832
 
683
833
  ### Upcoming Releases
684
834
 
685
- * **Mustermann 0.2.0** (next release with new features)
835
+ * **Mustermann 0.3.0** (next release with new features)
686
836
  * **Mustermann 1.0.0** (before Sinatra 2.0)
687
837
  * First stable release.
data/Rakefile CHANGED
@@ -1,4 +1,4 @@
1
- ENV['JRUBY_OPTS'] = '--2.0'
1
+ ENV['JRUBY_OPTS'] = '--2.0 -X-C'
2
2
  ENV['RBXOPT'] = '-X20'
3
3
 
4
4
  task(:spec) { ruby '-w -S rspec' }
@@ -7,14 +7,14 @@ require 'addressable/template'
7
7
 
8
8
 
9
9
  Mustermann.register(:regexp, Class.new(Mustermann::RegexpBased) {
10
- def compile(string, **options)
11
- Regexp.new(string)
10
+ def compile(**options)
11
+ Regexp.new(@string)
12
12
  end
13
13
  }, load: false)
14
14
 
15
15
  Mustermann.register(:addressable, Class.new(Mustermann::RegexpBased) {
16
- def compile(string, **options)
17
- Addressable::Template.new(string)
16
+ def compile(**options)
17
+ Addressable::Template.new(@string)
18
18
  end
19
19
  }, load: false)
20
20
 
@@ -0,0 +1,21 @@
1
+ require 'benchmark'
2
+
3
+ puts " atomic vs normal segments ".center(52, '=')
4
+
5
+ types = {
6
+ normal: /\A\/(?:a|%61)\/(?<b>[^\/\?#]+)(?:\/(?<c>[^\/\?#]+))?\Z/,
7
+ atomic: /\A\/(?:a|%61)\/(?<b>(?>[^\/\?#]+))(?:\/(?<c>(?>[^\/\?#]+)))?\Z/
8
+ }
9
+
10
+ Benchmark.bmbm do |x|
11
+ types.each do |name, regexp|
12
+ string = "/a/" << ?a * 10000 << "/" << ?a * 5000
13
+ fail unless regexp.match(string)
14
+ string << "/"
15
+ fail if regexp.match(string)
16
+
17
+ x.report name.to_s do
18
+ 100.times { regexp.match(string) }
19
+ end
20
+ end
21
+ end
@@ -81,6 +81,12 @@ module Mustermann
81
81
  pattern % values.values_at(*keys)
82
82
  end
83
83
 
84
+ # @see Mustermann::Expander#with_rest
85
+ # @!visibility private
86
+ def expandable_keys(keys)
87
+ mappings.keys.select { |k| (k - keys).empty? }.max_by(&:size) || keys
88
+ end
89
+
84
90
  # helper method for raising an error for unexpandable values
85
91
  # @!visibility private
86
92
  def error_for(values)
@@ -91,17 +97,17 @@ module Mustermann
91
97
  # @see Mustermann::AST::Translator#expand
92
98
  # @!visibility private
93
99
  def escape(string, *args)
94
- # URI::Parser is pretty slow, let's not had every string to it, even if it's uneccessary
100
+ # URI::Parser is pretty slow, let's not had every string to it, even if it's unnecessary
95
101
  string =~ /\A\w*\Z/ ? string : super
96
102
  end
97
103
 
98
- # Turns a sprintf pattern into our secret internal data strucutre.
104
+ # Turns a sprintf pattern into our secret internal data structure.
99
105
  # @!visibility private
100
106
  def pattern(string = "", *keys, **filters)
101
107
  [[keys, string, filters]]
102
108
  end
103
109
 
104
- # Creates the product of two of our secret internal data strucutres.
110
+ # Creates the product of two of our secret internal data structures.
105
111
  # @!visibility private
106
112
  def add_to(list, result)
107
113
  list << [[], ""] if list.empty?
@@ -107,7 +107,6 @@ module Mustermann
107
107
  root = new
108
108
  root.pattern = string
109
109
  root.parse(&block)
110
- #root.transform
111
110
  root
112
111
  end
113
112
  end
@@ -9,7 +9,6 @@ module Mustermann
9
9
  # @!visibility private
10
10
  class Parser
11
11
  # @param [String] string to be parsed
12
- # @param [Hash] **options parse options
13
12
  # @return [Mustermann::AST::Node] parse tree for string
14
13
  # @!visibility private
15
14
  def self.parse(string)
@@ -64,7 +63,7 @@ module Mustermann
64
63
  block ? type.parse(*args, &block) : type.new(*args)
65
64
  end
66
65
 
67
- # Create a node for a character we don't have an explicite rule for.
66
+ # Create a node for a character we don't have an explicit rule for.
68
67
  #
69
68
  # @param [String] char the character
70
69
  # @return [Mustermann::AST::Node] the node
@@ -88,9 +87,9 @@ module Mustermann
88
87
  # @return [Mustermann::AST::Node] node with suffix
89
88
  # @!visibility private
90
89
  def read_suffix(element)
91
- self.class.suffix.inject(element) do |element, (regexp, callback)|
92
- next element unless payload = scan(regexp)
93
- instance_exec(payload, element, &callback)
90
+ self.class.suffix.inject(element) do |ele, (regexp, callback)|
91
+ next ele unless payload = scan(regexp)
92
+ instance_exec(payload, ele, &callback)
94
93
  end
95
94
  end
96
95
 
@@ -122,7 +121,7 @@ module Mustermann
122
121
  # Helper for raising an exception for an unexpected character.
123
122
  # Will read character from buffer if buffer is passed in.
124
123
  #
125
- # @param [String, nil] char the unexcpected character
124
+ # @param [String, nil] char the unexpected character
126
125
  # @raise [Mustermann::ParseError, Exception]
127
126
  # @!visibility private
128
127
  def unexpected(char = getch, exception: ParseError)
@@ -2,8 +2,10 @@ require 'mustermann/ast/parser'
2
2
  require 'mustermann/ast/compiler'
3
3
  require 'mustermann/ast/transformer'
4
4
  require 'mustermann/ast/validation'
5
- require 'mustermann/ast/expander'
5
+
6
6
  require 'mustermann/regexp_based'
7
+ require 'mustermann/equality_map'
8
+ require 'mustermann/expander'
7
9
 
8
10
  module Mustermann
9
11
  # @see Mustermann::AST::Pattern
@@ -15,14 +17,9 @@ module Mustermann
15
17
 
16
18
  extend Forwardable, SingleForwardable
17
19
  single_delegate on: :parser, suffix: :parser
18
- instance_delegate %i[parser compiler transformer validation expander_class] => 'self.class'
20
+ instance_delegate %i[parser compiler transformer validation] => 'self.class'
19
21
  instance_delegate parse: :parser, transform: :transformer, validate: :validation
20
22
 
21
- # @api private
22
- # @return [#expand] expander object for pattern
23
- # @!visibility private
24
- attr_accessor :expander
25
-
26
23
  # @api private
27
24
  # @return [#parse] parser object for pattern
28
25
  # @!visibility private
@@ -53,25 +50,22 @@ module Mustermann
53
50
  Validation
54
51
  end
55
52
 
56
- # @api private
57
- # @return [#new] expander factory for pattern
58
53
  # @!visibility private
59
- def self.expander_class
60
- Expander
61
- end
62
-
63
- # @!visibility private
64
- def compile(string, **options)
65
- self.expander = expander_class.new
54
+ def compile(**options)
66
55
  options[:except] &&= parse options[:except]
67
- ast = validate(transform(parse(string)))
68
- expander.add(ast)
69
- compiler.compile(ast, **options)
56
+ compiler.compile(to_ast, **options)
70
57
  rescue CompileError => error
71
- error.message << ": %p" % string
58
+ error.message << ": %p" % @string
72
59
  raise error
73
60
  end
74
61
 
62
+ # Internal AST representation of pattern.
63
+ # @!visibility private
64
+ def to_ast
65
+ @ast_cache ||= EqualityMap.new
66
+ @ast_cache.fetch(@string) { validate(transform(parse(@string))) }
67
+ end
68
+
75
69
  # All AST-based pattern implementations support expanding.
76
70
  #
77
71
  # @example (see Mustermann::Pattern#expand)
@@ -79,8 +73,10 @@ module Mustermann
79
73
  # @return (see Mustermann::Pattern#expand)
80
74
  # @raise (see Mustermann::Pattern#expand)
81
75
  # @see Mustermann::Pattern#expand
76
+ # @see Mustermann::Expander
82
77
  def expand(**values)
83
- expander.expand(**values)
78
+ @expander ||= Mustermann::Expander.new(self)
79
+ @expander.expand(**values)
84
80
  end
85
81
 
86
82
  private :compile