PageRenderTime 26ms CodeModel.GetById 15ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/zafu/process/ruby_less_processing.rb

http://github.com/zena/zena
Ruby | 314 lines | 235 code | 33 blank | 46 comment | 54 complexity | 65bb151196bec82cb62fc10c433e4b50 MD5 | raw file
Possible License(s): CC-BY-SA-3.0
  1. require 'rubyless'
  2. module Zafu
  3. module Process
  4. module RubyLessProcessing
  5. EXCLUDE_KEYS_IN_ARGS = [:h]
  6. include RubyLess
  7. def self.included(base)
  8. base.process_unknown :rubyless_eval
  9. base.class_eval do
  10. def do_method(sym)
  11. super
  12. rescue RubyLess::Error => err
  13. parser_error(err.message)
  14. end
  15. end
  16. end
  17. # Actual method resolution. The lookup first starts in the current helper. If nothing is found there, it
  18. # searches inside a 'helpers' module and finally looks into the current node_context.
  19. # If nothing is found at this stage, we prepend the method with the current node and start over again.
  20. def safe_method_type(signature, receiver = nil)
  21. super || get_method_type(signature, false)
  22. end
  23. # Resolve unknown methods by using RubyLess in the current compilation context (the
  24. # translate method in RubyLess will call 'safe_method_type' in this module).
  25. def rubyless_eval(params = @params)
  26. if @method =~ /^([A-Z]\w+?)\?$/
  27. return rubyless_class_scope($1)
  28. end
  29. rubyless_render(@method, params)
  30. rescue RubyLess::NoMethodError => err
  31. klass_name = node.list_context? ? "[#{node.klass}]" : node.klass.to_s
  32. parser_continue("#{err.error_message} <span class='type'>#{err.method_with_arguments}</span> (#{klass_name} context)")
  33. rescue RubyLess::Error => err
  34. parser_continue(err.message)
  35. end
  36. # Print documentation on the current node type.
  37. def r_m
  38. if @params[:helper] == 'true'
  39. klass = helper.class
  40. else
  41. klass = node.klass
  42. end
  43. out "<div class='rubyless-m'><h3>Documentation for <b>#{klass}</b></h3>"
  44. out "<ul>"
  45. RubyLess::SafeClass.safe_methods_for(klass).each do |signature, opts|
  46. opts = opts.dup
  47. opts.delete(:method)
  48. if opts.keys == [:class]
  49. opts = opts[:class]
  50. end
  51. out "<li>#{signature.inspect} => #{opts.inspect}</li>"
  52. end
  53. out "</ul></div>"
  54. end
  55. # TEMPORARY METHOD DURING HACKING...
  56. # def r_erb
  57. # "<pre><%= @erb.gsub('<','&lt;').gsub('>','&gt;') %></pre>"
  58. # end
  59. def rubyless_render(method, params)
  60. # We need to set this here because we cannot pass options to RubyLess or get them back
  61. # when we evaluate the method to see if we can use blocks as arguments.
  62. @rendering_block_owner = true
  63. code = method_with_arguments(method, params)
  64. # It is strange that we need to freeze code... But if we don't, we
  65. # get double ##{} on some systems (Linux).
  66. rubyless_expand RubyLess.translate(self, code.freeze)
  67. ensure
  68. @rendering_block_owner = false
  69. end
  70. def set_markup_attr(markup, key, value)
  71. value = value.kind_of?(RubyLess::TypedString) ? value : RubyLess.translate_string(self, value)
  72. if value.literal
  73. markup.set_param(key, form_quote(value.literal))
  74. else
  75. markup.set_dyn_param(key, "<%= #{value} %>")
  76. end
  77. end
  78. def append_markup_attr(markup, key, value)
  79. value = RubyLess.translate_string(self, value)
  80. if value.literal
  81. markup.append_param(key, form_quote(value.literal))
  82. else
  83. markup.append_dyn_param(key, "<%= #{value} %>")
  84. end
  85. end
  86. def get_attribute_or_eval(use_string_block = true)
  87. if @params[:date] && method != 'link'
  88. return parser_continue("'date' parameter is deprecated. Please use 'attr' or 'eval'.")
  89. elsif attribute = @params[:attr]
  90. code = "this.#{attribute}"
  91. elsif code = @params[:eval] || @params[:test]
  92. elsif code = @params[:param]
  93. code = "params[:#{code}]"
  94. elsif text = @params[:text]
  95. code = "%Q{#{text}}"
  96. elsif text = @params[:t]
  97. code = "t(%Q{#{text}})"
  98. # elsif var = @params[:var]
  99. # if code = get_context_var('set_var', var)
  100. # return code
  101. # else
  102. # return parser_continue("Var #{var.inspect} not declared.")
  103. # end
  104. elsif use_string_block && @blocks.size == 1 && @blocks.first.kind_of?(String)
  105. return RubyLess::TypedString.new(@blocks.first.inspect, :class => String, :literal => @blocks.first)
  106. else
  107. return parser_continue("Missing attribute/eval parameter")
  108. end
  109. RubyLess.translate(self, code)
  110. rescue RubyLess::Error => err
  111. return parser_continue(err.message, code)
  112. end
  113. # Pass default values as parameters in @context as :param_XXXX
  114. def r_default
  115. cont = {}
  116. @params.each do |k, v|
  117. cont[:"params_#{k}"] = v
  118. end
  119. expand_with cont
  120. end
  121. private
  122. # Extract arguments from params (evaluates params as RubyLess strings).
  123. def extract_from_params(*keys)
  124. res = []
  125. keys.each do |key|
  126. next unless value = param(key.to_sym)
  127. res << ":#{key} => #{RubyLess.translate_string(self, value)}"
  128. end
  129. res.empty? ? nil : res
  130. end
  131. def param(key, default = nil)
  132. @params[key] || @context[:"params_#{key}"] || default
  133. end
  134. # Method resolution. The first matching method is returned. Order of evaluation is
  135. # 1. find node_context (@page, @image, self)
  136. # 2. set var (set_xxx = '...')
  137. # 3. template helper methods
  138. # 4. contextual node methods (var1.xxx)
  139. # 5. contextual first node of list method ([...].first.xxx)
  140. # 6. append block as argument (restart 1-5 with xxx(block_string))
  141. def get_method_type(signature, added_options = false)
  142. node = self.node
  143. if type = node_context_from_signature(signature)
  144. # Resolve this, @page, @node
  145. type
  146. elsif type = get_var_from_signature(signature)
  147. # Resolved stored set_xxx='something' in context.
  148. type
  149. elsif type = safe_method_from(helper, signature)
  150. # Resolve template helper methods
  151. type
  152. elsif helper.respond_to?(:helpers) && type = safe_method_from(helper.helpers, signature)
  153. # Resolve by looking at the included helpers
  154. type
  155. elsif node && !node.list_context? && type = safe_method_from(node.klass, signature, node)
  156. # not a list_contex
  157. # Resolve node context methods: xxx.foo, xxx.bar
  158. # All direct methods from nodes should be html escaped:
  159. type = type[:class].call(self, node.klass, signature) if type[:class].kind_of?(Proc)
  160. type.merge(:receiver => RubyLess::TypedString.new(node.name, :class => node.klass, :h => true))
  161. elsif node && node.list_context? && type = safe_method_from(Array, signature, node)
  162. # FIXME: why do we need this here ? Remove with related code in zafu_safe_definitions ?
  163. type = type[:class].call(self, node.klass, signature) if type[:class].kind_of?(Proc)
  164. type.merge(:receiver => RubyLess::TypedString.new(node.name, :class => Array, :query => node.opts[:query], :elem => node.klass.first))
  165. elsif @rendering_block_owner && @blocks.first.kind_of?(String) && !added_options
  166. # Insert the block content into the method: <r:trans>blah</r:trans> becomes trans("blah")
  167. signature_with_block = signature.dup
  168. signature_with_block << String
  169. if type = get_method_type(signature_with_block, true)
  170. type.merge(:prepend_args => RubyLess::TypedString.new(@blocks.first.inspect, :class => String, :literal => @blocks.first))
  171. else
  172. nil
  173. end
  174. elsif node && !added_options
  175. # Try prepending current node before arguments: link("foo") becomes link(var1, "foo")
  176. signature_with_node = signature.dup
  177. signature_with_node.insert(1, node.real_class) # node.klass ?
  178. if type = get_method_type(signature_with_node, true)
  179. type.merge(:prepend_args => RubyLess::TypedString.new(node.name, :class => node.klass))
  180. else
  181. nil
  182. end
  183. else
  184. nil
  185. end
  186. end
  187. def method_with_arguments(method, params)
  188. hash_arguments = []
  189. arguments = []
  190. params.keys.sort {|a,b| a.to_s <=> b.to_s}.each do |k|
  191. next if EXCLUDE_KEYS_IN_ARGS.include?(k)
  192. if k =~ /\A_/
  193. arguments << "%Q{#{params[k]}}"
  194. else
  195. hash_arguments << ":#{k} => %Q{#{params[k]}}"
  196. end
  197. end
  198. if hash_arguments != []
  199. arguments << hash_arguments.join(', ')
  200. end
  201. if arguments != []
  202. if method =~ /^(.*)\((.*)\)$/
  203. if $2 == ''
  204. "#{$1}(#{arguments.join(', ')})"
  205. else
  206. "#{$1}(#{$2}, #{arguments.join(', ')})"
  207. end
  208. else
  209. "#{method}(#{arguments.join(', ')})"
  210. end
  211. else
  212. method
  213. end
  214. end
  215. def rubyless_expand(res)
  216. if res.klass == String && !(@blocks.detect {|b| !b.kind_of?(String)})
  217. r_show(res)
  218. elsif res.klass == Boolean
  219. expand_if(res)
  220. elsif @blocks.empty?
  221. r_show(res)
  222. else
  223. expand_with_finder(:method => res, :class => res.klass, :query => res.opts[:query], :nil => res.could_be_nil?)
  224. end
  225. end
  226. def rubyless_class_scope(class_name)
  227. return parser_error("Cannot scope class in list (use each before filtering).") if node.list_context?
  228. # capital letter ==> class conditional
  229. klass = Module.const_get(class_name)
  230. if klass.ancestors.include?(node.klass)
  231. expand_if("#{node}.kind_of?(#{klass})")
  232. else
  233. # render nothing: incompatible classes
  234. ''
  235. end
  236. rescue
  237. parser_error("Invalid class name '#{class_name}'")
  238. end
  239. # Find a class or behavior based on a name. The returned class should implement
  240. # 'safe_method_type'.
  241. def get_class(class_name)
  242. Module.const_get(class_name)
  243. rescue
  244. nil
  245. end
  246. # This is used to resolve 'this' (current NodeContext), '@node' as NodeContext with class Node,
  247. # '@page' as first NodeContext of type Page, etc.
  248. def node_context_from_signature(signature)
  249. return nil unless signature.size == 1
  250. ivar = signature.first
  251. if ivar == 'this'
  252. node.opts.merge(:class => node.klass, :method => node.name)
  253. elsif ivar[0..0] == '@' && klass = get_class(ivar[1..-1].capitalize)
  254. if node = self.node(klass)
  255. node.opts.merge(:class => node.klass, :method => node.name)
  256. else
  257. nil
  258. end
  259. else
  260. nil
  261. end
  262. end
  263. # Find stored variables back. Stored elements are set with set_xxx='something to eval'.
  264. def get_var_from_signature(signature)
  265. return nil unless signature.size == 1
  266. if var = get_context_var('set_var', signature.first)
  267. {:method => var, :class => var.klass, :nil => var.could_be_nil?, :query => var.opts[:query]}
  268. else
  269. nil
  270. end
  271. end
  272. def safe_method_from(solver, signature, receiver = nil)
  273. if solver.respond_to?(:safe_method_type)
  274. solver.safe_method_type(signature, receiver)
  275. else
  276. RubyLess::SafeClass.safe_method_type_for(solver, signature)
  277. end
  278. end
  279. end # RubyLessProcessing
  280. end # Process
  281. end # Zafu