parameter_substitution 0.3.0.pre.1 → 0.3.0.pre.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/lib/parameter_substitution/configuration.rb +39 -0
  3. data/lib/parameter_substitution/context.rb +74 -0
  4. data/lib/parameter_substitution/encoder.rb +86 -0
  5. data/lib/parameter_substitution/expression.rb +146 -0
  6. data/lib/parameter_substitution/formatters/add_prefix.rb +19 -0
  7. data/lib/parameter_substitution/formatters/base.rb +46 -0
  8. data/lib/parameter_substitution/formatters/blank_if_nil.rb +11 -0
  9. data/lib/parameter_substitution/formatters/cgi_unescape.rb +11 -0
  10. data/lib/parameter_substitution/formatters/compare_string.rb +21 -0
  11. data/lib/parameter_substitution/formatters/date_time_custom.rb +25 -0
  12. data/lib/parameter_substitution/formatters/date_time_format.rb +53 -0
  13. data/lib/parameter_substitution/formatters/date_time_iso8601.rb +11 -0
  14. data/lib/parameter_substitution/formatters/date_time_iso8601_zulu.rb +11 -0
  15. data/lib/parameter_substitution/formatters/date_time_strftime.rb +19 -0
  16. data/lib/parameter_substitution/formatters/date_time_unix_timestamp.rb +11 -0
  17. data/lib/parameter_substitution/formatters/date_time_us_all_slashes.rb +11 -0
  18. data/lib/parameter_substitution/formatters/date_time_us_normal.rb +11 -0
  19. data/lib/parameter_substitution/formatters/date_time_us_seconds.rb +11 -0
  20. data/lib/parameter_substitution/formatters/date_time_us_short_am_pm.rb +11 -0
  21. data/lib/parameter_substitution/formatters/date_time_us_short_year.rb +11 -0
  22. data/lib/parameter_substitution/formatters/date_time_utc_year_first_dashes_seconds.rb +11 -0
  23. data/lib/parameter_substitution/formatters/date_us_dashes.rb +11 -0
  24. data/lib/parameter_substitution/formatters/date_us_normal.rb +11 -0
  25. data/lib/parameter_substitution/formatters/date_year_first_dashes.rb +11 -0
  26. data/lib/parameter_substitution/formatters/downcase.rb +11 -0
  27. data/lib/parameter_substitution/formatters/duration_as_seconds.rb +11 -0
  28. data/lib/parameter_substitution/formatters/duration_as_time.rb +11 -0
  29. data/lib/parameter_substitution/formatters/duration_grouped_by_description.rb +23 -0
  30. data/lib/parameter_substitution/formatters/greater_than_value.rb +21 -0
  31. data/lib/parameter_substitution/formatters/if_nil.rb +19 -0
  32. data/lib/parameter_substitution/formatters/in_timezone.rb +23 -0
  33. data/lib/parameter_substitution/formatters/json_parse.rb +26 -0
  34. data/lib/parameter_substitution/formatters/left.rb +19 -0
  35. data/lib/parameter_substitution/formatters/lookup.rb +19 -0
  36. data/lib/parameter_substitution/formatters/lower.rb +11 -0
  37. data/lib/parameter_substitution/formatters/manager.rb +35 -0
  38. data/lib/parameter_substitution/formatters/md5.rb +11 -0
  39. data/lib/parameter_substitution/formatters/mid.rb +20 -0
  40. data/lib/parameter_substitution/formatters/parse_time.rb +23 -0
  41. data/lib/parameter_substitution/formatters/right.rb +23 -0
  42. data/lib/parameter_substitution/formatters/sha256.rb +11 -0
  43. data/lib/parameter_substitution/formatters/split_after_colon.rb +11 -0
  44. data/lib/parameter_substitution/formatters/split_and_find.rb +20 -0
  45. data/lib/parameter_substitution/formatters/split_before_colon.rb +11 -0
  46. data/lib/parameter_substitution/formatters/time_with_seconds.rb +11 -0
  47. data/lib/parameter_substitution/formatters/trim.rb +11 -0
  48. data/lib/parameter_substitution/formatters/upper.rb +11 -0
  49. data/lib/parameter_substitution/method_call_expression.rb +58 -0
  50. data/lib/parameter_substitution/parse_error.rb +6 -0
  51. data/lib/parameter_substitution/parser.rb +109 -0
  52. data/lib/parameter_substitution/substitution_expression.rb +79 -0
  53. data/lib/parameter_substitution/text_expression.rb +52 -0
  54. data/lib/parameter_substitution/transform.rb +99 -0
  55. data/lib/parameter_substitution/version.rb +5 -0
  56. metadata +56 -2
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::DateTimeUtcYearFirstDashesSeconds < ParameterSubstitution::Formatters::DateTimeFormat
4
+ def self.description
5
+ "2017-04-11 15:55:10 (UTC)"
6
+ end
7
+
8
+ def self.format(value)
9
+ parse_to_time(value)&.utc&.strftime('%Y-%m-%d %H:%M:%S')
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::DateUsDashes < ParameterSubstitution::Formatters::DateTimeFormat
4
+ def self.description
5
+ "MM-DD-YYYY"
6
+ end
7
+
8
+ def self.format(value)
9
+ parse_to_time(value)&.strftime("%m-%d-%Y").to_s
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::DateUsNormal < ParameterSubstitution::Formatters::DateTimeFormat
4
+ def self.description
5
+ "MM/DD/YYYY"
6
+ end
7
+
8
+ def self.format(value)
9
+ parse_to_time(value)&.strftime("%m/%d/%Y").to_s
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::DateYearFirstDashes < ParameterSubstitution::Formatters::DateTimeFormat
4
+ def self.description
5
+ "YYYY-MM-DD"
6
+ end
7
+
8
+ def self.format(value)
9
+ parse_to_time(value)&.strftime("%Y-%m-%d").to_s
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Downcase < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Converts to string and downcases the values, preserves nil."
6
+ end
7
+
8
+ def self.format(value)
9
+ value&.to_s&.downcase
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::DurationAsSeconds < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Converts a duration to an integer value of seconds"
6
+ end
7
+
8
+ def self.format(value)
9
+ parse_duration(value)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::DurationAsTime < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Converts a duration in seconds into hh:mm:ss"
6
+ end
7
+
8
+ def self.format(duration)
9
+ Time.at(parse_duration(duration)).utc.strftime("%H:%M:%S")
10
+ end
11
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::DurationGroupedByDescription < ParameterSubstitution::Formatters::Base
4
+ DURATION_DESCRIPTIONS = [
5
+ [30.seconds, "<30sec"],
6
+ [60.seconds, "30-60sec"],
7
+ [5.minutes, "1-5min"],
8
+ [10.minutes, "5-10min"],
9
+ [20.minutes, "10-20min"],
10
+ [30.minutes, "20-30min"],
11
+ [60.minutes, "30-60min"],
12
+ [nil, ">60min"]
13
+ ].freeze
14
+
15
+ def self.description
16
+ "Converts a duration in seconds into one of the following: #{DURATION_DESCRIPTIONS.map(&:last).join(',')}"
17
+ end
18
+
19
+ def self.format(duration)
20
+ fixed_duration = parse_duration(duration)
21
+ DURATION_DESCRIPTIONS.find { |max_duration, _description| !max_duration || fixed_duration.to_i < max_duration }.last
22
+ end
23
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::GreaterThanValue < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Compares numerical values and returns results based on the comparison"
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(compare_value, true_value, false_value)
13
+ @compare_value = compare_value
14
+ @true_value = true_value
15
+ @false_value = false_value
16
+ end
17
+
18
+ def format(value)
19
+ self.class.parse_duration(value) > @compare_value.to_i ? @true_value : @false_value
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::IfNil < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Takes one new_value parameter. If the input is nil, the input is replaced with new_value."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(new_value)
13
+ @new_value = new_value
14
+ end
15
+
16
+ def format(value)
17
+ value.nil? ? @new_value : value
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::InTimezone < ParameterSubstitution::Formatters::DateTimeFormat
4
+ def self.description
5
+ "Converts a date time to the specified time zone."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(destination_timezone)
13
+ @destination_timezone = destination_timezone
14
+ end
15
+
16
+ def format(value)
17
+ if (value_as_time = self.class.parse_to_time(value))
18
+ value_as_time.in_time_zone(@destination_timezone).strftime('%Y-%m-%d %H:%M:%S')
19
+ else
20
+ value
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::JsonParse < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Attempts to parse strings as JSON. If valid, passes along the parsed object, if not valid json, or not a string, passes the json encoded value."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ false
10
+ end
11
+
12
+ def self.encoding
13
+ :json
14
+ end
15
+
16
+ def self.format(value)
17
+ if value.is_a?(String)
18
+ JSON.parse(value)
19
+ value
20
+ else
21
+ value.to_json
22
+ end
23
+ rescue JSON::ParserError
24
+ value.to_json
25
+ end
26
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Left < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Takes a single n argument. Returns the left most n characters from the input."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(character_count)
13
+ @character_count = character_count
14
+ end
15
+
16
+ def format(value)
17
+ value.to_s[0, @character_count]
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Lookup < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "This takes a table as a constructor parameter and performs a lookup from the value. If the value exists as a key in the lookup table, the key's value is returned."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(lookup_table)
13
+ @lookup_table = lookup_table
14
+ end
15
+
16
+ def format(value)
17
+ @lookup_table[value.to_s]
18
+ end
19
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Lower < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Converts to string and returns all characters lowercased, preserves nil."
6
+ end
7
+
8
+ def self.format(value)
9
+ value&.to_s&.downcase
10
+ end
11
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution
4
+ module Formatters
5
+ class Manager
6
+ class << self
7
+ def find(key)
8
+ all_formats[key.to_s]
9
+ end
10
+
11
+ def all_formats
12
+ default_formats.merge(custom_formatters_if_any)
13
+ end
14
+
15
+ def default_formats
16
+ @default_formats ||= formatter_class_hash(__FILE__, ["ParameterSubstitution", "Formatters"])
17
+ end
18
+
19
+ private
20
+
21
+ def formatter_class_hash(manager_file, module_array)
22
+ Hash[Dir[Pathname.new(manager_file).dirname + '*.rb'].map do |filename|
23
+ class_key = File.basename(filename).chomp(".rb")
24
+ class_name = (module_array + [class_key.camelize.to_s]).join('::').constantize
25
+ [class_key, class_name]
26
+ end].except("manager")
27
+ end
28
+
29
+ def custom_formatters_if_any
30
+ ParameterSubstitution.config&.custom_formatters || {}
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Md5 < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Generates an md5 hash of the value."
6
+ end
7
+
8
+ def self.format(value)
9
+ Digest::MD5.hexdigest(value.to_s)
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Mid < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Takes starting_position and character_count as arguments. Returns the character_count characters starting from starting_position from the input."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(starting_position, character_count)
13
+ @starting_position = starting_position
14
+ @character_count = character_count
15
+ end
16
+
17
+ def format(value)
18
+ value.to_s[@starting_position, @character_count]
19
+ end
20
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::ParseTime < ParameterSubstitution::Formatters::DateTimeFormat
4
+ def self.description
5
+ "Takes format_string as a parameter and uses format_string to parse the input as a time. Does not change the input if the value is not a time."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(format_string)
13
+ @format_string = format_string
14
+ end
15
+
16
+ def format(value)
17
+ value && Time.strptime(value.to_s, @format_string).strftime('%Y-%m-%d %H:%M:%S')
18
+ rescue ArgumentError => ex
19
+ # strptime raises argument error if either argument is wrong.
20
+ ex.message =~ /invalid strptime format/ or raise
21
+ value
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Right < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Takes a single n argument. Returns the right most n characters from the input."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(character_count)
13
+ @character_count = character_count
14
+ end
15
+
16
+ def format(value)
17
+ if (as_string = value.to_s).size > @character_count
18
+ as_string[-@character_count, @character_count]
19
+ else
20
+ as_string
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Sha256 < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Generates a sha256 hash of the value."
6
+ end
7
+
8
+ def self.format(value)
9
+ Digest::SHA256.hexdigest(value.to_s)
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::SplitAfterColon < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Returns the portion of a string after the first colon, or nil if there is no colon."
6
+ end
7
+
8
+ def self.format(value)
9
+ value && value.to_s.split(':', 2)[1]&.lstrip
10
+ end
11
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::SplitAndFind < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Takes delimiter and index as arguments. Splits the input on delimiters and returns the indexed element. Preserves nil."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(delimiter, index)
13
+ @delimiter = delimiter
14
+ @index = index
15
+ end
16
+
17
+ def format(value)
18
+ value && value.to_s.split(@delimiter)[@index]
19
+ end
20
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::SplitBeforeColon < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Returns the portion of a string before the first colon, or the full string if there is no colon."
6
+ end
7
+
8
+ def self.format(value)
9
+ value && value.to_s.split(':')[0]
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::TimeWithSeconds < ParameterSubstitution::Formatters::DateTimeFormat
4
+ def self.description
5
+ "hh:mm:ss"
6
+ end
7
+
8
+ def self.format(value)
9
+ parse_to_time(value)&.strftime("%H:%M:%S").to_s
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Trim < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Returns the input as a string with leading and trailing whitespace removed."
6
+ end
7
+
8
+ def self.format(value)
9
+ value&.to_s&.strip
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::Upper < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Converts to string and returns all characters uppercased, preserves nil."
6
+ end
7
+
8
+ def self.format(value)
9
+ value&.to_s&.upcase
10
+ end
11
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution
4
+ class MethodCallExpression
5
+ attr_reader :name, :arguments
6
+
7
+ def initialize(name, arguments)
8
+ @name = name
9
+ @arguments = arguments || []
10
+ end
11
+
12
+ def validate
13
+ if format_class
14
+ expected_arguments = format_class&.has_parameters? ? format_class.instance_method(:initialize).arity : 0
15
+ if @arguments.size != expected_arguments
16
+ raise ParameterSubstitution::ParseError, "Wrong number of arguments for '#{@name}' expected #{expected_arguments}, received #{@arguments.size}"
17
+ end
18
+ else
19
+ raise ParameterSubstitution::ParseError, "Unknown method '#{@name}'"
20
+ end
21
+ end
22
+
23
+ def call_method(value)
24
+ column_formatter.format(value)
25
+ end
26
+
27
+ def encoding
28
+ format_class.encoding
29
+ end
30
+
31
+ private
32
+
33
+ def column_formatter
34
+ @column_formatter ||= begin
35
+ if format_class&.has_parameters?
36
+ format_class.new(*format_args)
37
+ else
38
+ format_class
39
+ end
40
+ end
41
+ end
42
+
43
+ def format_class
44
+ @format_class ||= ParameterSubstitution::Formatters::Manager.find(@name)
45
+ end
46
+
47
+ def format_args
48
+ @arguments.map do |arg|
49
+ case arg
50
+ when ParameterSubstitution::Expression
51
+ arg.evaluate
52
+ else
53
+ arg
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution
4
+ class ParseError < StandardError
5
+ end
6
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'parslet'
4
+ #
5
+ # The parser describes the rules of the grammar, and when executed on a string it will
6
+ # either fail with an exception or it will return a structure describing what it found.
7
+ #
8
+ # See the parslet documentation for more detail.
9
+ #
10
+
11
+ class ParameterSubstitution
12
+ class Parser < Parslet::Parser
13
+ def initialize(parameter_start: "<", parameter_end: ">", allow_unmatched_parameter_end: false)
14
+ @parameter_start = parameter_start
15
+ @parameter_end = parameter_end
16
+ @allow_unmatched_parameter_end = allow_unmatched_parameter_end
17
+ super()
18
+ end
19
+
20
+ root :text_with_substitution_parameters
21
+
22
+ rule(:double_quote) { str("\"") }
23
+ rule(:single_quote) { str("\'") }
24
+ rule(:escape) { str("\\") }
25
+ rule(:dot) { str(".") }
26
+ rule(:open_param) { str(@parameter_start) }
27
+ rule(:close_param) { str(@parameter_end) }
28
+ rule(:digit) { match["0-9"] }
29
+ rule(:parameter_start) { str("(") }
30
+ rule(:parameter_end) { str(")") }
31
+ rule(:space) { str(" ") }
32
+ rule(:space?) { space.repeat }
33
+ rule(:comma) { space? >> str(",") >> space? }
34
+
35
+ rule :double_quoted_string_arg do
36
+ double_quote >> (escape >> any | double_quote.absent? >> any).repeat.as(:string_arg) >> double_quote
37
+ end
38
+
39
+ rule :single_quoted_string_arg do
40
+ single_quote >> (escape >> any | single_quote.absent? >> any).repeat.as(:string_arg) >> single_quote
41
+ end
42
+
43
+ rule :string_arg do
44
+ double_quoted_string_arg | single_quoted_string_arg
45
+ end
46
+
47
+ rule :float_arg do
48
+ (digit.repeat(1) >> dot >> digit.repeat(1)).as(:float_arg)
49
+ end
50
+
51
+ rule :int_arg do
52
+ digit.repeat(1).as(:int_arg)
53
+ end
54
+
55
+ rule :nil_arg do
56
+ str('nil').as(:nil_arg)
57
+ end
58
+
59
+ rule :substitution_parameter_arg do
60
+ open_param >> parameter.repeat.as(:raw_expression) >> close_param
61
+ end
62
+
63
+ rule :arg do
64
+ float_arg | string_arg | int_arg | nil_arg | substitution_parameter_arg
65
+ end
66
+
67
+ rule :argument_list do
68
+ parameter_start >> space? >> (arg >> (comma >> arg).repeat).repeat(0, 1).as(:arg_list) >> space? >> parameter_end
69
+ end
70
+
71
+ rule :method_name do
72
+ (parameter_start.absent? >> close_param.absent? >> dot.absent? >> any).repeat(1).as(:method_call)
73
+ end
74
+
75
+ rule :method_call do
76
+ method_name >> argument_list.maybe
77
+ end
78
+
79
+ rule :method_call_list do
80
+ (dot >> method_call).repeat(0)
81
+ end
82
+
83
+ rule :parameter_name do
84
+ ((escape >> any) | (open_param.absent? >> close_param.absent? >> dot.absent? >> any)).repeat(1)
85
+ end
86
+
87
+ rule :parameter do
88
+ parameter_name.as(:parameter_name) >> method_call_list.as(:method_calls)
89
+ end
90
+
91
+ rule :substitution_parameter_with_brackets do
92
+ open_param >> parameter >> close_param
93
+ end
94
+
95
+ rule :text do
96
+ text_char =
97
+ if @allow_unmatched_parameter_end
98
+ (open_param.absent? >> any)
99
+ else
100
+ (open_param.absent? >> close_param.absent? >> any)
101
+ end
102
+ text_char.repeat(1).as(:text)
103
+ end
104
+
105
+ rule :text_with_substitution_parameters do
106
+ (text | substitution_parameter_with_brackets).repeat.as(:expression)
107
+ end
108
+ end
109
+ end