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

Sign up to get free protection for your applications and to get access to all the features.
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