parameter_substitution 0.3.0.pre.1 → 1.1.0.pre.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. checksums.yaml +5 -5
  2. data/lib/parameter_substitution.rb +1 -2
  3. data/lib/parameter_substitution/configuration.rb +39 -0
  4. data/lib/parameter_substitution/context.rb +74 -0
  5. data/lib/parameter_substitution/encoder.rb +86 -0
  6. data/lib/parameter_substitution/expression.rb +146 -0
  7. data/lib/parameter_substitution/formatters/add_prefix.rb +19 -0
  8. data/lib/parameter_substitution/formatters/base.rb +46 -0
  9. data/lib/parameter_substitution/formatters/blank_if_nil.rb +11 -0
  10. data/lib/parameter_substitution/formatters/cgi_unescape.rb +11 -0
  11. data/lib/parameter_substitution/formatters/compare_string.rb +21 -0
  12. data/lib/parameter_substitution/formatters/date_time_custom.rb +22 -0
  13. data/lib/parameter_substitution/formatters/date_time_format.rb +53 -0
  14. data/lib/parameter_substitution/formatters/date_time_iso8601.rb +11 -0
  15. data/lib/parameter_substitution/formatters/date_time_iso8601_zulu.rb +11 -0
  16. data/lib/parameter_substitution/formatters/date_time_strftime.rb +19 -0
  17. data/lib/parameter_substitution/formatters/date_time_unix_timestamp.rb +11 -0
  18. data/lib/parameter_substitution/formatters/date_time_us_all_slashes.rb +11 -0
  19. data/lib/parameter_substitution/formatters/date_time_us_normal.rb +11 -0
  20. data/lib/parameter_substitution/formatters/date_time_us_seconds.rb +11 -0
  21. data/lib/parameter_substitution/formatters/date_time_us_short_am_pm.rb +11 -0
  22. data/lib/parameter_substitution/formatters/date_time_us_short_year.rb +11 -0
  23. data/lib/parameter_substitution/formatters/date_time_utc_year_first_dashes_seconds.rb +11 -0
  24. data/lib/parameter_substitution/formatters/date_us_dashes.rb +11 -0
  25. data/lib/parameter_substitution/formatters/date_us_normal.rb +11 -0
  26. data/lib/parameter_substitution/formatters/date_year_first_dashes.rb +11 -0
  27. data/lib/parameter_substitution/formatters/downcase.rb +11 -0
  28. data/lib/parameter_substitution/formatters/duration_as_seconds.rb +11 -0
  29. data/lib/parameter_substitution/formatters/duration_as_time.rb +11 -0
  30. data/lib/parameter_substitution/formatters/duration_grouped_by_description.rb +23 -0
  31. data/lib/parameter_substitution/formatters/greater_than_value.rb +21 -0
  32. data/lib/parameter_substitution/formatters/if_nil.rb +19 -0
  33. data/lib/parameter_substitution/formatters/if_truthy.rb +36 -0
  34. data/lib/parameter_substitution/formatters/in_timezone.rb +23 -0
  35. data/lib/parameter_substitution/formatters/json_parse.rb +26 -0
  36. data/lib/parameter_substitution/formatters/left.rb +19 -0
  37. data/lib/parameter_substitution/formatters/lookup.rb +19 -0
  38. data/lib/parameter_substitution/formatters/lower.rb +11 -0
  39. data/lib/parameter_substitution/formatters/manager.rb +35 -0
  40. data/lib/parameter_substitution/formatters/md5.rb +11 -0
  41. data/lib/parameter_substitution/formatters/mid.rb +20 -0
  42. data/lib/parameter_substitution/formatters/parse_time.rb +23 -0
  43. data/lib/parameter_substitution/formatters/right.rb +23 -0
  44. data/lib/parameter_substitution/formatters/sha256.rb +11 -0
  45. data/lib/parameter_substitution/formatters/split_after_colon.rb +11 -0
  46. data/lib/parameter_substitution/formatters/split_and_find.rb +20 -0
  47. data/lib/parameter_substitution/formatters/split_before_colon.rb +11 -0
  48. data/lib/parameter_substitution/formatters/time_with_seconds.rb +11 -0
  49. data/lib/parameter_substitution/formatters/trim.rb +11 -0
  50. data/lib/parameter_substitution/formatters/upper.rb +11 -0
  51. data/lib/parameter_substitution/method_call_expression.rb +58 -0
  52. data/lib/parameter_substitution/parse_error.rb +6 -0
  53. data/lib/parameter_substitution/parser.rb +109 -0
  54. data/lib/parameter_substitution/substitution_expression.rb +79 -0
  55. data/lib/parameter_substitution/text_expression.rb +52 -0
  56. data/lib/parameter_substitution/transform.rb +99 -0
  57. data/lib/parameter_substitution/version.rb +5 -0
  58. metadata +117 -10
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 6cc065caf5495a6eb293189475b549b2143a422f
4
- data.tar.gz: cb4cc3f82eb1ecd6163ad7cbf432cc2454f0e497
2
+ SHA256:
3
+ metadata.gz: 01e0aa08d4f8130fd493c54e5958aed36159746476fa168180407401584aaaf6
4
+ data.tar.gz: 4bc6bd8c7b94edc3690c474f8b7ece56d6e9aadf1d825e14bcc7413a2b1fe7a9
5
5
  SHA512:
6
- metadata.gz: dead877cadef6706c2f0bfc833ce677c45c816e786a33b3ca9a32e4b9a3f12e9c473dfda8bef88c4dbb296b97b80d3811327eaa5ee61d1c6b65ffbc014840aa4
7
- data.tar.gz: 713b75455392a1dcd45326d8a1d6143b4de25177e5036040e50e3404a29107f5058109c2bf04878bb29cb517358d5ea4774703654489542ecc9af9f94be30c83
6
+ metadata.gz: e8c2436cdce9ccaf16e02fe368aaa22337d43af01cdd1c35677a7fccf8af19db089c7ebfffbf859b52b3188b7323b884d8ed7cc57f63935afc7fb2d3f1075829
7
+ data.tar.gz: 1b1b2548aaa1a4e6ed4940c4589e65f3247d76ca9aa07089d0c7cde701acecb1704b12018b57b1661ce89d4b47416c337c9ee3aaab3a7b7c208b8f88263349ae
@@ -2,6 +2,7 @@
2
2
 
3
3
  # See lib/parameter_substitution/readme.md
4
4
 
5
+ require 'active_support/all'
5
6
  require "parameter_substitution/context"
6
7
  require "parameter_substitution/parse_error"
7
8
  require "parameter_substitution/parser"
@@ -18,8 +19,6 @@ Dir[File.dirname(__FILE__) + '/parameter_substitution/formatters/*.rb'].each do
18
19
  require file
19
20
  end
20
21
 
21
- require 'active_support/all'
22
-
23
22
  class ParameterSubstitution
24
23
  class SubstitutionError < StandardError; end
25
24
 
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution
4
+ class Configuration
5
+ attr_reader :custom_formatters
6
+
7
+ def custom_formatters=(custom_formatters)
8
+ check_that_classes_are_classes(custom_formatters)
9
+ check_for_correct_base_class(custom_formatters)
10
+
11
+ @custom_formatters = custom_formatters
12
+ end
13
+
14
+ private
15
+
16
+ def check_that_classes_are_classes(custom_formatters)
17
+ bad_formatters = custom_formatters.reject do |_formatter, klass|
18
+ klass.is_a?(Class)
19
+ end
20
+
21
+ raise_if_any(bad_formatters, "must be of type Class")
22
+ end
23
+
24
+ def check_for_correct_base_class(custom_formatters)
25
+ bad_formatters = custom_formatters.reject do |_formatter, klass|
26
+ klass.ancestors.include?(ParameterSubstitution::Formatters::Base)
27
+ end
28
+
29
+ raise_if_any(bad_formatters, "must inherit from ParameterSubstitution::Formatters::Base and did not")
30
+ end
31
+
32
+ def raise_if_any(formatters, failure_context)
33
+ if formatters.any?
34
+ log_context = formatters.map { |formatter, klass| [formatter, klass].join(": ") }.join(", ")
35
+ raise StandardError, "CONFIGURATION ERROR: custom_formatters (#{log_context}) #{failure_context}."
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'invoca/utils/enumerable'
4
+
5
+ class ParameterSubstitution
6
+ class Context
7
+ attr_reader :input, :mapping, :required_parameters, :parameter_start, :parameter_end,
8
+ :destination_encoding, :allow_unknown_replacement_parameters, :allow_nil,
9
+ :allow_unmatched_parameter_end
10
+
11
+ def initialize(
12
+ input:,
13
+ mapping:,
14
+ required_parameters: [],
15
+ parameter_start: "<",
16
+ parameter_end: ">",
17
+ destination_encoding: :text,
18
+ allow_unknown_replacement_parameters: false,
19
+ allow_nil: false,
20
+ allow_unmatched_parameter_end: false
21
+ )
22
+
23
+ @input = input
24
+ @mapping = mapping
25
+ @required_parameters = required_parameters
26
+ @parameter_start = parameter_start
27
+ @parameter_end = parameter_end
28
+ @destination_encoding = destination_encoding
29
+ @allow_unknown_replacement_parameters = allow_unknown_replacement_parameters
30
+ @allow_nil = allow_nil
31
+ @allow_unmatched_parameter_end = allow_unmatched_parameter_end
32
+ end
33
+
34
+ def allow_unknown_params?(inside_quotes)
35
+ allow_unknown_replacement_parameters || (destination_encoding == :json && inside_quotes)
36
+ end
37
+
38
+ def mapping_has_key?(key)
39
+ downcased_mapping.key?(key.downcase)
40
+ end
41
+
42
+ def mapped_value(key)
43
+ downcased_mapping[key.downcase]
44
+ end
45
+
46
+ def mapping_keys
47
+ mapping.keys
48
+ end
49
+
50
+ def formatted_arg_list(arg_list)
51
+ arg_list.sort.map { |arg| "'#{arg}'" }.join(", ")
52
+ end
53
+
54
+ def duplicate_raw
55
+ ParameterSubstitution::Context.new(
56
+ input: @input,
57
+ mapping: @mapping,
58
+ required_parameters: @required_parameters,
59
+ parameter_start: @parameter_start,
60
+ parameter_end: @parameter_end,
61
+ destination_encoding: :raw,
62
+ allow_unknown_replacement_parameters: @allow_unknown_replacement_parameters,
63
+ allow_nil: @allow_nil,
64
+ allow_unmatched_parameter_end: @allow_unmatched_parameter_end
65
+ )
66
+ end
67
+
68
+ private
69
+
70
+ def downcased_mapping
71
+ @downcase_mapping ||= mapping.build_hash { |k, v| [k.downcase, v] }
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'active_support/core_ext/string/inflections'
5
+ require 'builder'
6
+
7
+ class ParameterSubstitution
8
+ class Encoder
9
+ ENCODINGS = {
10
+ cgi: "CGI encode - for html parameters",
11
+ html: "HTML encode - for html content",
12
+ xml: "XML encode",
13
+ angle_brackets: "Content converted to a string and placed in angle brackets",
14
+ json: "JSON encode",
15
+ raw: "content is not changed",
16
+ text: "content is converted to a string"
17
+ }.keys
18
+
19
+ class << self
20
+ def encode(value, destination_encoding, source_encoding, parameter_name, inside_quotes)
21
+ destination_encoding.in?(ENCODINGS) or raise "unknown encoding #{destination_encoding}"
22
+ source_encoding.in?(ENCODINGS) or raise "unknown encoding #{source_encoding}"
23
+
24
+ result =
25
+ if source_encoding == destination_encoding
26
+ if destination_encoding == :json && inside_quotes
27
+ value.to_json
28
+ else
29
+ value
30
+ end
31
+ else
32
+ case destination_encoding
33
+ when :cgi
34
+ CGI.escape(value.to_s)
35
+ when :html
36
+ CGI.escapeHTML(value.to_s)
37
+ when :xml
38
+ value_for_xml(parameter_name, value)
39
+ when :angle_brackets
40
+ "<#{value}>"
41
+ when :json
42
+ value.to_json
43
+ when :raw
44
+ value
45
+ when :text
46
+ value.to_s
47
+ else
48
+ raise "unexepected escaping #{destination_encoding}"
49
+ end
50
+ end
51
+
52
+ if destination_encoding == :json && inside_quotes
53
+ if value.is_a?(String)
54
+ result[1...-1]
55
+ else
56
+ result.to_json[1...-1]
57
+ end
58
+ else
59
+ result
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def value_for_xml(key, value)
66
+ if value.is_a?(Array)
67
+ array_to_xml(key, value)
68
+ else
69
+ CGI.escapeHTML(value.to_s)
70
+ end
71
+ end
72
+
73
+ def array_to_xml(key, value)
74
+ tag_name = key_to_tag_name(key)
75
+ buffer = +""
76
+ builder = Builder::XmlMarkup.new(target: buffer)
77
+ value.each { |tag_value| builder.tag!(tag_name, tag_value) }
78
+ buffer
79
+ end
80
+
81
+ def key_to_tag_name(key)
82
+ key.gsub('ids_', 'id_').singularize
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'invoca/utils'
4
+
5
+ class ParameterSubstitution
6
+ class Expression
7
+ attr_reader :expression_list, :context
8
+
9
+ def initialize(expression_list, context)
10
+ @expression_list = expression_list
11
+ @context = context
12
+ end
13
+
14
+ def validate
15
+ validate_required_parameters
16
+ validate_unknown_parameters
17
+ validate_expressions
18
+ end
19
+
20
+ def evaluate
21
+ if context.destination_encoding == :raw && @expression_list.size == 1
22
+ # When using the destination encoding of raw, the output should preserve the type from the mapping if the substitution
23
+ # is the full expression. So the input of '<id>' with the mapping { 'id' => 1 } should return 1 and not "1".
24
+ @expression_list.first.evaluate(:OutsideString, only_expression: true)
25
+ else
26
+ map_expressions_with_quote_tracking do |expression, inside_quotes|
27
+ expression.evaluate(inside_quotes).to_s
28
+ end.join("")
29
+ end
30
+ end
31
+
32
+ def warnings
33
+ unknown_parameters_message
34
+ end
35
+
36
+ def parameter_and_method_warnings
37
+ unknown_messages = unknown_parameter_messages + unknown_method_messages
38
+ unknown_messages.empty? ? nil : unknown_messages.uniq
39
+ end
40
+
41
+ def substitution_parameter_names
42
+ @expression_list.map_compact(&:parameter_name)
43
+ end
44
+
45
+ def method_names
46
+ @expression_list.reduce([]) do |all_method_names, expression|
47
+ all_method_names + methods_used_by_expression(expression)
48
+ end
49
+ end
50
+
51
+ def methods_used_by_expression(expression)
52
+ if (method_calls = expression.try(:method_calls))
53
+ method_calls.reduce([]) do |all_method_call_names, method_call|
54
+ all_method_call_names + [method_call.name.to_s] + method_call.arguments&.flat_map { |arg| arg.try(:method_names) }.compact # arg.try returns 'nil' when no methods are called; only method names needed
55
+ end
56
+ else
57
+ []
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ def validate_required_parameters
64
+ matched_parameters = substitution_parameter_names.uniq
65
+ required_parameters = @context.required_parameters
66
+ if !required_parameters.empty? && (missing_fields = required_parameters.reject { |rt| rt.in?(matched_parameters) }).any?
67
+ raise ParameterSubstitution::ParseError,
68
+ "The following #{pluralize_text("field", missing_fields.size)} must be included: #{@context.formatted_arg_list(missing_fields)}"
69
+ end
70
+ end
71
+
72
+ def validate_unknown_parameters
73
+ unless @context.allow_unknown_replacement_parameters
74
+ if (message = unknown_parameters_message)
75
+ raise ParameterSubstitution::ParseError, message
76
+ end
77
+ end
78
+ end
79
+
80
+ def validate_expressions
81
+ map_expressions_with_quote_tracking do |expression, inside_quotes|
82
+ expression.validate(inside_quotes)
83
+ end
84
+ end
85
+
86
+ def unknown_method_messages
87
+ unknown_parameter_methods.map_compact do |param, methods|
88
+ method_string = pluralize_text("method", methods.size)
89
+ unless methods.empty? || unknown_parameters.include?(param)
90
+ "Unknown #{method_string} #{@context.formatted_arg_list(methods)} used on parameter '#{param}'"
91
+ end
92
+ end
93
+ end
94
+
95
+ def unknown_parameter_messages
96
+ unknown_parameters.map do |param|
97
+ unknown_methods_for_param = unknown_parameter_methods[param]
98
+ method_string = pluralize_text("method", unknown_methods_for_param.size)
99
+ unknown_method_message = if unknown_methods_for_param.empty?
100
+ ''
101
+ else
102
+ " and #{method_string} #{@context.formatted_arg_list(unknown_methods_for_param)}"
103
+ end
104
+ "Unknown param '#{param}'#{unknown_method_message}"
105
+ end
106
+ end
107
+
108
+ def unknown_parameters_message
109
+ unless unknown_parameters.empty?
110
+ "Unknown replacement #{pluralize_text("parameter", unknown_parameters.size)} #{@context.formatted_arg_list(unknown_parameters)}"
111
+ end
112
+ end
113
+
114
+ def map_expressions_with_quote_tracking
115
+ inside_quotes = false
116
+ @expression_list.map do |expression|
117
+ result = yield expression, inside_quotes
118
+ inside_quotes = expression.ends_inside_quotes(started_inside_quotes: inside_quotes)
119
+ result
120
+ end
121
+ end
122
+
123
+ def unknown_parameters
124
+ @unknown_parameters ||=
125
+ map_expressions_with_quote_tracking do |expression, inside_quotes|
126
+ expression.unknown_parameters(inside_quotes)
127
+ end.compact
128
+ end
129
+
130
+ def unknown_parameter_methods
131
+ @expression_list.select(&:parameter_name).reduce({}) do |hash, expression|
132
+ hash[expression.parameter_name] ||= []
133
+ hash[expression.parameter_name].push(*missing_methods(expression))
134
+ hash
135
+ end
136
+ end
137
+
138
+ def missing_methods(expression)
139
+ (methods_used_by_expression(expression) - ParameterSubstitution::Formatters::Manager.all_formats.map { |k, _v| k.to_s })
140
+ end
141
+
142
+ def pluralize_text(text, amount)
143
+ text + (amount > 1 ? 's' : '')
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::AddPrefix < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "This takes a prefix as a constructor parameter and prepends it to the value. If the value is blank, nothing is shown."
6
+ end
7
+
8
+ def self.has_parameters?
9
+ true
10
+ end
11
+
12
+ def initialize(prefix)
13
+ @prefix = prefix
14
+ end
15
+
16
+ def format(value)
17
+ value.presence && (@prefix + value.to_s)
18
+ end
19
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution
4
+ module Formatters
5
+ class Base
6
+ class << self
7
+ def description
8
+ raise NotImplementedError, "Derived classes must implement"
9
+ end
10
+
11
+ def format(_value)
12
+ raise NotImplementedError, "Derived classes must implement"
13
+ end
14
+
15
+ def key
16
+ name.split('::').last.gsub(/Format/, '').underscore
17
+ end
18
+
19
+ # Formats that have parameters are constructed and format is called on the instance.
20
+ # Formats without parameters have format called on the class.
21
+ def has_parameters?
22
+ false
23
+ end
24
+
25
+ def encoding
26
+ :raw
27
+ end
28
+
29
+ # TODO: Move out of the base class.
30
+ def parse_duration(duration)
31
+ if duration.is_a?(String)
32
+ if duration =~ /([\d]{1,2})\:([\d]{1,2})\:([\d]{1,2})/
33
+ (Regexp.last_match(1).to_i.hours + Regexp.last_match(2).to_i.minutes + Regexp.last_match(3).to_i.seconds).to_i
34
+ elsif duration =~ /([\d]{1,2})\:([\d]{1,2})/
35
+ (Regexp.last_match(1).to_i.minutes + Regexp.last_match(2).to_i.seconds).to_i
36
+ else
37
+ duration.to_i
38
+ end
39
+ else
40
+ duration.to_i
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ class ParameterSubstitution::Formatters::BlankIfNil < ParameterSubstitution::Formatters::Base
4
+ def self.description
5
+ "Converts nil values to empty strings: ''"
6
+ end
7
+
8
+ def self.format(value)
9
+ value.nil? ? "" : value
10
+ end
11
+ end