liquid_interpolatable.rb 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. module LiquidInterpolatable
  2. extend ActiveSupport::Concern
  3. included do
  4. validate :validate_interpolation
  5. end
  6. def valid?(context = nil)
  7. super
  8. rescue Liquid::Error
  9. errors.empty?
  10. end
  11. def validate_interpolation
  12. interpolated
  13. rescue Liquid::Error => e
  14. errors.add(:options, "has an error with Liquid templating: #{e.message}")
  15. false
  16. end
  17. # Return the current interpolation context. Use this in your Agent
  18. # class to manipulate interpolation context for user.
  19. #
  20. # For example, to provide local variables:
  21. #
  22. # # Create a new scope to define variables in:
  23. # interpolation_context.stack {
  24. # interpolation_context['_something_'] = 42
  25. # # And user can say "{{_something_}}" in their options.
  26. # value = interpolated['some_key']
  27. # }
  28. #
  29. def interpolation_context
  30. @interpolation_context ||= Context.new(self)
  31. end
  32. # Take the given object as "self" in the current interpolation
  33. # context while running a given block.
  34. #
  35. # The most typical use case for this is to evaluate options for each
  36. # received event like this:
  37. #
  38. # def receive(incoming_events)
  39. # incoming_events.each do |event|
  40. # interpolate_with(event) do
  41. # # Handle each event based on "interpolated" options.
  42. # end
  43. # end
  44. # end
  45. def interpolate_with(self_object)
  46. case self_object
  47. when nil
  48. yield
  49. else
  50. context = interpolation_context
  51. begin
  52. context.environments.unshift(self_object.to_liquid)
  53. yield
  54. ensure
  55. context.environments.shift
  56. end
  57. end
  58. end
  59. def interpolate_options(options, self_object = nil)
  60. interpolate_with(self_object) do
  61. case options
  62. when String
  63. interpolate_string(options)
  64. when ActiveSupport::HashWithIndifferentAccess, Hash
  65. options.each_with_object(ActiveSupport::HashWithIndifferentAccess.new) { |(key, value), memo|
  66. memo[key] = interpolate_options(value)
  67. }
  68. when Array
  69. options.map { |value| interpolate_options(value) }
  70. else
  71. options
  72. end
  73. end
  74. end
  75. def interpolated(self_object = nil)
  76. interpolate_with(self_object) do
  77. (@interpolated_cache ||= {})[[options, interpolation_context]] ||=
  78. interpolate_options(options)
  79. end
  80. end
  81. def interpolate_string(string, self_object = nil)
  82. interpolate_with(self_object) do
  83. Liquid::Template.parse(string).render!(interpolation_context)
  84. end
  85. end
  86. class Context < Liquid::Context
  87. def initialize(agent)
  88. super({}, {}, { agent: agent }, true)
  89. end
  90. def hash
  91. [@environments, @scopes, @registers].hash
  92. end
  93. def eql?(other)
  94. other.environments == @environments &&
  95. other.scopes == @scopes &&
  96. other.registers == @registers
  97. end
  98. end
  99. require 'uri'
  100. module Filters
  101. # Percent encoding for URI conforming to RFC 3986.
  102. # Ref: http://tools.ietf.org/html/rfc3986#page-12
  103. def uri_escape(string)
  104. CGI.escape(string) rescue string
  105. end
  106. # Parse an input into a URI object, optionally resolving it
  107. # against a base URI if given.
  108. #
  109. # A URI object will have the following properties: scheme,
  110. # userinfo, host, port, registry, path, opaque, query, and
  111. # fragment.
  112. def to_uri(uri, base_uri = nil)
  113. if base_uri
  114. URI(base_uri) + uri.to_s
  115. else
  116. URI(uri.to_s)
  117. end
  118. rescue URI::Error
  119. nil
  120. end
  121. # Escape a string for use in XPath expression
  122. def to_xpath(string)
  123. subs = string.to_s.scan(/\G(?:\A\z|[^"]+|[^']+)/).map { |x|
  124. case x
  125. when /"/
  126. %Q{'#{x}'}
  127. else
  128. %Q{"#{x}"}
  129. end
  130. }
  131. if subs.size == 1
  132. subs.first
  133. else
  134. 'concat(' << subs.join(', ') << ')'
  135. end
  136. end
  137. end
  138. Liquid::Template.register_filter(LiquidInterpolatable::Filters)
  139. module Tags
  140. class Credential < Liquid::Tag
  141. def initialize(tag_name, name, tokens)
  142. super
  143. @credential_name = name.strip
  144. end
  145. def render(context)
  146. credential = context.registers[:agent].credential(@credential_name)
  147. raise "No user credential named '#{@credential_name}' defined" if credential.nil?
  148. credential
  149. end
  150. end
  151. end
  152. Liquid::Template.register_tag('credential', LiquidInterpolatable::Tags::Credential)
  153. end