haml 3.1.0.alpha.19 → 3.1.0.alpha.22

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of haml might be problematic. Click here for more details.

Files changed (33) hide show
  1. data/EDGE_GEM_VERSION +1 -1
  2. data/VERSION +1 -1
  3. data/lib/haml/precompiler.rb +1 -0
  4. data/lib/haml/template/plugin.rb +16 -6
  5. data/vendor/sass/doc-src/SASS_CHANGELOG.md +41 -0
  6. data/vendor/sass/doc-src/SASS_REFERENCE.md +37 -6
  7. data/vendor/sass/lib/sass.rb +7 -3
  8. data/vendor/sass/lib/sass/engine.rb +4 -4
  9. data/vendor/sass/lib/sass/environment.rb +24 -15
  10. data/vendor/sass/lib/sass/less.rb +31 -12
  11. data/vendor/sass/lib/sass/plugin/staleness_checker.rb +1 -1
  12. data/vendor/sass/lib/sass/script/funcall.rb +51 -9
  13. data/vendor/sass/lib/sass/script/functions.rb +189 -57
  14. data/vendor/sass/lib/sass/script/node.rb +7 -1
  15. data/vendor/sass/lib/sass/script/number.rb +21 -15
  16. data/vendor/sass/lib/sass/script/operation.rb +10 -5
  17. data/vendor/sass/lib/sass/script/parser.rb +61 -17
  18. data/vendor/sass/lib/sass/script/string.rb +2 -3
  19. data/vendor/sass/lib/sass/script/variable.rb +6 -0
  20. data/vendor/sass/lib/sass/scss/parser.rb +8 -5
  21. data/vendor/sass/lib/sass/selector/sequence.rb +2 -2
  22. data/vendor/sass/lib/sass/tree/mixin_node.rb +25 -5
  23. data/vendor/sass/lib/sass/tree/node.rb +2 -2
  24. data/vendor/sass/lib/sass/tree/prop_node.rb +9 -6
  25. data/vendor/sass/lib/sass/tree/rule_node.rb +9 -8
  26. data/vendor/sass/lib/sass/util.rb +5 -3
  27. data/vendor/sass/test/sass/conversion_test.rb +14 -0
  28. data/vendor/sass/test/sass/engine_test.rb +85 -0
  29. data/vendor/sass/test/sass/functions_test.rb +89 -0
  30. data/vendor/sass/test/sass/less_conversion_test.rb +24 -3
  31. data/vendor/sass/test/sass/script_conversion_test.rb +65 -0
  32. data/vendor/sass/test/sass/scss/scss_test.rb +63 -0
  33. metadata +2 -2
@@ -1 +1 @@
1
- 3.1.0.alpha.19
1
+ 3.1.0.alpha.22
data/VERSION CHANGED
@@ -1 +1 @@
1
- 3.1.0.alpha.19
1
+ 3.1.0.alpha.22
@@ -1,4 +1,5 @@
1
1
  require 'strscan'
2
+ require 'cgi'
2
3
  require 'haml/shared'
3
4
 
4
5
  module Haml
@@ -2,12 +2,17 @@
2
2
  # using the > 2.0.1 template handler API.
3
3
 
4
4
  module Haml
5
- class Plugin < Haml::Util.av_template_class(:Handler)
6
- if (defined?(ActionView::TemplateHandlers) &&
7
- defined?(ActionView::TemplateHandlers::Compilable)) ||
8
- (defined?(ActionView::Template) &&
9
- defined?(ActionView::Template::Handlers) &&
10
- defined?(ActionView::Template::Handlers::Compilable))
5
+ # In Rails 3.1+, template handlers don't inherit from anything. In <= 3.0, they do.
6
+ # To avoid messy logic figuring this out, we just inherit from whatever the ERB handler does.
7
+ class Plugin < Haml::Util.av_template_class(:Handlers)::ERB.superclass
8
+ if ((defined?(ActionView::TemplateHandlers) &&
9
+ defined?(ActionView::TemplateHandlers::Compilable)) ||
10
+ (defined?(ActionView::Template) &&
11
+ defined?(ActionView::Template::Handlers) &&
12
+ defined?(ActionView::Template::Handlers::Compilable))) &&
13
+ # In Rails 3.1+, we don't need to include Compilable.
14
+ Haml::Util.av_template_class(:Handlers)::ERB.include?(
15
+ Haml::Util.av_template_class(:Handlers)::Compilable)
11
16
  include Haml::Util.av_template_class(:Handlers)::Compilable
12
17
  end
13
18
 
@@ -29,6 +34,11 @@ module Haml
29
34
  Haml::Engine.new(source, options).send(:precompiled_with_ambles, [])
30
35
  end
31
36
 
37
+ # In Rails 3.1+, #call takes the place of #compile
38
+ def self.call(template)
39
+ new.compile(template)
40
+ end
41
+
32
42
  def cache_fragment(block, name = {}, options = nil)
33
43
  @view.fragment_for(block, name, options) do
34
44
  eval("_hamlout.buffer", block.binding)
@@ -7,6 +7,37 @@
7
7
 
8
8
  * Add an {Sass::Script::Functions#invert `invert` function} that takes the inverse of colors.
9
9
 
10
+ * Many performance optimizations have been made by [thedarkone](http://github.com/thedarkone).
11
+
12
+ ### Keyword Arguments
13
+
14
+ Both mixins and Sass functions now support the ability to pass in keyword arguments.
15
+ For example, with mixins:
16
+
17
+ @mixin border-radius($value, $moz: true, $webkit: true, $css3: true) {
18
+ @if $moz { -moz-border-radius: $value }
19
+ @if $webkit { -webkit-border-radius: $value }
20
+ @if $css3 { border-radius: $value }
21
+ }
22
+
23
+ @include border-radius(10px, $webkit: false);
24
+
25
+ And with functions:
26
+
27
+ p {
28
+ color: hsl($hue: 180, $saturation: 78%, lightness: 57%);
29
+ }
30
+
31
+ Keyword arguments are of the form `$name: value` and come after normal arguments.
32
+ They can be used for either optional or required arguments.
33
+ For mixins, the names are the same as the argument names for the mixins.
34
+ For functions, the names are defined along with the functions.
35
+ The argument names for the built-in functions are listed
36
+ {Sass::Script::Functions in the function documentation}.
37
+
38
+ Sass functions defined in Ruby can use the {Sass::Script::Functions.declare} method
39
+ to declare the names of the arguments they take.
40
+
10
41
  ### Backwards Incompatibilities -- Must Read!
11
42
 
12
43
  * When `@import` is given a path without `.sass`, `.scss`, or `.css` extension,
@@ -18,6 +49,16 @@
18
49
  This flag hasn't been necessary since Rails 2.0.
19
50
  Existing Rails 2.0 installations will continue to work.
20
51
 
52
+ ## 3.0.22
53
+
54
+ [Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.22).
55
+
56
+ * Remove `vendor/sass`, which snuck into the gem by mistake
57
+ and was causing trouble for Heroku users (thanks to [Jacques Crocker](http://railsjedi.com/)).
58
+
59
+ * `sass-convert` now understands better when it's acceptable
60
+ to remove parentheses from expressions.
61
+
21
62
  ## 3.0.21
22
63
 
23
64
  [Tagged on GitHub](http://github.com/nex3/haml/commit/3.0.21).
@@ -275,8 +275,9 @@ Available options are:
275
275
  This is useful to set if the Sass template is embedded in a Ruby file.
276
276
 
277
277
  {#load_paths-option} `:load_paths`
278
- : An array of filesystem paths which should be searched
278
+ : An array of filesystem paths or importers which should be searched
279
279
  for Sass templates imported with the [`@import`](#import) directive.
280
+ These may be strings, `Pathname` objects, or subclasses of {Sass::Importers::Base}.
280
281
  This defaults to the working directory and, in Rack, Rails, or Merb,
281
282
  whatever `:template_location` is.
282
283
 
@@ -851,7 +852,7 @@ SassScript defines some useful functions
851
852
  that are called using the normal CSS function syntax:
852
853
 
853
854
  p {
854
- color: hsl(0, 100%, 0.5);
855
+ color: hsl(0, 100%, 50%);
855
856
  }
856
857
 
857
858
  is compiled to:
@@ -859,7 +860,23 @@ is compiled to:
859
860
  p {
860
861
  color: #ff0000; }
861
862
 
862
- See {Sass::Script::Functions} for a full listing of Sass functions,
863
+ #### Keyword Arguments
864
+
865
+ Sass functions can also be called using explicit keyword arguments.
866
+ The above example can also be written as:
867
+
868
+ p {
869
+ color: hsl($hue: 0, $saturation: 100%, $lightness: 50%);
870
+ }
871
+
872
+ While this is less concise, it can make the stylesheet easier to read.
873
+ It also allows functions to present more flexible interfaces,
874
+ providing many arguments without becoming difficult to call.
875
+
876
+ Named arguments can be passed in any order, and arguments with default values can be omitted.
877
+ Since the named arguments are variable names, underscores and dashes can be used interchangeably.
878
+
879
+ See {Sass::Script::Functions} for a full listing of Sass functions and their argument names,
863
880
  as well as instructions on defining your own in Ruby.
864
881
 
865
882
  ### Interpolation: `#{}` {#interpolation_}
@@ -1591,6 +1608,21 @@ is compiled to:
1591
1608
  border-width: 2in;
1592
1609
  border-style: dashed; }
1593
1610
 
1611
+ #### Keyword Arguments
1612
+
1613
+ Mixins can also be included using explicit keyword arguments.
1614
+ For instance, we the above example could be written as:
1615
+
1616
+ p { @include sexy-border($color: blue); }
1617
+ h1 { @include sexy-border($color: blue, $width: 2in); }
1618
+
1619
+ While this is less concise, it can make the stylesheet easier to read.
1620
+ It also allows functions to present more flexible interfaces,
1621
+ providing many arguments without becoming difficult to call.
1622
+
1623
+ Named arguments can be passed in any order, and arguments with default values can be omitted.
1624
+ Since the named arguments are variable names, underscores and dashes can be used interchangeably.
1625
+
1594
1626
  ## Output Style
1595
1627
 
1596
1628
  Although the default CSS style that Sass outputs is very nice
@@ -1682,9 +1714,8 @@ Using these features requires a strong understanding of Ruby.
1682
1714
 
1683
1715
  ### Defining Custom Sass Functions
1684
1716
 
1685
- The same way that Sass defines new functions for use in Sass stylesheets is available
1686
- to users who which to do so. For more information see the [source
1687
- documentation](/docs/yardoc/Sass/Script/Functions.html#adding_custom_functions).
1717
+ Users can define their own Sass functions using the Ruby API.
1718
+ For more information, see the [source documentation](Sass/Script/Functions.html#adding_custom_functions).
1688
1719
 
1689
1720
  ### Cache Stores
1690
1721
 
@@ -43,17 +43,21 @@ module Sass
43
43
  # @raise [ArgumentError] if the document uses an unknown encoding with `@charset`
44
44
  #
45
45
  # @overload compile_file(filename, options = {})
46
+ # Return the compiled CSS rather than writing it to a file.
47
+ #
46
48
  # @return [String] The compiled CSS.
47
49
  #
48
50
  # @overload compile_file(filename, css_filename, options = {})
51
+ # Write the compiled CSS to a file.
52
+ #
49
53
  # @param css_filename [String] The location to which to write the compiled CSS.
50
54
  def self.compile_file(filename, *args)
51
55
  options = args.last.is_a?(Hash) ? args.pop : {}
52
- css_filename ||= args.shift
53
- options[:css_filename] = css_filename
56
+ css_filename = args.shift
54
57
  result = Sass::Engine.for_file(filename, options).render
55
58
  if css_filename
56
- open(css_filename,"w") {|css_file| css_file.write(result) }
59
+ options[:css_filename] ||= css_filename
60
+ open(css_filename,"w") {|css_file| css_file.write(result)}
57
61
  nil
58
62
  else
59
63
  result
@@ -158,8 +158,8 @@ module Sass
158
158
  options[:line_comments] ||= options[:line_numbers]
159
159
 
160
160
  options[:load_paths] = options[:load_paths].map do |p|
161
- next p unless p.is_a?(String)
162
- options[:filesystem_importer].new(p)
161
+ next p unless p.is_a?(String) || (defined?(Pathname) && p.is_a?(Pathname))
162
+ options[:filesystem_importer].new(p.to_s)
163
163
  end
164
164
 
165
165
  # Backwards compatibility
@@ -731,11 +731,11 @@ WARNING
731
731
  raise SyntaxError.new("Invalid mixin include \"#{line.text}\".") if name.nil?
732
732
 
733
733
  offset = line.offset + line.text.size - arg_string.size
734
- args = Script::Parser.new(arg_string.strip, @line, offset, @options).
734
+ args, keywords = Script::Parser.new(arg_string.strip, @line, offset, @options).
735
735
  parse_mixin_include_arglist
736
736
  raise SyntaxError.new("Illegal nesting: Nothing may be nested beneath mixin directives.",
737
737
  :line => @line + 1) unless line.children.empty?
738
- Tree::MixinNode.new(name, args)
738
+ Tree::MixinNode.new(name, args, keywords)
739
739
  end
740
740
 
741
741
  def parse_script(script, options = {})
@@ -22,12 +22,12 @@ module Sass
22
22
 
23
23
  # @param parent [Environment] See \{#parent}
24
24
  def initialize(parent = nil)
25
- @vars = {}
26
- @mixins = {}
27
25
  @parent = parent
28
- @stack = [] unless parent
29
- @mixins_in_use = Set.new unless parent
30
- set_var("important", Script::String.new("!important")) unless @parent
26
+ unless parent
27
+ @stack = []
28
+ @mixins_in_use = Set.new
29
+ set_var("important", Script::String.new("!important"))
30
+ end
31
31
  end
32
32
 
33
33
  # The options hash.
@@ -35,7 +35,7 @@ module Sass
35
35
  #
36
36
  # @return [{Symbol => Object}]
37
37
  def options
38
- @options || (parent && parent.options) || {}
38
+ @options || parent_options || {}
39
39
  end
40
40
 
41
41
  # Push a new stack frame onto the mixin/include stack.
@@ -53,13 +53,13 @@ module Sass
53
53
  # `:line`
54
54
  # : The line of the file on which the lexical scope changed. Never nil.
55
55
  def push_frame(frame_info)
56
- if stack.last && stack.last[:prepared]
57
- stack.last.delete(:prepared)
58
- stack.last.merge!(frame_info)
56
+ top_of_stack = stack.last
57
+ if top_of_stack && top_of_stack.delete(:prepared)
58
+ top_of_stack.merge!(frame_info)
59
59
  else
60
- stack.push(frame_info)
60
+ stack.push(top_of_stack = frame_info)
61
61
  end
62
- mixins_in_use << stack.last[:mixin] if stack.last[:mixin] && !stack.last[:prepared]
62
+ mixins_in_use << top_of_stack[:mixin] if top_of_stack[:mixin] && !top_of_stack[:prepared]
63
63
  end
64
64
 
65
65
  # Like \{#push\_frame}, but next time a stack frame is pushed,
@@ -93,28 +93,36 @@ module Sass
93
93
  @mixins_in_use ||= @parent.mixins_in_use
94
94
  end
95
95
 
96
+ private
97
+
98
+ def parent_options
99
+ @parent_options ||= @parent && @parent.options
100
+ end
101
+
96
102
  class << self
97
103
  private
104
+ UNDERSCORE, DASH = '_', '-'
98
105
 
99
106
  # Note: when updating this,
100
107
  # update sass/yard/inherited_hash.rb as well.
101
108
  def inherited_hash(name)
102
109
  class_eval <<RUBY, __FILE__, __LINE__ + 1
103
110
  def #{name}(name)
104
- _#{name}(name.gsub('_', '-'))
111
+ _#{name}(name.tr(UNDERSCORE, DASH))
105
112
  end
106
113
 
107
114
  def _#{name}(name)
108
- @#{name}s[name] || @parent && @parent._#{name}(name)
115
+ (@#{name}s && @#{name}s[name]) || @parent && @parent._#{name}(name)
109
116
  end
110
117
  protected :_#{name}
111
118
 
112
119
  def set_#{name}(name, value)
113
- name = name.gsub('_', '-')
120
+ name = name.tr(UNDERSCORE, DASH)
114
121
  @#{name}s[name] = value unless try_set_#{name}(name, value)
115
122
  end
116
123
 
117
124
  def try_set_#{name}(name, value)
125
+ @#{name}s ||= {}
118
126
  if @#{name}s.include?(name)
119
127
  @#{name}s[name] = value
120
128
  true
@@ -127,7 +135,8 @@ module Sass
127
135
  protected :try_set_#{name}
128
136
 
129
137
  def set_local_#{name}(name, value)
130
- @#{name}s[name.gsub('_', '-')] = value
138
+ @#{name}s ||= {}
139
+ @#{name}s[name.tr(UNDERSCORE, DASH)] = value
131
140
  end
132
141
  RUBY
133
142
  end
@@ -246,24 +246,43 @@ WARNING
246
246
  private
247
247
 
248
248
  LESS_TO_SASS_OPERATORS = {"-" => :minus, "+" => :plus, "*" => :times, "/" => :div, "=" => :single_eq}
249
+
249
250
  def _to_sass_tree(arr)
250
- return Sass::Script::UnaryOperation.new(_to_sass_tree(arr[1..-1]), :minus) if arr[0] == "-"
251
- _to_sass_tree2(*_sass_split(arr))
251
+ e, rest = _to_sass_tree_plus_minus_eq(arr)
252
+ until rest.empty?
253
+ e2, rest = _to_sass_tree_plus_minus_eq(rest)
254
+ e = Sass::Script::Operation.new(e, e2, :concat)
255
+ end
256
+ return e
252
257
  end
253
258
 
254
- def _to_sass_tree2(first, rest)
255
- return first if rest.empty?
256
- if rest[0].is_a?(Operator)
259
+ def _to_sass_tree_plus_minus_eq(arr)
260
+ e, rest = _to_sass_tree_times_div(arr)
261
+ while rest[0] && rest[0].is_a?(Operator) && %w[+ - =].include?(rest[0])
257
262
  op = LESS_TO_SASS_OPERATORS[rest[0]]
258
- if op == :times || op == :div
259
- second, rest = _sass_split(rest[1..-1])
260
- return _to_sass_tree2(Sass::Script::Operation.new(first, second, op), rest)
261
- else
262
- return Sass::Script::Operation.new(first, _to_sass_tree(rest[1..-1]), op)
263
- end
263
+ e2, rest = _to_sass_tree_times_div(rest[1..-1])
264
+ e = Sass::Script::Operation.new(e, e2, op)
264
265
  end
266
+ return e, rest
267
+ end
265
268
 
266
- Sass::Script::Operation.new(first, _to_sass_tree(rest), :concat)
269
+ def _to_sass_tree_times_div(arr)
270
+ e, rest = _to_sass_tree_unary(arr)
271
+ while rest[0] && rest[0].is_a?(Operator) && %w[* /].include?(rest[0])
272
+ op = LESS_TO_SASS_OPERATORS[rest[0]]
273
+ e2, rest = _to_sass_tree_unary(rest[1..-1])
274
+ e = Sass::Script::Operation.new(e, e2, op)
275
+ end
276
+ return e, rest
277
+ end
278
+
279
+ def _to_sass_tree_unary(arr)
280
+ if arr[0] == "-"
281
+ first, rest = _sass_split(arr[1..-1])
282
+ return Sass::Script::UnaryOperation.new(first, :minus), rest
283
+ else
284
+ return _sass_split(arr[0..-1])
285
+ end
267
286
  end
268
287
 
269
288
  def _sass_split(arr)
@@ -121,7 +121,7 @@ module Sass
121
121
  end
122
122
 
123
123
  def dependency_updated?(css_mtime)
124
- lambda do |uri, importer|
124
+ Proc.new do |uri, importer|
125
125
  mtime(uri, importer) > css_mtime ||
126
126
  dependencies_stale?(uri, importer, css_mtime)
127
127
  end
@@ -1,4 +1,5 @@
1
- require File.join(File.dirname(__FILE__), 'functions')
1
+ require 'sass/script/functions'
2
+
2
3
  module Sass
3
4
  module Script
4
5
  # A SassScript parse node representing a function call.
@@ -17,6 +18,11 @@ module Sass
17
18
  # @return [Array<Script::Node>]
18
19
  attr_reader :args
19
20
 
21
+ # The keyword arguments to the function.
22
+ #
23
+ # @return [{String => Script::Node}]
24
+ attr_reader :keywords
25
+
20
26
  # Don't set the context for child nodes if this is `url()`,
21
27
  # since `url()` allows quoted strings.
22
28
  #
@@ -27,21 +33,29 @@ module Sass
27
33
  end
28
34
 
29
35
  # @param name [String] See \{#name}
30
- # @param name [Array<Script::Node>] See \{#args}
31
- def initialize(name, args)
36
+ # @param args [Array<Script::Node>] See \{#args}
37
+ # @param keywords [{String => Script::Node}] See \{#keywords}
38
+ def initialize(name, args, keywords)
32
39
  @name = name
33
40
  @args = args
41
+ @keywords = keywords
34
42
  super()
35
43
  end
36
44
 
37
45
  # @return [String] A string representation of the function call
38
46
  def inspect
39
- "#{name}(#{args.map {|a| a.inspect}.join(', ')})"
47
+ args = @args.map {|a| a.inspect}.join(', ')
48
+ keywords = @keywords.sort_by {|k, v| k}.
49
+ map {|k, v| "$#{k}: #{v.inspect}"}.join(', ')
50
+ "#{name}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
40
51
  end
41
52
 
42
53
  # @see Node#to_sass
43
54
  def to_sass(opts = {})
44
- "#{dasherize(name, opts)}(#{args.map {|a| a.to_sass(opts)}.join(', ')})"
55
+ args = @args.map {|a| a.to_sass(opts)}.join(', ')
56
+ keywords = @keywords.sort_by {|k, v| k}.
57
+ map {|k, v| "$#{dasherize(k, opts)}: #{v.to_sass(opts)}"}.join(', ')
58
+ "#{dasherize(name, opts)}(#{args}#{', ' unless args.empty? || keywords.empty?}#{keywords})"
45
59
  end
46
60
 
47
61
  # Returns the arguments to the function.
@@ -49,7 +63,7 @@ module Sass
49
63
  # @return [Array<Node>]
50
64
  # @see Node#children
51
65
  def children
52
- @args
66
+ @args + @keywords.values
53
67
  end
54
68
 
55
69
  protected
@@ -60,10 +74,13 @@ module Sass
60
74
  # @return [Literal] The SassScript object that is the value of the function call
61
75
  # @raise [Sass::SyntaxError] if the function call raises an ArgumentError
62
76
  def _perform(environment)
63
- args = self.args.map {|a| a.perform(environment)}
64
- ruby_name = name.gsub('-', '_')
77
+ args = @args.map {|a| a.perform(environment)}
78
+ keywords = Sass::Util.map_hash(@keywords) {|k, v| [k, v.perform(environment)]}
79
+ ruby_name = @name.tr('-', '_')
80
+ args = construct_ruby_args(ruby_name, args, keywords)
81
+
65
82
  unless Sass::Util.has?(:public_instance_method, Functions, ruby_name) && ruby_name !~ /^__/
66
- opts(Script::String.new("#{name}(#{args.map {|a| a.perform(environment)}.join(', ')})"))
83
+ opts(Script::String.new("#{name}(#{args.join(', ')})"))
67
84
  else
68
85
  opts(Functions::EvaluationContext.new(environment.options).send(ruby_name, *args))
69
86
  end
@@ -71,6 +88,31 @@ module Sass
71
88
  raise e unless e.backtrace.any? {|t| t =~ /:in `(block in )?(#{name}|perform)'$/}
72
89
  raise Sass::SyntaxError.new("#{e.message} for `#{name}'")
73
90
  end
91
+
92
+ def construct_ruby_args(name, args, keywords)
93
+ return args if keywords.empty?
94
+ unless signature = Functions.signature(name.to_sym, args.size, keywords.size)
95
+ raise Sass::SyntaxError.new("Function #{name} doesn't support keyword arguments")
96
+ end
97
+
98
+ args = args + signature.args[args.size..-1].map do |argname|
99
+ if keywords.has_key?(argname)
100
+ keywords.delete(argname)
101
+ else
102
+ raise Sass::SyntaxError, "Function #{name} requires an argument named $#{argname}"
103
+ end
104
+ end
105
+
106
+ if keywords.size > 0
107
+ if signature.var_kwargs
108
+ args << Sass::Util.map_hash(keywords) {|k, v| [k.to_sym, v]}
109
+ else
110
+ raise Sass::SyntaxError, "Function #{name} doesn't take an argument named $#{keywords.keys.sort.first}"
111
+ end
112
+ end
113
+
114
+ args
115
+ end
74
116
  end
75
117
  end
76
118
  end