module Expressive require 'json' require "cgi" class Scope attr_reader :lookups def initialize(parent = {}) @parent = parent @symbols = {} @lookups = {} end def add_lookup_table(lookup_table_name, lookups) @lookups[lookup_table_name] = {} unless @lookups.include?(lookup_table_name) @lookups[lookup_table_name].merge!(lookups) end def add_lookup_function(lookup_function_name, passed_in_options = {}, &func_proc) @lookups[lookup_function_name] = [passed_in_options, func_proc] end def clear_lookup_tables @lookups.clear end def include?(name) @symbols.include?(name) or @lookups.include?(name) end def [](name) if @symbols[name].nil? @parent[name] else @symbols[name] end end def []=(name, value) if value if value.is_a?(ExtendedValue) @symbols[name] = value unless @symbols.include?(name) else current_value = @symbols[name] if current_value and current_value.is_a?(ExtendedValue) current_value.set(value) else @symbols[name] = value end end else @symbols[name] = value end end def merge(scope) @symbols.merge!(scope) end def override_scope(scope) scope.merge!(@symbols) @symbols = scope end def retrieve_scope @symbols end def define(name, &block) self[name] = Function.new(&block) end def syntax(name, &block) self[name] = Syntax.new(&block) end end class TopLevel < Scope def initialize super syntax('set') do |scope, cells| scope[cells.first.text_value] = cells[1].eval(scope) end syntax('$append') do |scope, cells| scope[cells.first.text_value] = [] if scope[cells.first.text_value].nil? addition = cells[1].eval(scope) if addition.is_a?(Array) scope[cells.first.text_value] = scope[cells.first.text_value] | addition else scope[cells.first.text_value] << addition end end syntax('$hash') do |scope, cells| hash = {} cells.each do |cell| key = cell.elements[1].elements[0].elements[1].text_value value = cell.elements[1].elements[1].elements[1].instance_eval{eval(scope)} hash[key] = value end hash end syntax('$index') do |scope, cell| data = cell[0].eval(scope) index = cell[1].eval(scope) data[index] end syntax('$hval') do |scope, cells| key = cells[0].eval(scope) hash = cells[1].eval(scope) hash[key] end syntax('$lookup') {|scope, cells| perform_lookup(scope, cells)} syntax('lookup') {|scope, cells| perform_lookup(scope, cells)} syntax('post') do |scope, cells| perform_webhook(:post, scope, cells) end syntax('$concat') do |scope, cells| perform_concat(scope, cells) end syntax('$join') do |scope, cells| perform_join(scope, cells) end syntax('$sms') do |scope, cells| perform_sms(scope, cells) end syntax('$random') do |scope, cells| perform_random(scope, cells) end syntax('$split') do |scope, cells| perform_split(scope, cells) end syntax('put') do |scope, cells| perform_webhook(:put, scope, cells) end syntax('get') do |scope, cells| perform_webhook(:get, scope, cells) end syntax('date') do |scope, cells| if cells.empty? Date.today.to_time.utc else date = cells.first.text_value.gsub(/[\)\(]/, '') values = date.split('/').map(&:to_i).reverse Date.new(values[0], values[1], values[2]).to_time.utc end end syntax('datetime') do |scope, cells| if cells.empty? DateTime.now else date = cells.first.text_value.gsub(/[\)\(]/, '') DateTime.parse(date) end end define('$id') {|*args| args.first.id } define('+') {|a,b| a.to_f + b.to_f } define('-') {|a,b| a.to_f - b.to_f } define('*') {|a,b| a.to_f * b.to_f } define('/') {|a,b| a.to_f / b.to_f } define('=') {|a,b| a == b } define('>') {|a,b| a.to_f > b.to_f } define('<') {|a,b| a.to_f < b.to_f } define('>=') {|a,b| a.to_f >= b.to_f } define('<=') {|a,b| a.to_f <= b.to_f } define('and') {|a,b| !!a && !!b } define('or') {|a,b| !!a || !!b } define('sum') {|*args| args.flatten.map(&:to_f).reduce(:+) || 0 } define('$sub') {|*args| result = args.flatten.map(&:to_f).reduce(:-) || 0 } define('if') {|*args| args.compact!; args[0] ? args[1] : args[2] } define('$days_ago'){|*args| Time.now - args[0].to_i * 86400 } define('$hours_ago'){|*args| Time.now - args[0].to_i * 3600 } define('$minutes_ago'){|*args| Time.now - args[0].to_i * 60 } define('$head'){|*args| args.flatten.first } define('$include'){|val, arr| arr.include?(val) } define('$tail'){|*args| args.flatten[1..-1] } define('$reverse'){|*args| args.flatten.reverse } define('$not'){|*args| not args.first } define('$round'){|*args| perform_round(args) } define('round'){|*args| perform_round(args) } end def to_hash h = self.retrieve_scope.dup h.delete_if{|k, v| v.kind_of?(Expressive::Function) || v.kind_of?(Expressive::ExtendedValue)} end private def perform_concat(scope, cells) return cells[0].eval(scope).to_s + cells[1].eval(scope).to_s end def perform_join(scope, cells) return cells[0].eval(scope).to_s + " " + cells[1].eval(scope).to_s end def perform_sms(scope, cells) uri = URI.parse("http://api.clickatell.com/http/sendmsg") query = {:user => ENV["clickatell_username"], :password => ENV["clickatell_password"], :api_id => ENV['clickatell_api_id']} to = cells[0].eval(scope) message = CGI.escape(cells[1].eval(scope)) if to[0] == "0" to = "44" + to[1..-1] elsif to[0] == "+" to = to[1..-1] end query[:to] = to query[:text] = message uri.query = query.map{|k,v| "#{k}=#{v}"}.join("&") begin response = Webhook.new(:get, uri.to_s, {}, {}).execute scope = scope.merge(response) if response.is_a?(Hash) rescue RestClient::Exception => e scope['_errors'] = e.message end scope end def perform_random(scope, cells) evaluated_cells = cells.map{|cell| cell.eval(scope)} chosen_cell = rand(Range.new(0,evaluated_cells.count-1)) evaluated_cells[chosen_cell] end def perform_split(scope, cells) return cells[0].elements[1].text_value.split(cells[1].elements[1].text_value) end #(post "http://example.com" "*" (headers "AUTH-TOKEN=13415") ) #(post "http://example.com" name email john smith (headers "AUTH-TOKEN=13415") ) def perform_round(*args) tuple = args.first tuple[0].to_f.round(tuple[1]) end def perform_lookup(scope, cells) lookup_result = scope.lookups[cells.first.text_value] if lookup_result key = cells[1] ? cells[1].text_value[1..-2] : nil if lookup_result.is_a?(Hash) lookup_result[key] elsif lookup_result.is_a?(Array) options = lookup_result.first the_proc = lookup_result.last the_proc.call(options, key, *Array(cells[2..-1]).map {|cell| cell.eval(scope)}) end end end def perform_webhook(verb, scope, cells) #convert cells to array so it can manipulated easily cells_array = cells.map{|c| c.eval(scope).kind_of?(Hash) || !!c.eval(scope) == c.eval(scope) ? c.eval(scope) : c.text_value.gsub(/\A"|"\Z/, '')} if !cells_array[2].nil? and !!cells_array[2] == cells_array[2] update_case = cells_array[2] else update_case = true end url = cells_array.shift url = Expressive.run(url, scope) if url =~ /\A\(.*\)\Z/ headers = create_webhook_headers(cells_array) params = create_webhook_parameters(scope, cells_array) begin response = Webhook.new(verb, url, params, headers).execute if update_case scope = scope.merge(response) if response.is_a?(Hash) else return response end rescue RestClient::Exception => e scope['_errors'] = e.message end scope end def create_webhook_headers(cells) header_position = cells.index("headers") return if header_position.nil? headers = cells[header_position + 1..-1] cells.delete("headers") headers.each{|h| cells.delete(h)} headers.inject({}) do |header, h| idx = h.index("=") header[h[0..(idx-1)]] = h[(idx+1)..-1] header end end def create_webhook_parameters(scope, cells) if cells.first && cells.first == '*' scope.to_hash elsif cells.first.kind_of? Hash cells.first else cells.inject({}) do |params, key| params[key] = scope[key] params end end end end end