# To Do: # 1. Improve the parsing rules - require at least one variable declaration, # prohibit the vars attribute from starting or ending with garbage (anchor it) # # 2. Move test tags into the testing domain (somehow). # # Would be great if: # I could move the vars evaluation process from the if/unless tags and make it # part of this module. # I could just overload with a vars attribute without having to # rewrite the whole snippet tag - sort of a "facets/shards" for radius tags module VarTags include Radiant::Taggable class TagError < StandardError; end desc %{ Store variable values. Each variable consists of a unique name (alphanumeric without spaces) and its value. One or more variables may be declared with the @vars@ attribute. If the @store@ tag is a container tag (i.e. ...), then the variable values are available only within the container (local). If the @store@ tag is a single tag (), then the variables are available globally. The store operation trys to determine whether each value is a: * integer * float, or * boolean (true or false) * string (default if not one of the above) You can force a value to a string by wrapping the value in single quotes. *Usage:*

    
or...
...
} tag 'store' do |tag| if tag.attr['vars'] then if tag.single? then tag.globals.vars ||= {} tag.globals.vars.update(parse_vars(tag.attr['vars'])) else tag.locals.vars ||= {} tag.locals.vars.update(parse_vars(tag.attr['vars'])) tag.expand end return nil else raise TagError.new("'unless' tag must contain 'condition' attribute") end end desc %{ Renders the snippet specified in the @name@ attribute within the context of a page. Optionally, you may declare one or more variables using the @var@ attribute (the syntax for creating variable(s) is identical to @vars set=""@ except the scope of the variables is limited to the context of the snippet). *Usage:*
} tag 'snippet' do |tag| if name = tag.attr['name'] if snippet = Snippet.find_by_name(name.strip) # this if-clause is added (otherwise, this is the std radiant snippet) if tag.attr['vars'] then tag.locals.vars ||= {} tag.locals.vars.update(parse_vars(tag.attr['vars'])) end page = tag.locals.page global_page = tag.globals.actual_page || tag.globals.page page.render_snippet(snippet, global_page) else raise TagError.new('snippet not found') end else raise TagError.new("'snippet' tag must contain 'name' attribute") end end #### NEEDS HELP... #### # I created this tag to test the store tag - this is just for testing. # # How do I create them as part of the test - so they don't also appear in # production? Ideally, I'd just add them to the test.rb files somehow but # there's a whole includes taggable issue there. ################## tag 'test_inspect_vars' do |tag| if scope = tag.attr['scope'] case scope when 'local' (tag.locals.vars || {}).inspect when 'global' (tag.globals.vars || {}).inspect else raise TagError.new("`scope' attribute in `inspect_vars' tag is not valid") end else current_vars(tag).sort.inspect end end private # # Returns a combination of the local and global versions of the vars hashes # (with local versions having preference). # def current_vars(tag) (tag.locals.vars || {}).update(tag.globals.vars || {}) end # # Takes a variable string and parses it into a hash of variable/value pairs. # The format looks like "NAME1:VALUE1;NAME2:VALUE2" The rules are: # * Each pair must be separated by a semi-colon. The final pair may either # include or not include a trailing semi-colon. # * The NAME must be made up of \w characters (alphanumeric) followed by a # colon. # * The VALUE may be either wrapped in single quotes (will be read as a # string) or just a set of characters (any except semi-colon or colon). If # a single quote is needed within the string, a second single quote can be # used to 'escape' it. So, 'My Friend''s String' becomes: "My Friend's String" # * The NAME and VALUE may be padded with spaces (will be removed during # parsing). # # Once parsed, the routine "casts" the variable into either: # * Boolean # * Float # * Integer # * String # def parse_vars(var_string) var_hash = {} variable_pairs = var_string.scan(/\s*([\w]+)\s*:\s*('(?:[^']|'')*'|[^;:]+)\s*/) variable_pairs.each { |x| if (x[1][0] == 39) && (x[1][-1] == 39) then # string starts and ends with ' -> strip em off and convert '' to ' x[1] = x[1][1..(x[1].length-2)].gsub("''", "'") elsif (x[1] == 'false') || (x[1] == 'False') || (x[1] == 'FALSE') then x[1] = false elsif (x[1] == 'true') || (x[1] == 'True') || (x[1] == 'TRUE') then x[1] = true elsif (x[1].to_f.to_s == x[1]) then x[1] = x[1].to_f elsif (x[1].to_i.to_s == x[1]) then x[1] = x[1].to_i end var_hash[x[0]] = x[1] } return var_hash end end