Переглянути джерело

Merge pull request #382 from cantino/interpolated_options

Add memoized interpolated_options and used it everywhere.
Andrew Cantino 10 роки тому
батько
коміт
fb0b423a83
50 змінених файлів з 250 додано та 272 видалено
  1. 0 3
      app/concerns/email_concern.rb
  2. 17 13
      app/concerns/liquid_interpolatable.rb
  3. 1 0
      app/models/agent.rb
  4. 4 4
      app/models/agents/adioso_agent.rb
  5. 2 2
      app/models/agents/basecamp_agent.rb
  6. 7 8
      app/models/agents/data_output_agent.rb
  7. 1 1
      app/models/agents/email_agent.rb
  8. 1 1
      app/models/agents/email_digest_agent.rb
  9. 6 6
      app/models/agents/event_formatting_agent.rb
  10. 4 4
      app/models/agents/ftpsite_agent.rb
  11. 6 6
      app/models/agents/growl_agent.rb
  12. 2 4
      app/models/agents/hipchat_agent.rb
  13. 6 8
      app/models/agents/human_task_agent.rb
  14. 7 7
      app/models/agents/imap_folder_agent.rb
  15. 7 9
      app/models/agents/jabber_agent.rb
  16. 7 7
      app/models/agents/java_script_agent.rb
  17. 8 8
      app/models/agents/jira_agent.rb
  18. 11 11
      app/models/agents/mqtt_agent.rb
  19. 11 13
      app/models/agents/peak_detector_agent.rb
  20. 6 6
      app/models/agents/post_agent.rb
  21. 4 4
      app/models/agents/public_transport_agent.rb
  22. 3 4
      app/models/agents/pushbullet_agent.rb
  23. 24 23
      app/models/agents/pushover_agent.rb
  24. 2 2
      app/models/agents/sentiment_agent.rb
  25. 8 7
      app/models/agents/shell_command_agent.rb
  26. 4 5
      app/models/agents/slack_agent.rb
  27. 1 1
      app/models/agents/stubhub_agent.rb
  28. 6 8
      app/models/agents/translation_agent.rb
  29. 9 8
      app/models/agents/trigger_agent.rb
  30. 9 9
      app/models/agents/twilio_agent.rb
  31. 2 3
      app/models/agents/twitter_publish_agent.rb
  32. 5 5
      app/models/agents/twitter_stream_agent.rb
  33. 5 5
      app/models/agents/twitter_user_agent.rb
  34. 7 7
      app/models/agents/weather_agent.rb
  35. 4 4
      app/models/agents/webhook_agent.rb
  36. 22 22
      app/models/agents/website_agent.rb
  37. 3 3
      app/models/agents/weibo_publish_agent.rb
  38. 3 3
      app/models/agents/weibo_user_agent.rb
  39. 9 6
      spec/models/agent_spec.rb
  40. 0 2
      spec/models/agents/data_output_agent_spec.rb
  41. 0 2
      spec/models/agents/hipchat_agent_spec.rb
  42. 0 2
      spec/models/agents/human_task_agent_spec.rb
  43. 0 2
      spec/models/agents/jabber_agent_spec.rb
  44. 0 2
      spec/models/agents/peak_detector_agent_spec.rb
  45. 0 2
      spec/models/agents/pushbullet_agent_spec.rb
  46. 4 3
      spec/models/agents/shell_command_agent_spec.rb
  47. 0 2
      spec/models/agents/slack_agent_spec.rb
  48. 0 2
      spec/models/agents/translation_agent_spec.rb
  49. 0 2
      spec/models/agents/trigger_agent_spec.rb
  50. 2 1
      spec/support/shared_examples/liquid_interpolatable.rb

+ 0 - 3
app/concerns/email_concern.rb

@@ -31,7 +31,4 @@ module EmailConcern
   def present_hash(hash, skip_key = nil)
     hash.to_a.sort_by {|a| a.first.to_s }.map { |k, v| "#{k}: #{v}" unless k.to_s == skip_key.to_s }.compact
   end
-
-  module ClassMethods
-  end
 end

+ 17 - 13
app/concerns/liquid_interpolatable.rb

@@ -1,22 +1,26 @@
 module LiquidInterpolatable
   extend ActiveSupport::Concern
 
-  def interpolate_options(options, payload)
-    case options.class.to_s
-    when 'String'
-      interpolate_string(options, payload)
-    when 'ActiveSupport::HashWithIndifferentAccess', 'Hash'
-      duped_options = options.dup
-      duped_options.each do |key, value|
-        duped_options[key] = interpolate_options(value, payload)
-      end
-    when 'Array'
-      options.collect do |value|
-        interpolate_options(value, payload)
-      end
+  def interpolate_options(options, payload = {})
+    case options
+      when String
+        interpolate_string(options, payload)
+      when ActiveSupport::HashWithIndifferentAccess, Hash
+        options.inject(ActiveSupport::HashWithIndifferentAccess.new) { |memo, (key, value)| memo[key] = interpolate_options(value, payload); memo }
+      when Array
+        options.map { |value| interpolate_options(value, payload) }
+      else
+        options
     end
   end
 
+  def interpolated(payload = {})
+    key = [options, payload]
+    @interpolated_cache ||= {}
+    @interpolated_cache[key] ||= interpolate_options(options, payload)
+    @interpolated_cache[key]
+  end
+
   def interpolate_string(string, payload)
     Liquid::Template.parse(string).render!(payload, registers: {agent: self})
   end

+ 1 - 0
app/models/agent.rb

@@ -12,6 +12,7 @@ class Agent < ActiveRecord::Base
   include JSONSerializedField
   include RDBMSFunctions
   include WorkingHelpers
+  include LiquidInterpolatable
   include HasGuid
 
   markdown_class_attributes :description, :event_description

+ 4 - 4
app/models/agents/adioso_agent.rb

@@ -54,9 +54,9 @@ module Agents
     end
 
     def check
-      auth_options = {:basic_auth => {:username =>options[:username], :password=>options['password']}}
-      parse_response = HTTParty.get "http://api.adioso.com/v2/search/parse?q=#{URI.encode(options['from'])}+to+#{URI.encode(options['to'])}", auth_options
-      fare_request = parse_response["search_url"].gsub /(end=)(\d*)([^\d]*)(\d*)/, "\\1#{date_to_unix_epoch(options['end_date'])}\\3#{date_to_unix_epoch(options['start_date'])}"
+      auth_options = {:basic_auth => {:username =>interpolated[:username], :password=>interpolated['password']}}
+      parse_response = HTTParty.get "http://api.adioso.com/v2/search/parse?q=#{URI.encode(interpolated['from'])}+to+#{URI.encode(interpolated['to'])}", auth_options
+      fare_request = parse_response["search_url"].gsub /(end=)(\d*)([^\d]*)(\d*)/, "\\1#{date_to_unix_epoch(interpolated['end_date'])}\\3#{date_to_unix_epoch(interpolated['start_date'])}"
       fare = HTTParty.get fare_request, auth_options
 
 			if fare["warnings"]
@@ -64,7 +64,7 @@ module Agents
 			else
 				event = fare["results"].min {|a,b| a["cost"] <=> b["cost"]}
 				event["date"]  = Time.at(event["date"]).to_date.httpdate[0..15]
-				event["route"] = "#{options['from']} to #{options['to']}"
+				event["route"] = "#{interpolated['from']} to #{interpolated['to']}"
 				create_event :payload => event
 			end
     end

+ 2 - 2
app/models/agents/basecamp_agent.rb

@@ -76,11 +76,11 @@ module Agents
 
   private
     def request_url
-      "https://basecamp.com/#{URI.encode(options[:user_id].to_s)}/api/v1/projects/#{URI.encode(options[:project_id].to_s)}/events.json"
+      "https://basecamp.com/#{URI.encode(interpolated[:user_id].to_s)}/api/v1/projects/#{URI.encode(interpolated[:project_id].to_s)}/events.json"
     end
 
     def request_options
-      {:basic_auth => {:username =>options[:username], :password=>options[:password]}, :headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}}
+      {:basic_auth => {:username => interpolated[:username], :password => interpolated[:password]}, :headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}}
     end
 
     def query_parameters

+ 7 - 8
app/models/agents/data_output_agent.rb

@@ -1,7 +1,5 @@
 module Agents
   class DataOutputAgent < Agent
-    include LiquidInterpolatable
-
     cannot_be_scheduled!
 
     description  do
@@ -52,6 +50,7 @@ module Agents
       unless options['secrets'].is_a?(Array) && options['secrets'].length > 0
         errors.add(:base, "Please specify one or more secrets for 'authenticating' incoming feed requests")
       end
+
       unless options['expected_receive_period_in_days'].present? && options['expected_receive_period_in_days'].to_i > 0
         errors.add(:base, "Please provide 'expected_receive_period_in_days' to indicate how many days can pass before this Agent is considered to be not working")
       end
@@ -62,27 +61,27 @@ module Agents
     end
 
     def events_to_show
-      (options['events_to_show'].presence || 40).to_i
+      (interpolated['events_to_show'].presence || 40).to_i
     end
 
     def feed_ttl
-      (options['ttl'].presence || 60).to_i
+      (interpolated['ttl'].presence || 60).to_i
     end
 
     def feed_title
-      options['template']['title'].presence || "#{name} Event Feed"
+      interpolated['template']['title'].presence || "#{name} Event Feed"
     end
 
     def feed_link
-      options['template']['link'].presence || "https://#{ENV['DOMAIN']}"
+      interpolated['template']['link'].presence || "https://#{ENV['DOMAIN']}"
     end
 
     def feed_description
-      options['template']['description'].presence || "A feed of Events received by the '#{name}' Huginn Agent"
+      interpolated['template']['description'].presence || "A feed of Events received by the '#{name}' Huginn Agent"
     end
 
     def receive_web_request(params, method, format)
-      if options['secrets'].include?(params['secret'])
+      if interpolated['secrets'].include?(params['secret'])
         items = received_events.order('id desc').limit(events_to_show).map do |event|
           interpolated = interpolate_options(options['template']['item'], event.payload)
           interpolated['guid'] = event.id

+ 1 - 1
app/models/agents/email_agent.rb

@@ -25,7 +25,7 @@ module Agents
     def receive(incoming_events)
       incoming_events.each do |event|
         log "Sending digest mail to #{user.email} with event #{event.id}"
-        SystemMailer.delay.send_message(:to => user.email, :subject => options['subject'], :headline => options['headline'], :groups => [present(event.payload)])
+        SystemMailer.delay.send_message(:to => user.email, :subject => interpolated(event.payload)['subject'], :headline => interpolated(event.payload)['headline'], :groups => [present(event.payload)])
       end
     end
   end

+ 1 - 1
app/models/agents/email_digest_agent.rb

@@ -37,7 +37,7 @@ module Agents
         ids = self.memory['events'].join(",")
         groups = self.memory['queue'].map { |payload| present(payload) }
         log "Sending digest mail to #{user.email} with events [#{ids}]"
-        SystemMailer.delay.send_message(:to => user.email, :subject => options['subject'], :headline => options['headline'], :groups => groups)
+        SystemMailer.delay.send_message(:to => user.email, :subject => interpolated['subject'], :headline => interpolated['headline'], :groups => groups)
         self.memory['queue'] = []
         self.memory['events'] = []
       end

+ 6 - 6
app/models/agents/event_formatting_agent.rb

@@ -1,6 +1,5 @@
 module Agents
   class EventFormattingAgent < Agent
-    include LiquidInterpolatable
     cannot_be_scheduled!
 
     description <<-MD
@@ -81,7 +80,7 @@ module Agents
     after_save :clear_matchers
 
     def validate_options
-      errors.add(:base, "instructions, mode, skip_agent, and skip_created_at all need to be present.") unless options['instructions'].present? and options['mode'].present? and options['skip_agent'].present? and options['skip_created_at'].present?
+      errors.add(:base, "instructions, mode, skip_agent, and skip_created_at all need to be present.") unless options['instructions'].present? && options['mode'].present? && options['skip_agent'].present? && options['skip_created_at'].present?
 
       validate_matchers
     end
@@ -105,11 +104,12 @@ module Agents
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        formatted_event = options['mode'].to_s == "merge" ? event.payload.dup : {}
         payload = perform_matching(event.payload)
-        formatted_event.merge! interpolate_options(options['instructions'], payload)
-        formatted_event['agent'] = Agent.find(event.agent_id).type.slice!(8..-1) unless options['skip_agent'].to_s == "true"
-        formatted_event['created_at'] = event.created_at unless options['skip_created_at'].to_s == "true"
+        opts = interpolated(payload)
+        formatted_event = opts['mode'].to_s == "merge" ? event.payload.dup : {}
+        formatted_event.merge! opts['instructions']
+        formatted_event['agent'] = Agent.find(event.agent_id).type.slice!(8..-1) unless opts['skip_agent'].to_s == "true"
+        formatted_event['created_at'] = event.created_at unless opts['skip_created_at'].to_s == "true"
         create_event :payload => formatted_event
       end
     end

+ 4 - 4
app/models/agents/ftpsite_agent.rb

@@ -29,7 +29,7 @@ module Agents
     MD
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def default_options
@@ -90,10 +90,10 @@ module Agents
     end
 
     def each_entry
-      patterns = options['patterns']
+      patterns = interpolated['patterns']
 
       after =
-        if str = options['after']
+        if str = interpolated['after']
           Time.parse(str)
         else
           Time.at(0)
@@ -174,7 +174,7 @@ module Agents
     end
 
     def base_uri
-      @base_uri ||= URI(options['url'])
+      @base_uri ||= URI(interpolated['url'])
     end
 
     def saving_entries

+ 6 - 6
app/models/agents/growl_agent.rb

@@ -26,7 +26,7 @@ module Agents
     end
     
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def validate_options
@@ -36,13 +36,13 @@ module Agents
     end
     
     def register_growl
-      @growler = Growl.new options['growl_server'], options['growl_app_name'], "GNTP"
-      @growler.password = options['growl_password']
-      @growler.add_notification options['growl_notification_name']
+      @growler = Growl.new interpolated['growl_server'], interpolated['growl_app_name'], "GNTP"
+      @growler.password = interpolated['growl_password']
+      @growler.add_notification interpolated['growl_notification_name']
     end
     
     def notify_growl(subject, message)
-      @growler.notify(options['growl_notification_name'],subject,message)
+      @growler.notify(interpolated['growl_notification_name'], subject, message)
     end
 
     def receive(incoming_events)
@@ -51,7 +51,7 @@ module Agents
         message = (event.payload['message'] || event.payload['text']).to_s
         subject = event.payload['subject'].to_s
         if message.present? && subject.present?
-          log "Sending Growl notification '#{subject}': '#{message}' to #{options['growl_server']} with event #{event.id}"
+          log "Sending Growl notification '#{subject}': '#{message}' to #{interpolated(event.payload)['growl_server']} with event #{event.id}"
           notify_growl(subject,message)
         else
           log "Event #{event.id} not sent, message and subject expected"

+ 2 - 4
app/models/agents/hipchat_agent.rb

@@ -1,7 +1,5 @@
 module Agents
   class HipchatAgent < Agent
-    include LiquidInterpolatable
-
     cannot_be_scheduled!
     cannot_create_events!
 
@@ -42,9 +40,9 @@ module Agents
     end
 
     def receive(incoming_events)
-      client = HipChat::Client.new(options[:auth_token])
+      client = HipChat::Client.new(interpolated[:auth_token])
       incoming_events.each do |event|
-        mo = interpolate_options options, event.payload
+        mo = interpolated(event.payload)
         client[mo[:room_name]].send(mo[:username], mo[:message], :notify => mo[:notify].to_s == 'true' ? 1 : 0, :color => mo[:color])
       end
     end

+ 6 - 8
app/models/agents/human_task_agent.rb

@@ -2,8 +2,6 @@ require 'rturk'
 
 module Agents
   class HumanTaskAgent < Agent
-    include LiquidInterpolatable
-
     default_schedule "every_10m"
 
     description <<-MD
@@ -204,20 +202,20 @@ module Agents
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def check
       review_hits
 
-      if options['trigger_on'] == "schedule" && (memory['last_schedule'] || 0) <= Time.now.to_i - options['submission_period'].to_i * 60 * 60
+      if interpolated['trigger_on'] == "schedule" && (memory['last_schedule'] || 0) <= Time.now.to_i - interpolated['submission_period'].to_i * 60 * 60
         memory['last_schedule'] = Time.now.to_i
         create_basic_hit
       end
     end
 
     def receive(incoming_events)
-      if options['trigger_on'] == "event"
+      if interpolated['trigger_on'] == "event"
         incoming_events.each do |event|
           create_basic_hit event
         end
@@ -227,11 +225,11 @@ module Agents
     protected
 
     def take_majority?
-      options['combination_mode'] == "take_majority" || options['take_majority'] == "true"
+      interpolated['combination_mode'] == "take_majority" || interpolated['take_majority'] == "true"
     end
 
     def create_poll?
-      options['combination_mode'] == "poll"
+      interpolated['combination_mode'] == "poll"
     end
 
     def event_for_hit(hit_id)
@@ -367,7 +365,7 @@ module Agents
     end
 
     def all_questions_are_numeric?
-      options['hit']['questions'].all? do |question|
+      interpolated['hit']['questions'].all? do |question|
         question['selections'].all? do |selection|
           selection['key'] == selection['key'].to_f.to_s || selection['key'] == selection['key'].to_i.to_s
         end

+ 7 - 7
app/models/agents/imap_folder_agent.rb

@@ -111,7 +111,7 @@ module Agents
     }
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def default_options
@@ -240,7 +240,7 @@ module Agents
         matched_part = nil
         matches = {}
 
-        options['conditions'].all? { |key, value|
+        interpolated['conditions'].all? { |key, value|
           case key
           when 'subject'
             value.present? or next true
@@ -308,7 +308,7 @@ module Agents
           notified << mail.message_id if mail.message_id
         end
 
-        if options['mark_as_read']
+        if interpolated['mark_as_read']
           log 'Marking as read'
           mail.mark_as_read
         end
@@ -322,14 +322,14 @@ module Agents
     end
 
     def each_unread_mail
-      host, port, ssl, username = options.values_at(:host, :port, :ssl, :username)
+      host, port, ssl, username = interpolated.values_at(:host, :port, :ssl, :username)
 
       log "Connecting to #{host}#{':%d' % port if port}#{' via SSL' if ssl}"
       Client.open(host, Integer(port), ssl) { |imap|
         log "Logging in as #{username}"
-        imap.login(username, options[:password])
+        imap.login(username, interpolated[:password])
 
-        options['folders'].each { |folder|
+        interpolated['folders'].each { |folder|
           log "Selecting the folder: %s" % folder
 
           imap.select(folder)
@@ -351,7 +351,7 @@ module Agents
     end
 
     def mime_types
-      options['mime_types'] || %w[text/plain text/enriched text/html]
+      interpolated['mime_types'] || %w[text/plain text/enriched text/html]
     end
 
     private

+ 7 - 9
app/models/agents/jabber_agent.rb

@@ -1,7 +1,5 @@
 module Agents
   class JabberAgent < Agent
-    include LiquidInterpolatable
-
     cannot_be_scheduled!
     cannot_create_events!
 
@@ -30,12 +28,12 @@ module Agents
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        log "Sending IM to #{options['jabber_receiver']} with event #{event.id}"
+        log "Sending IM to #{interpolated['jabber_receiver']} with event #{event.id}"
         deliver body(event)
       end
     end
@@ -45,15 +43,15 @@ module Agents
     end
 
     def deliver(text)
-      client.send Jabber::Message::new(options['jabber_receiver'], text).set_type(:chat)
+      client.send Jabber::Message::new(interpolated['jabber_receiver'], text).set_type(:chat)
     end
 
     private
 
     def client
-      Jabber::Client.new(Jabber::JID::new(options['jabber_sender'])).tap do |sender|
-        sender.connect(options['jabber_server'], (options['jabber_port'] || '5222'))
-        sender.auth(options['jabber_password'])
+      Jabber::Client.new(Jabber::JID::new(interpolated['jabber_sender'])).tap do |sender|
+        sender.connect(interpolated['jabber_server'], interpolated['jabber_port'] || '5222')
+        sender.auth interpolated['jabber_password']
       end
     end
 
@@ -62,7 +60,7 @@ module Agents
     end
 
     def body(event)
-      interpolate_string(options['message'], event.payload)
+      interpolated(event.payload)['message']
     end
   end
 end

+ 7 - 7
app/models/agents/java_script_agent.rb

@@ -35,12 +35,12 @@ module Agents
     def working?
       return false if recent_error_logs?
 
-      if options['expected_update_period_in_days'].present?
-        return false unless event_created_within?(options['expected_update_period_in_days'])
+      if interpolated['expected_update_period_in_days'].present?
+        return false unless event_created_within?(interpolated['expected_update_period_in_days'])
       end
 
-      if options['expected_receive_period_in_days'].present?
-        return false unless last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago
+      if interpolated['expected_receive_period_in_days'].present?
+        return false unless last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago
       end
 
       true
@@ -92,7 +92,7 @@ module Agents
 
       context["doCreateEvent"] = lambda { |a, y| create_event(payload: clean_nans(JSON.parse(y))).payload.to_json }
       context["getIncomingEvents"] = lambda { |a| incoming_events.to_json }
-      context["getOptions"] = lambda { |a, x| options.to_json }
+      context["getOptions"] = lambda { |a, x| interpolated.to_json }
       context["doLog"] = lambda { |a, x| log x }
       context["doError"] = lambda { |a, x| error x }
       context["getMemory"] = lambda do |a, x, y|
@@ -112,12 +112,12 @@ module Agents
       if cred
         credential(cred) || 'Agent.check = function() { this.error("Unable to find credential"); };'
       else
-        options['code']
+        interpolated['code']
       end
     end
 
     def credential_referenced_by_code
-      options['code'] =~ /\Acredential:(.*)\Z/ && $1
+      interpolated['code'] =~ /\Acredential:(.*)\Z/ && $1
     end
 
     def setup_javascript

+ 8 - 8
app/models/agents/jira_agent.rb

@@ -56,7 +56,7 @@ module Agents
     end
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def check
@@ -81,14 +81,14 @@ module Agents
 
   private
     def request_url(jql, start_at)
-      "#{options[:jira_url]}/rest/api/2/search?jql=#{CGI::escape(jql)}&fields=*all&startAt=#{start_at}"
+      "#{interpolated[:jira_url]}/rest/api/2/search?jql=#{CGI::escape(jql)}&fields=*all&startAt=#{start_at}"
     end
 
     def request_options
       ropts = {:headers => {"User-Agent" => "Huginn (https://github.com/cantino/huginn)"}}
 
-      if !options[:username].empty?
-        ropts = ropts.merge({:basic_auth => {:username =>options[:username], :password=>options[:password]}})
+      if !interpolated[:username].empty?
+        ropts = ropts.merge({:basic_auth => {:username =>interpolated[:username], :password=>interpolated[:password]}})
       end
 
       ropts
@@ -121,10 +121,10 @@ module Agents
 
       jql = ""
 
-      if !options[:jql].empty? && since
-        jql = "(#{options[:jql]}) and updated >= '#{since.strftime('%Y-%m-%d %H:%M')}'"
+      if !interpolated[:jql].empty? && since
+        jql = "(#{interpolated[:jql]}) and updated >= '#{since.strftime('%Y-%m-%d %H:%M')}'"
       else
-        jql = options[:jql] if !options[:jql].empty?
+        jql = interpolated[:jql] if !interpolated[:jql].empty?
         jql = "updated >= '#{since.strftime('%Y-%m-%d %H:%M')}'" if since
       end
 
@@ -142,7 +142,7 @@ module Agents
           raise RuntimeError.new("There is no progress while fetching issues")
         end
 
-        if Time.now > start_time + options['timeout'].to_i * 60
+        if Time.now > start_time + interpolated['timeout'].to_i * 60
           raise RuntimeError.new("Timeout exceeded while fetching issues")
         end
 

+ 11 - 11
app/models/agents/mqtt_agent.rb

@@ -68,13 +68,13 @@ module Agents
 
     def validate_options
       unless options['uri'].present? &&
-        options['topic'].present?
+             options['topic'].present?
         errors.add(:base, "topic and uri are required")
       end
     end
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def default_options
@@ -91,13 +91,13 @@ module Agents
     end
 
     def mqtt_client
-      @client ||= MQTT::Client.new(options['uri'])
+      @client ||= MQTT::Client.new(interpolated['uri'])
 
-      if options['ssl']
-        @client.ssl = options['ssl'].to_sym
-        @client.ca_file = options['ca_file']
-        @client.cert_file = options['cert_file']
-        @client.key_file = options['key_file']
+      if interpolated['ssl']
+        @client.ssl = interpolated['ssl'].to_sym
+        @client.ca_file = interpolated['ca_file']
+        @client.cert_file = interpolated['cert_file']
+        @client.key_file = interpolated['key_file']
       end
 
       @client
@@ -106,7 +106,7 @@ module Agents
     def receive(incoming_events)
       mqtt_client.connect do |c|
         incoming_events.each do |event|
-          c.publish(options['topic'], payload)
+          c.publish(interpolated(event.payload)['topic'], event.payload)
         end
 
         c.disconnect
@@ -117,8 +117,8 @@ module Agents
     def check
       mqtt_client.connect do |c|
 
-        Timeout::timeout((options['max_read_time'].presence || 15).to_i) {
-          c.get(options['topic']) do |topic, message|
+        Timeout::timeout((interpolated['max_read_time'].presence || 15).to_i) {
+          c.get(interpolated['topic']) do |topic, message|
 
             # A lot of services generate JSON. Try that first
             payload = JSON.parse(message) rescue message

+ 11 - 13
app/models/agents/peak_detector_agent.rb

@@ -2,8 +2,6 @@ require 'pp'
 
 module Agents
   class PeakDetectorAgent < Agent
-    include LiquidInterpolatable
-
     cannot_be_scheduled!
 
     description <<-MD
@@ -45,7 +43,7 @@ module Agents
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def receive(incoming_events)
@@ -69,7 +67,7 @@ module Agents
         if newest_value > average_value + std_multiple * standard_deviation
           memory['peaks'][group] << newest_time
           memory['peaks'][group].reject! { |p| p <= newest_time - window_duration }
-          create_event :payload => { 'message' => interpolate_string(options['message'], event.payload), 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s }
+          create_event :payload => { 'message' => interpolated(event.payload)['message'], 'peak' => newest_value, 'peak_time' => newest_time, 'grouped_by' => group.to_s }
         end
       end
     end
@@ -94,33 +92,33 @@ module Agents
     end
 
     def window_duration
-      if options['window_duration'].present? # The older option
-        options['window_duration'].to_i
+      if interpolated['window_duration'].present? # The older option
+        interpolated['window_duration'].to_i
       else
-        (options['window_duration_in_days'] || 14).to_f.days
+        (interpolated['window_duration_in_days'] || 14).to_f.days
       end
     end
 
     def std_multiple
-      (options['std_multiple'] || 3).to_f
+      (interpolated['std_multiple'] || 3).to_f
     end
 
     def peak_spacing
-      if options['peak_spacing'].present? # The older option
-        options['peak_spacing'].to_i
+      if interpolated['peak_spacing'].present? # The older option
+        interpolated['peak_spacing'].to_i
       else
-        (options['min_peak_spacing_in_days'] || 2).to_f.days
+        (interpolated['min_peak_spacing_in_days'] || 2).to_f.days
       end
     end
 
     def group_for(event)
-      ((options['group_by_path'].present? && Utils.value_at(event.payload, options['group_by_path'])) || 'no_group')
+      ((interpolated['group_by_path'].present? && Utils.value_at(event.payload, interpolated['group_by_path'])) || 'no_group')
     end
 
     def remember(group, event)
       memory['data'] ||= {}
       memory['data'][group] ||= []
-      memory['data'][group] << [ Utils.value_at(event.payload, options['value_path']), event.created_at.to_i ]
+      memory['data'][group] << [ Utils.value_at(event.payload, interpolated['value_path']), event.created_at.to_i ]
       cleanup group
     end
 

+ 6 - 6
app/models/agents/post_agent.rb

@@ -27,15 +27,15 @@ module Agents
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def method
-      (options['method'].presence || 'post').to_s.downcase
+      (interpolated['method'].presence || 'post').to_s.downcase
     end
 
     def headers
-      options['headers'].presence || {}
+      interpolated['headers'].presence || {}
     end
 
     def validate_options
@@ -58,16 +58,16 @@ module Agents
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        handle (options['payload'].presence || {}).merge(event.payload)
+        handle (interpolated(event.payload)['payload'].presence || {}).merge(event.payload)
       end
     end
 
     def check
-      handle options['payload'].presence || {}
+      handle interpolated['payload'].presence || {}
     end
 
     def generate_uri(params = nil)
-      uri = URI options[:post_url]
+      uri = URI interpolated[:post_url]
       uri.query = URI.encode_www_form(Hash[URI.decode_www_form(uri.query || '')].merge(params)) if params
       uri
     end

+ 4 - 4
app/models/agents/public_transport_agent.rb

@@ -48,12 +48,12 @@ module Agents
     MD
 
     def check_url
-      stop_query = URI.encode(options["stops"].collect{|a| "&stops=#{a}"}.join)
-      "http://webservices.nextbus.com/service/publicXMLFeed?command=predictionsForMultiStops&a=#{options["agency"]}#{stop_query}"
+      stop_query = URI.encode(interpolated["stops"].collect{|a| "&stops=#{a}"}.join)
+      "http://webservices.nextbus.com/service/publicXMLFeed?command=predictionsForMultiStops&a=#{interpolated["agency"]}#{stop_query}"
     end
 
     def stops
-      options["stops"].collect{|a| a.split("|").last}
+      interpolated["stops"].collect{|a| a.split("|").last}
     end
 
     def check
@@ -65,7 +65,7 @@ module Agents
         predictions.each do |pr|
           parent = pr.parent.parent
           vals = {"routeTitle" => parent["routeTitle"], "stopTag" => parent["stopTag"]}
-          if pr["minutes"] && pr["minutes"].to_i < options["alert_window_in_minutes"].to_i
+          if pr["minutes"] && pr["minutes"].to_i < interpolated["alert_window_in_minutes"].to_i
             vals = vals.merge Hash.from_xml(pr.to_xml)
             if not_already_in_memory?(vals)
               create_event(:payload => vals)

+ 3 - 4
app/models/agents/pushbullet_agent.rb

@@ -1,7 +1,5 @@
 module Agents
   class PushbulletAgent < Agent
-    include LiquidInterpolatable
-
     cannot_be_scheduled!
     cannot_create_events!
 
@@ -49,10 +47,11 @@ module Agents
     end
 
     private
+
     def query_options(event)
-      mo = interpolate_options options, event.payload
+      mo = interpolated(event.payload)
       {
-        :basic_auth => {:username =>mo[:api_key], :password=>''},
+        :basic_auth => {:username => mo[:api_key], :password => ''},
         :body => {:device_iden => mo[:device_id], :title => mo[:title], :body => mo[:body], :type => 'note'}
       }
     end

+ 24 - 23
app/models/agents/pushover_agent.rb

@@ -58,44 +58,45 @@ module Agents
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        message = (event.payload['message'].presence  || event.payload['text'].presence  || options['message']).to_s
+        payload_interpolated = interpolated(event.payload)
+        message = (event.payload['message'].presence || event.payload['text'].presence || payload_interpolated['message']).to_s
         if message.present?
-            post_params = {
-              'token' => options['token'],
-              'user' => options['user'],
-              'message' => message
-            }
+          post_params = {
+            'token' => payload_interpolated['token'],
+            'user' => payload_interpolated['user'],
+            'message' => message
+          }
 
-            post_params['device'] = event.payload['device'].presence  || options['device']
-            post_params['title'] = event.payload['title'].presence  || event.payload['subject'].presence  || options['title']
+          post_params['device'] = event.payload['device'].presence || payload_interpolated['device']
+          post_params['title'] = event.payload['title'].presence || event.payload['subject'].presence || payload_interpolated['title']
 
-            url = (event.payload['url'].presence  || options['url'] || '').to_s
-            url = url.slice 0..512
-            post_params['url'] = url
+          url = (event.payload['url'].presence || payload_interpolated['url'] || '').to_s
+          url = url.slice 0..512
+          post_params['url'] = url
 
-            url_title = (event.payload['url_title'].presence  || options['url_title']).to_s
-            url_title = url_title.slice 0..100
-            post_params['url_title'] = url_title
+          url_title = (event.payload['url_title'].presence || payload_interpolated['url_title']).to_s
+          url_title = url_title.slice 0..100
+          post_params['url_title'] = url_title
 
-            post_params['priority'] = (event.payload['priority'].presence  || options['priority']).to_i
+          post_params['priority'] = (event.payload['priority'].presence || payload_interpolated['priority']).to_i
 
-            if event.payload.has_key? 'timestamp'
-              post_params['timestamp'] = (event.payload['timestamp']).to_s
-            end
+          if event.payload.has_key? 'timestamp'
+            post_params['timestamp'] = (event.payload['timestamp']).to_s
+          end
 
-            post_params['sound'] = (event.payload['sound'].presence  || options['sound']).to_s
+          post_params['sound'] = (event.payload['sound'].presence || payload_interpolated['sound']).to_s
 
-            post_params['retry'] = (event.payload['retry'].presence  || options['retry']).to_i
+          post_params['retry'] = (event.payload['retry'].presence || payload_interpolated['retry']).to_i
 
-            post_params['expire'] = (event.payload['expire'].presence  || options['expire']).to_i
+          post_params['expire'] = (event.payload['expire'].presence || payload_interpolated['expire']).to_i
 
-            send_notification(post_params)
+          send_notification(post_params)
         end
       end
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def send_notification(post_params)

+ 2 - 2
app/models/agents/sentiment_agent.rb

@@ -34,13 +34,13 @@ module Agents
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def receive(incoming_events)
       anew = self.class.sentiment_hash
       incoming_events.each do |event|
-        Utils.values_at(event.payload, options['content']).each do |content|
+        Utils.values_at(event.payload, interpolated['content']).each do |content|
           sent_values = sentiment_values anew, content
           create_event :payload => { 'content' => content,
                                      'valence' => sent_values[0],

+ 8 - 7
app/models/agents/shell_command_agent.rb

@@ -15,7 +15,8 @@ module Agents
 
       `expected_update_period_in_days` is used to determine if the Agent is working.
 
-      ShellCommandAgent can also act upon received events. These events may contain their own `path` and `command` values. If they do not, ShellCommandAgent will use the configured options. For this reason, please specify defaults even if you are planning to have this Agent to respond to events.
+      ShellCommandAgent can also act upon received events. When receiving an event, this Agent's options can interpolate values from the incoming event.
+      For example, your command could be defined as `{{cmd}}`, in which case the event's `cmd` property would be used.
 
       The resulting event will contain the `command` which was executed, the `path` it was executed under, the `exit_status` of the command, the `errors`, and the actual `output`. ShellCommandAgent will not log an error if the result implies that something went wrong.
 
@@ -55,25 +56,25 @@ module Agents
     end
 
     def working?
-      Agents::ShellCommandAgent.should_run? && event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      Agents::ShellCommandAgent.should_run? && event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        handle(event.payload, event)
+        handle(interpolated(event.payload), event)
       end
     end
 
     def check
-      handle(options)
+      handle(interpolated)
     end
 
     private
 
-    def handle(opts = options, event = nil)
+    def handle(opts, event = nil)
       if Agents::ShellCommandAgent.should_run?
-        command = opts['command'] || options['command']
-        path = opts['path'] || options['path']
+        command = opts['command']
+        path = opts['path']
 
         result, errors, exit_status = run_command(path, command)
 

+ 4 - 5
app/models/agents/slack_agent.rb

@@ -1,6 +1,5 @@
 module Agents
   class SlackAgent < Agent
-    include LiquidInterpolatable
     cannot_be_scheduled!
     cannot_create_events!
 
@@ -45,20 +44,20 @@ module Agents
     end
 
     def webhook
-      options[:webhook].presence || DEFAULT_WEBHOOK
+      interpolated[:webhook].presence || DEFAULT_WEBHOOK
     end
 
     def username
-      options[:username].presence || DEFAULT_USERNAME
+      interpolated[:username].presence || DEFAULT_USERNAME
     end
 
     def slack_notifier
-      @slack_notifier ||= Slack::Notifier.new(options[:team_name], options[:auth_token], webhook, username: username)
+      @slack_notifier ||= Slack::Notifier.new(interpolated[:team_name], interpolated[:auth_token], webhook, username: username)
     end
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        opts = interpolate_options options, event.payload
+        opts = interpolated(event.payload)
         slack_notifier.ping opts[:message], channel: opts[:channel], username: opts[:username]
       end
     end

+ 1 - 1
app/models/agents/stubhub_agent.rb

@@ -35,7 +35,7 @@ module Agents
     end
 
     def url
-      options['url']
+      interpolated['url']
     end
 
     def check

+ 6 - 8
app/models/agents/translation_agent.rb

@@ -1,7 +1,5 @@
 module Agents
   class TranslationAgent < Agent
-    include LiquidInterpolatable
-
     cannot_be_scheduled!
 
     description <<-MD
@@ -30,7 +28,7 @@ module Agents
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def translate(text, to, access_token)
@@ -61,16 +59,16 @@ module Agents
 
     def receive(incoming_events)
       auth_uri = URI "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
-      response = postform auth_uri, :client_id => options['client_id'],
-                                    :client_secret => options['client_secret'],
+      response = postform auth_uri, :client_id => interpolated['client_id'],
+                                    :client_secret => interpolated['client_secret'],
                                     :scope => "http://api.microsofttranslator.com",
                                     :grant_type => "client_credentials"
       access_token = JSON.parse(response.body)["access_token"]
       incoming_events.each do |event|
         translated_event = {}
-        options['content'].each_pair do |key, value|
-          to_be_translated = interpolate_string(value, event.payload)
-          translated_event[key] = translate(to_be_translated.first, options['to'], access_token)
+        opts = interpolated(event.payload)
+        opts['content'].each_pair do |key, value|
+          translated_event[key] = translate(value.first, opts['to'], access_token)
         end
         create_event :payload => translated_event
       end

+ 9 - 8
app/models/agents/trigger_agent.rb

@@ -1,7 +1,5 @@
 module Agents
   class TriggerAgent < Agent
-    include LiquidInterpolatable
-
     cannot_be_scheduled!
 
     VALID_COMPARISON_TYPES = %w[regex !regex field<value field<=value field==value field!=value field>=value field>value]
@@ -30,7 +28,7 @@ module Agents
 
     def validate_options
       unless options['expected_receive_period_in_days'].present? && options['rules'].present? &&
-          options['rules'].all? { |rule| rule['type'].present? && VALID_COMPARISON_TYPES.include?(rule['type']) && rule['value'].present? && rule['path'].present? }
+             options['rules'].all? { |rule| rule['type'].present? && VALID_COMPARISON_TYPES.include?(rule['type']) && rule['value'].present? && rule['path'].present? }
         errors.add(:base, "expected_receive_period_in_days, message, and rules, with a type, value, and path for every rule, are required")
       end
 
@@ -53,12 +51,15 @@ module Agents
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def receive(incoming_events)
       incoming_events.each do |event|
-        match = options['rules'].all? do |rule|
+
+        opts = interpolated(event.payload)
+
+        match = opts['rules'].all? do |rule|
           value_at_path = Utils.value_at(event['payload'], rule['path'])
           rule_values = rule['value']
           rule_values = [rule_values] unless rule_values.is_a?(Array)
@@ -90,9 +91,9 @@ module Agents
         if match
           if keep_event?
             payload = event.payload.dup
-            payload['message'] = interpolate_string(options['message'], event.payload) if options['message'].present?
+            payload['message'] = opts['message'] if opts['message'].present?
           else
-            payload = { 'message' => interpolate_string(options['message'], event.payload) }
+            payload = { 'message' => opts['message'] }
           end
 
           create_event :payload => payload
@@ -101,7 +102,7 @@ module Agents
     end
 
     def keep_event?
-      options['keep_event'] == 'true'
+      interpolated['keep_event'] == 'true'
     end
   end
 end

+ 9 - 9
app/models/agents/twilio_agent.rb

@@ -39,18 +39,18 @@ module Agents
     end
 
     def receive(incoming_events)
-      @client = Twilio::REST::Client.new options['account_sid'], options['auth_token']
+      @client = Twilio::REST::Client.new interpolated['account_sid'], interpolated['auth_token']
       memory['pending_calls'] ||= {}
       incoming_events.each do |event|
         message = (event.payload['message'].presence || event.payload['text'].presence || event.payload['sms'].presence).to_s
         if message.present?
-          if options['receive_call'].to_s == 'true'
+          if interpolated(event.payload)['receive_call'].to_s == 'true'
             secret = SecureRandom.hex 3
             memory['pending_calls'][secret] = message
             make_call secret
           end
 
-          if options['receive_text'].to_s == 'true'
+          if interpolated(event.payload)['receive_text'].to_s == 'true'
             message = message.slice 0..160
             send_message message
           end
@@ -59,19 +59,19 @@ module Agents
     end
 
     def working?
-      last_receive_at && last_receive_at > options['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
+      last_receive_at && last_receive_at > interpolated['expected_receive_period_in_days'].to_i.days.ago && !recent_error_logs?
     end
 
     def send_message(message)
-      @client.account.sms.messages.create :from => options['sender_cell'],
-                                          :to => options['receiver_cell'],
+      @client.account.sms.messages.create :from => interpolated['sender_cell'],
+                                          :to => interpolated['receiver_cell'],
                                           :body => message
     end
 
     def make_call(secret)
-      @client.account.calls.create :from => options['sender_cell'],
-                                   :to => options['receiver_cell'],
-                                   :url => post_url(options['server_url'], secret)
+      @client.account.calls.create :from => interpolated['sender_cell'],
+                                   :to => interpolated['receiver_cell'],
+                                   :url => post_url(interpolated['server_url'], secret)
     end
 
     def post_url(server_url, secret)

+ 2 - 3
app/models/agents/twitter_publish_agent.rb

@@ -3,7 +3,6 @@ require "twitter"
 module Agents
   class TwitterPublishAgent < Agent
     include TwitterConcern
-    include LiquidInterpolatable
 
     cannot_be_scheduled!
 
@@ -26,7 +25,7 @@ module Agents
     end
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && most_recent_event && most_recent_event.payload['success'] == true && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && most_recent_event && most_recent_event.payload['success'] == true && !recent_error_logs?
     end
 
     def default_options
@@ -42,7 +41,7 @@ module Agents
         incoming_events = incoming_events.first(20)
       end
       incoming_events.each do |event|
-        tweet_text = interpolate_string(options['message'], event.payload)
+        tweet_text = interpolated(event.payload)['message']
         begin
           tweet = publish_tweet tweet_text
           create_event :payload => {

+ 5 - 5
app/models/agents/twitter_stream_agent.rb

@@ -65,7 +65,7 @@ module Agents
     end
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def default_options
@@ -80,7 +80,7 @@ module Agents
       filter = lookup_filter(filter)
 
       if filter
-        if options['generate'] == "counts"
+        if interpolated['generate'] == "counts"
           # Avoid memory pollution by reloading the Agent.
           agent = Agent.find(id)
           agent.memory['filter_counts'] ||= {}
@@ -95,7 +95,7 @@ module Agents
     end
 
     def check
-      if options['generate'] == "counts" && memory['filter_counts'] && memory['filter_counts'].length > 0
+      if interpolated['generate'] == "counts" && memory['filter_counts'] && memory['filter_counts'].length > 0
         memory['filter_counts'].each do |filter, count|
           create_event :payload => { 'filter' => filter, 'count' => count, 'time' => Time.now.to_i }
         end
@@ -106,7 +106,7 @@ module Agents
     protected
 
     def lookup_filter(filter)
-      options['filters'].each do |known_filter|
+      interpolated['filters'].each do |known_filter|
         if known_filter == filter
           return filter
         elsif known_filter.is_a?(Array)
@@ -119,7 +119,7 @@ module Agents
 
     def remove_unused_keys!(agent, base)
       if agent.memory[base]
-        (agent.memory[base].keys - agent.options['filters'].map {|f| f.is_a?(Array) ? f.first.to_s : f.to_s }).each do |removed_key|
+        (agent.memory[base].keys - agent.interpolated['filters'].map {|f| f.is_a?(Array) ? f.first.to_s : f.to_s }).each do |removed_key|
           agent.memory[base].delete(removed_key)
         end
       end

+ 5 - 5
app/models/agents/twitter_user_agent.rb

@@ -51,7 +51,7 @@ module Agents
     default_schedule "every_1h"
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def default_options
@@ -76,15 +76,15 @@ module Agents
     end
 
     def starting_at
-      if options[:starting_at].present?
-        Time.parse(options[:starting_at]) rescue created_at
+      if interpolated[:starting_at].present?
+        Time.parse(interpolated[:starting_at]) rescue created_at
       else
         created_at
       end
     end
 
     def include_retweets?
-      options[:include_retweets] != "false"
+      interpolated[:include_retweets] != "false"
     end
 
     def check
@@ -93,7 +93,7 @@ module Agents
       opts.merge! :since_id => since_id unless since_id.nil?
 
       # http://rdoc.info/gems/twitter/Twitter/REST/Timelines#user_timeline-instance_method
-      tweets = twitter.user_timeline(options['username'], opts)
+      tweets = twitter.user_timeline(interpolated['username'], opts)
 
       tweets.each do |tweet|
         if tweet.created_at >= starting_at

+ 7 - 7
app/models/agents/weather_agent.rb

@@ -51,11 +51,11 @@ module Agents
     default_schedule "8pm"
 
     def working?
-      event_created_within?((options['expected_update_period_in_days'].presence || 2).to_i) && !recent_error_logs?
+      event_created_within?((interpolated['expected_update_period_in_days'].presence || 2).to_i) && !recent_error_logs?
     end
 
     def key_setup?
-      options['api_key'].present? && options['api_key'] != "your-key"
+      interpolated['api_key'].present? && interpolated['api_key'] != "your-key"
     end
 
     def default_options
@@ -69,15 +69,15 @@ module Agents
     end
 
     def service
-      options["service"].presence || "wunderground"
+      interpolated["service"].presence || "wunderground"
     end
 
     def which_day
-      (options["which_day"].presence || 1).to_i
+      (interpolated["which_day"].presence || 1).to_i
     end
 
     def location
-      options["location"].presence || options["zipcode"]
+      interpolated["location"].presence || interpolated["zipcode"]
     end
 
     def validate_options
@@ -89,12 +89,12 @@ module Agents
     end
 
     def wunderground
-      Wunderground.new(options['api_key']).forecast_for(location)['forecast']['simpleforecast']['forecastday'] if key_setup?
+      Wunderground.new(interpolated['api_key']).forecast_for(location)['forecast']['simpleforecast']['forecastday'] if key_setup?
     end
 
     def forecastio
       if key_setup?
-        ForecastIO.api_key = options['api_key']
+        ForecastIO.api_key = interpolated['api_key']
         lat, lng = location.split(',')
         ForecastIO.forecast(lat,lng)['daily']['data']
       end

+ 4 - 4
app/models/agents/webhook_agent.rb

@@ -27,7 +27,7 @@ module Agents
     event_description do
       <<-MD
         The event payload is base on the value of the `payload_path` option,
-        which is set to `#{options['payload_path']}`.
+        which is set to `#{interpolated['payload_path']}`.
       MD
     end
 
@@ -40,7 +40,7 @@ module Agents
     def receive_web_request(params, method, format)
       secret = params.delete('secret')
       return ["Please use POST requests only", 401] unless method == "post"
-      return ["Not Authorized", 401] unless secret == options['secret']
+      return ["Not Authorized", 401] unless secret == interpolated['secret']
 
       create_event(:payload => payload_for(params))
 
@@ -48,7 +48,7 @@ module Agents
     end
 
     def working?
-      event_created_within?(options['expected_receive_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_receive_period_in_days']) && !recent_error_logs?
     end
 
     def validate_options
@@ -58,7 +58,7 @@ module Agents
     end
 
     def payload_for(params)
-      Utils.value_at(params, options['payload_path']) || {}
+      Utils.value_at(params, interpolated['payload_path']) || {}
     end
   end
 end

+ 22 - 22
app/models/agents/website_agent.rb

@@ -55,11 +55,11 @@ module Agents
     MD
 
     event_description do
-      "Events will have the fields you specified.  Your options look like:\n\n    #{Utils.pretty_print options['extract']}"
+      "Events will have the fields you specified.  Your options look like:\n\n    #{Utils.pretty_print interpolated['extract']}"
     end
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def default_options
@@ -125,7 +125,7 @@ module Agents
     end
 
     def check
-      check_url options['url']
+      check_url interpolated['url']
     end
 
     def check_url(in_url)
@@ -136,7 +136,7 @@ module Agents
         response = faraday.get(url)
         if response.success?
           body = response.body
-          if (encoding = options['force_encoding']).present?
+          if (encoding = interpolated['force_encoding']).present?
             body = body.encode(Encoding::UTF_8, encoding)
           end
           doc = parse(body)
@@ -148,7 +148,7 @@ module Agents
             end
           else
             output = {}
-            options['extract'].each do |name, extraction_details|
+            interpolated['extract'].each do |name, extraction_details|
               if extraction_type == "json"
                 result = Utils.values_at(doc, extraction_details['path'])
                 log "Extracting #{extraction_type} at #{extraction_details['path']}: #{result}"
@@ -181,17 +181,17 @@ module Agents
               output[name] = result
             end
 
-            num_unique_lengths = options['extract'].keys.map { |name| output[name].length }.uniq
+            num_unique_lengths = interpolated['extract'].keys.map { |name| output[name].length }.uniq
 
             if num_unique_lengths.length != 1
-              error "Got an uneven number of matches for #{options['name']}: #{options['extract'].inspect}"
+              error "Got an uneven number of matches for #{interpolated['name']}: #{interpolated['extract'].inspect}"
               return
             end
 
             old_events = previous_payloads num_unique_lengths.first
             num_unique_lengths.first.times do |index|
               result = {}
-              options['extract'].keys.each do |name|
+              interpolated['extract'].keys.each do |name|
                 result[name] = output[name][index]
                 if name.to_s == 'url'
                   result[name] = (response.env[:url] + result[name]).to_s
@@ -223,11 +223,11 @@ module Agents
     # If mode is set to 'on_change', this method may return false and update an existing
     # event to expire further in the future.
     def store_payload!(old_events, result)
-      if !options['mode'].present?
+      if !interpolated['mode'].present?
         return true
-      elsif options['mode'].to_s == "all"
+      elsif interpolated['mode'].to_s == "all"
         return true
-      elsif options['mode'].to_s == "on_change"
+      elsif interpolated['mode'].to_s == "on_change"
         result_json = result.to_json
         old_events.each do |old_event|
           if old_event.payload.to_json == result_json
@@ -238,12 +238,12 @@ module Agents
         end
         return true
       end
-      raise "Illegal options[mode]: " + options['mode'].to_s
+      raise "Illegal options[mode]: " + interpolated['mode'].to_s
     end
 
     def previous_payloads(num_events)
-      if options['uniqueness_look_back'].present?
-        look_back = options['uniqueness_look_back'].to_i
+      if interpolated['uniqueness_look_back'].present?
+        look_back = interpolated['uniqueness_look_back'].to_i
       else
         # Larger of UNIQUENESS_FACTOR * num_events and UNIQUENESS_LOOK_BACK
         look_back = UNIQUENESS_FACTOR * num_events
@@ -251,18 +251,18 @@ module Agents
           look_back = UNIQUENESS_LOOK_BACK
         end
       end
-      events.order("id desc").limit(look_back) if options['mode'].present? && options['mode'].to_s == "on_change"
+      events.order("id desc").limit(look_back) if interpolated['mode'].present? && interpolated['mode'].to_s == "on_change"
     end
 
     def extract_full_json?
-      !options['extract'].present? && extraction_type == "json"
+      !interpolated['extract'].present? && extraction_type == "json"
     end
 
     def extraction_type
-      (options['type'] || begin
-        if options['url'] =~ /\.(rss|xml)$/i
+      (interpolated['type'] || begin
+        if interpolated['url'] =~ /\.(rss|xml)$/i
           "xml"
-        elsif options['url'] =~ /\.json$/i
+        elsif interpolated['url'] =~ /\.json$/i
           "json"
         else
           "html"
@@ -295,7 +295,7 @@ module Agents
       @faraday ||= Faraday.new { |builder|
         builder.headers = headers if headers.length > 0
 
-        if (user_agent = options['user_agent']).present?
+        if (user_agent = interpolated['user_agent']).present?
           builder.headers[:user_agent] = user_agent
         end
 
@@ -318,7 +318,7 @@ module Agents
     end
 
     def basic_auth_credentials
-      case value = options['basic_auth']
+      case value = interpolated['basic_auth']
       when nil, ''
         return nil
       when Array
@@ -330,7 +330,7 @@ module Agents
     end
 
     def headers
-      options['headers'].presence || {}
+      interpolated['headers'].presence || {}
     end
   end
 end

+ 3 - 3
app/models/agents/weibo_publish_agent.rb

@@ -21,13 +21,13 @@ module Agents
 
     def validate_options
       unless options['uid'].present? &&
-        options['expected_update_period_in_days'].present?
+             options['expected_update_period_in_days'].present?
         errors.add(:base, "expected_update_period_in_days and uid are required")
       end
     end
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && most_recent_event.payload['success'] == true && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && most_recent_event.payload['success'] == true && !recent_error_logs?
     end
 
     def default_options
@@ -47,7 +47,7 @@ module Agents
         incoming_events = incoming_events.first(20)
       end
       incoming_events.each do |event|
-        tweet_text = Utils.value_at(event.payload, options['message_path'])
+        tweet_text = Utils.value_at(event.payload, interpolated(event.payload)['message_path'])
         if event.agent.type == "Agents::TwitterUserAgent"
           tweet_text = unwrap_tco_urls(tweet_text, event.payload)
         end

+ 3 - 3
app/models/agents/weibo_user_agent.rb

@@ -71,13 +71,13 @@ module Agents
 
     def validate_options
       unless options['uid'].present? &&
-        options['expected_update_period_in_days'].present?
+             options['expected_update_period_in_days'].present?
         errors.add(:base, "expected_update_period_in_days and uid are required")
       end
     end
 
     def working?
-      event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
+      event_created_within?(interpolated['expected_update_period_in_days']) && !recent_error_logs?
     end
 
     def default_options
@@ -92,7 +92,7 @@ module Agents
 
     def check
       since_id = memory['since_id'] || nil
-      opts = {:uid => options['uid'].to_i}
+      opts = {:uid => interpolated['uid'].to_i}
       opts.merge! :since_id => since_id unless since_id.nil?
 
       # http://open.weibo.com/wiki/2/statuses/user_timeline/en

+ 9 - 6
spec/models/agent_spec.rb

@@ -121,13 +121,16 @@ describe Agent do
       stub(Agents::CannotBeScheduled).valid_type?("Agents::CannotBeScheduled") { true }
     end
 
-    let(:new_instance) do
-      agent = Agents::SomethingSource.new(:name => "some agent")
-      agent.user = users(:bob)
-      agent
-    end
+    describe Agents::SomethingSource do
+      let(:new_instance) do
+        agent = Agents::SomethingSource.new(:name => "some agent")
+        agent.user = users(:bob)
+        agent
+      end
 
-    it_behaves_like HasGuid
+      it_behaves_like LiquidInterpolatable
+      it_behaves_like HasGuid
+    end
 
     describe ".default_schedule" do
       it "stores the default on the class" do

+ 0 - 2
spec/models/agents/data_output_agent_spec.rb

@@ -3,8 +3,6 @@
 require 'spec_helper'
 
 describe Agents::DataOutputAgent do
-  it_behaves_like LiquidInterpolatable
-
   let(:agent) do
     _agent = Agents::DataOutputAgent.new(:name => 'My Data Output Agent')
     _agent.options = _agent.default_options.merge('secrets' => ['secret1', 'secret2'], 'events_to_show' => 2)

+ 0 - 2
spec/models/agents/hipchat_agent_spec.rb

@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Agents::HipchatAgent do
-  it_behaves_like LiquidInterpolatable
-
   before(:each) do
     @valid_params = {
                       'auth_token' => 'token',

+ 0 - 2
spec/models/agents/human_task_agent_spec.rb

@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Agents::HumanTaskAgent do
-  it_behaves_like LiquidInterpolatable
-
   before do
     @checker = Agents::HumanTaskAgent.new(:name => "my human task agent")
     @checker.options = @checker.default_options

+ 0 - 2
spec/models/agents/jabber_agent_spec.rb

@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Agents::JabberAgent do
-  it_behaves_like LiquidInterpolatable
-
   let(:sent) { [] }
   let(:config) {
     {

+ 0 - 2
spec/models/agents/peak_detector_agent_spec.rb

@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Agents::PeakDetectorAgent do
-  it_behaves_like LiquidInterpolatable
-
   before do
     @valid_params = {
         'name' => "my peak detector agent",

+ 0 - 2
spec/models/agents/pushbullet_agent_spec.rb

@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Agents::PushbulletAgent do
-  it_behaves_like LiquidInterpolatable
-
   before(:each) do
     @valid_params = {
                       'api_key' => 'token',

+ 4 - 3
spec/models/agents/shell_command_agent_spec.rb

@@ -17,7 +17,7 @@ describe Agents::ShellCommandAgent do
     @event = Event.new
     @event.agent = agents(:jane_weather_agent)
     @event.payload = {
-      :command => "ls"
+      :cmd => "ls"
     }
     @event.save!
 
@@ -78,13 +78,14 @@ describe Agents::ShellCommandAgent do
 
   describe "#receive" do
     before do
-      stub(@checker).run_command(@valid_path, @event.payload[:command]) { ["fake ls output", "", 0] }
+      stub(@checker).run_command(@valid_path, @event.payload[:cmd]) { ["fake ls output", "", 0] }
     end
 
     it "creates events" do
+      @checker.options[:command] = "{{cmd}}"
       @checker.receive([@event])
       Event.last.payload[:path].should == @valid_path
-      Event.last.payload[:command].should == @event.payload[:command]
+      Event.last.payload[:command].should == @event.payload[:cmd]
       Event.last.payload[:output].should == "fake ls output"
     end
 

+ 0 - 2
spec/models/agents/slack_agent_spec.rb

@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Agents::SlackAgent do
-  it_behaves_like LiquidInterpolatable
-
   before(:each) do
     @valid_params = {
                       'auth_token' => 'token',

+ 0 - 2
spec/models/agents/translation_agent_spec.rb

@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Agents::TranslationAgent do
-    it_behaves_like LiquidInterpolatable
-
     before do
         @valid_params = {
             :name    => "somename",

+ 0 - 2
spec/models/agents/trigger_agent_spec.rb

@@ -1,8 +1,6 @@
 require 'spec_helper'
 
 describe Agents::TriggerAgent do
-  it_behaves_like LiquidInterpolatable
-
   before do
     @valid_params = {
       'name' => "my trigger agent",

+ 2 - 1
spec/support/shared_examples/liquid_interpolatable.rb

@@ -28,7 +28,7 @@ shared_examples_for LiquidInterpolatable do
       }
     end
 
-    it "hsould work with arrays", focus: true do
+    it "should work with arrays", focus: true do
       @checker.options = {"value" => ["{{variable}}", "Much array", "Hey, {{hello_world}}"]}
       @checker.interpolate_options(@checker.options, @event.payload).should == {
         "value" => ["hello", "Much array", "Hey, Hello world"]
@@ -53,6 +53,7 @@ shared_examples_for LiquidInterpolatable do
       @checker.interpolate_string("{{variable}} you", @event.payload).should == "hello you"
     end
   end
+
   describe "liquid tags" do
     it "should work with existing credentials" do
       @checker.interpolate_string("{% credential aws_key %}", {}).should == '2222222222-jane'