fu 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fu.gemspec
4
+ gemspec
@@ -0,0 +1,53 @@
1
+ ![Puts the Fu in Mustache](http://2.bp.blogspot.com/-_i2s2gzRwgw/TZCLNfnXg4I/AAAAAAAAAEg/_fIOfF6cUxw/s1600/the-face-of-fu-manchu-original.jpg)
2
+
3
+ Fu
4
+ ==
5
+
6
+ Fu combines the logic–less portability of Mustache with the terse utility of Haml. This is what it looks like:
7
+
8
+ %ul
9
+ {{#children}}
10
+ %li {{name}}
11
+
12
+ Then in the (Sinatra) app:
13
+
14
+ get "/list" do
15
+ fu :list, :locals => {:children => [{:name => "Arne"}, {:name => "Bjarne"}]}
16
+ end
17
+
18
+ And you get:
19
+
20
+ <ul><li>Arne</li><li>Bjarne</li></ul>
21
+
22
+ A contrived example using all aspects of the syntax:
23
+
24
+ %h1 Hello, {{user_name}}
25
+ %p.text
26
+ This is a paragraph of
27
+ text.
28
+ %ul.friend_list(data-attribute1="some data", data-attribute2="{{some_mustache_data}}")
29
+ {{#friends}}
30
+ %li
31
+ {{>friend_partial}}
32
+ {{^friends}}
33
+ %p.error
34
+ You, unfortunately, have no friends.
35
+
36
+ Usage
37
+ =====
38
+
39
+ Direct:
40
+
41
+ Fu.to_mustache("%p Hello {{mustache}}")
42
+
43
+ With Sinatra and Tilt:
44
+
45
+ require 'fu/tilt'
46
+
47
+ Stick your fu-templates in your views-folder with the extension `.fu`.
48
+
49
+ Then, in your app:
50
+
51
+ get "/some_action" do
52
+ fu :some_template, :locals => {...}
53
+ end
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,29 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fu/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "fu"
7
+ s.version = Fu::VERSION
8
+ s.authors = ["Simen Svale Skogsrud"]
9
+ s.email = ["simen@bengler.no"]
10
+ s.homepage = ""
11
+ s.summary = %q{Fu template engine}
12
+ s.description = %q{Fu combines the logic–less portability of Mustache with the terse utility of Haml.}
13
+
14
+ s.rubyforge_project = "fu"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec"
23
+ s.add_development_dependency "sinatra"
24
+ s.add_development_dependency "sinatra"
25
+ s.add_development_dependency "rack-test"
26
+ s.add_development_dependency "mustache"
27
+
28
+ # s.add_runtime_dependency "rest-client"
29
+ end
@@ -0,0 +1,12 @@
1
+ # Fu combines the logic–less portability of Mustache with the terse utility of Haml.
2
+
3
+ require "fu/version"
4
+ require "fu/error"
5
+ require "fu/parser"
6
+ require "fu/mustache"
7
+
8
+ module Fu
9
+ def self.to_mustache(fu)
10
+ Fu::Mustache.new(Fu::Parser.new(fu).root).mustache
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ module Fu
2
+ # An exception raised by Fu code.
3
+ class Error < StandardError
4
+ # The line of the template on which the error occurred.
5
+ #
6
+ # @return [Fixnum]
7
+ attr_reader :position
8
+
9
+ # @param message [String] The error message
10
+ # @param line [Fixnum] See \{#line}
11
+ def initialize(message = nil, position = nil)
12
+ super(message)
13
+ @position = position
14
+ end
15
+ end
16
+
17
+ # SyntaxError is the type of exception raised when Fu encounters an
18
+ # ill-formatted document.
19
+ class SyntaxError < Fu::Error; end
20
+ end
@@ -0,0 +1,62 @@
1
+ # Renders Mustache templates from a Fu parse-tree
2
+
3
+ require 'cgi'
4
+
5
+ module Fu
6
+ class Mustache
7
+ attr_reader :mustache
8
+ SELF_CLOSING_TAGS = %w(meta img link br hr input area param col base)
9
+ BLOCK_ACTIONS = %w(# ^) # <- Mustache actions that take a block
10
+ NO_SPACE_CHARS = /[{}<>]/ # <- Characters that do not need to be separated by a space
11
+ # when joining elements (e.g. "<p>!</p>", not "<p> ! </p>")
12
+
13
+ def initialize(root)
14
+ @mustache = flatten(render_children(root))
15
+ end
16
+
17
+ private
18
+
19
+ # Flatten the tag_tree inserting spaces only where they have to be.
20
+ def flatten(tag_tree)
21
+ tag_tree.flatten.inject("") do |result, element|
22
+ if result[-1] =~ NO_SPACE_CHARS || element[0] =~ NO_SPACE_CHARS
23
+ "#{result}#{element}"
24
+ else
25
+ "#{result} #{element}"
26
+ end
27
+ end
28
+ end
29
+
30
+ # Returns a tag-tree of nested arrays reflecting the structure of the
31
+ # document. E.g. ["<p>",["<em>", "Italicized text", "</em>"],"</p>"]
32
+ def render_children(node)
33
+ node.children.map { |child| self.send("render_#{child.type}", child) }
34
+ end
35
+
36
+ def render_text(node)
37
+ [CGI.escapeHTML(node.text), render_children(node)].compact
38
+ end
39
+
40
+ def render_mustache(node)
41
+ /^\s*(?<action>[#>^]?)\s*(?<identifier>.*)\s*$/ =~ node.statement
42
+ if BLOCK_ACTIONS.include?(action)
43
+ ["{{#{action}#{identifier}}}", render_children(node), "{{/#{identifier}}}"]
44
+ else
45
+ ["{{#{node.statement}}}", render_children(node)]
46
+ end
47
+ end
48
+
49
+ def render_element(node)
50
+ tag = (node.tag || 'div').downcase
51
+ attributes = (node.attributes||{}).dup
52
+ attributes[:class] = [attributes[:class], node.css_classes].flatten.compact.join(' ')
53
+ attributes[:id] = node.dom_id
54
+ attribute_string = attributes.select{|k,v| v && !v.empty?}.map{|k,v| "#{k}=\"#{v}\""}.map{|s| " #{s}"}.join
55
+ if SELF_CLOSING_TAGS.include?(tag) && node.children.empty?
56
+ ["<#{tag}#{attribute_string}/>"]
57
+ else
58
+ ["<#{tag}#{attribute_string}>", render_children(node), "</#{tag}>"]
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,90 @@
1
+ # Parses Fu templates
2
+
3
+ require 'strscan'
4
+ require 'ostruct'
5
+
6
+ module Fu
7
+ class Parser
8
+ attr_reader :root
9
+
10
+ def initialize(text)
11
+ @root = OpenStruct.new(:type => :root, :children => [])
12
+ scanner = StringScanner.new(text.gsub(/\t/, ' '))
13
+ parse_children(@root, scanner)
14
+ end
15
+
16
+ private
17
+
18
+ def parse_children(parent, scanner, parent_indent = -1)
19
+ indent = (scanner.check(/\ +/) || '').size
20
+ while indent > parent_indent && !scanner.eos? do
21
+ node = parse_line(parent, scanner)
22
+ parse_children(node, scanner, indent)
23
+ indent = (scanner.check(/\ +/) || '').size
24
+ end
25
+ end
26
+
27
+ def parse_line(parent, scanner)
28
+ scanner.scan(/[^\S\n]*/) # Consume any leading spaces
29
+ node = OpenStruct.new(:parent => parent, :children => [])
30
+ parent.children << node
31
+ # If present, the line must open with tag or script
32
+ if element_statement = scanner.scan(/\%[a-zA-Z0-9\-_]+/) # e.g. '%video'
33
+ node.type = :element
34
+ node.tag = element_statement[1..-1]
35
+ elsif mustache_statement = scanner.scan(/\{\{[^\S\n]*[#\^][^\S\n]*[a-zA-Z0-9_]+[^\S\n]*\}\}/) # e.g. = {{#comments}}
36
+ node.type = :mustache
37
+ node.statement = mustache_statement.scan(/[#\^]\s*[a-zA-Z0-9_]+/).flatten.first
38
+ end
39
+
40
+ # Classes and id's may be added, e.g. #my_special_header_id.alert.featured
41
+ while scan = scanner.scan(/[\.\#][a-zA-Z0-9\-_]+/) do
42
+ unless node.type.nil? || node.type == :element
43
+ raise SyntaxError.new("Can only attach id's or classes to elements", scanner.pos)
44
+ end
45
+ node.type = :element
46
+ node.tag ||= 'div'
47
+ case scan[0]
48
+ when '.' then (node.css_classes ||= []) << scan[1..-1]
49
+ when '#' then node.dom_id = scan[1..-1]
50
+ end
51
+ end
52
+
53
+ # Attributes-section, e.g. (hidden=true, data-bananas="one, two, five")
54
+ if node.type == :element && scan = scanner.scan(/[^\S\n]*\(/) # Match opening '('
55
+ node.attributes ||= {}
56
+ begin
57
+ scanner.scan(/\s*/) # Ditch whitespace
58
+ key = scanner.scan(/[a-zA-Z0-9\-_]+/)
59
+ value = nil
60
+ raise SyntaxError.new("Expected '='", scanner.pos) unless scanner.scan(/\s*\=\s*/)
61
+ if quote = scanner.scan(/['"]/)
62
+ value = scanner.scan(/[^\n]*?(?<!\\)#{'\\'+quote}/).chomp(quote) # Consume anything until a matching, unescaped quote
63
+ else
64
+ value = scanner.scan(/[a-zA-Z0-9\-_]+/) # Simple values need not be quoted
65
+ end
66
+ node.attributes[key] = value
67
+ raise SyntaxError.new("Expected attribute value", scanner.pos) unless value
68
+ scanner.scan(/\s*,/) # Discard whitespace and optional commas
69
+ end until scanner.scan(/\s*\)/) # Match ending ')'
70
+ end
71
+
72
+ # Any plaintext?
73
+ scan = scanner.scan(/[^\n]+/)
74
+ unless scan.nil? || scan.strip.empty?
75
+ if node.type # Is this a trailing child like e.g.: %h1.title This is a trailing child
76
+ node.children << OpenStruct.new(
77
+ :parent => node, :children => [],
78
+ :type => :text, :text => scan.strip
79
+ )
80
+ else # This very node is teh text!
81
+ node.type = :text
82
+ node.text = scan.strip
83
+ end
84
+ end
85
+
86
+ scanner.scan(/\n/) # Consume end of line
87
+ node
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,31 @@
1
+ # Makes Fu available through Tilt, also contains a utility
2
+ # function that will be added to Sinatra if Sinatra is
3
+ # defined.
4
+
5
+ require 'tilt'
6
+ require 'mustache'
7
+
8
+ module Tilt
9
+ class FuTemplate < Template
10
+ self.default_mime_type = "text/html"
11
+ def initialize_engine
12
+ return if defined? ::Fu
13
+ require_template_library 'fu'
14
+ end
15
+
16
+ def prepare; end
17
+
18
+ def evaluate(scope, locals, &block)
19
+ Mustache.render(Fu.to_mustache(data), locals.merge(scope.is_a?(Hash) ? scope : {}).merge({:yield => block.nil? ? '' : block.call}))
20
+ end
21
+ end
22
+ register FuTemplate, 'fu'
23
+ end
24
+
25
+ if defined?(Sinatra)
26
+ module Sinatra::Templates
27
+ def fu(template, options={}, locals={})
28
+ render :fu, template, options, locals
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Fu
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,3 @@
1
+ %ul
2
+ {{#children}}
3
+ %li {{name}}
@@ -0,0 +1,96 @@
1
+ require 'spec_helper'
2
+ describe Fu::Mustache do
3
+ it "can build a div from a single class" do
4
+ Fu.to_mustache(".klass").should eq '<div class="klass"></div>'
5
+ end
6
+
7
+ it "can add multiple classes by appending" do
8
+ Fu.to_mustache(".klass.odd").should eq '<div class="klass odd"></div>'
9
+ end
10
+
11
+ it "allows tag type to be specified on the line" do
12
+ Fu.to_mustache("%video").should eq "<video></video>"
13
+ end
14
+
15
+ it "knows that some tags are self closing" do
16
+ Fu.to_mustache("%br").should eq "<br/>"
17
+ end
18
+
19
+ it "can specify a tag name, classes and a dom-id just by piling on statements" do
20
+ result = Fu.to_mustache("%tag.klass1.klass2#identifier")
21
+ result.should =~ /^\<tag\ /
22
+ result.should =~ /class\=\"klass1 klass2\"/
23
+ result.should =~ /id\=\"identifier\"/
24
+ end
25
+
26
+ it "handles inline text children" do
27
+ Fu.to_mustache("%h1 This is a title").should eq "<h1>This is a title</h1>"
28
+ end
29
+
30
+ it "allows the designer to provide arbitrary attributes" do
31
+ result = Fu.to_mustache <<-END
32
+ %tag (a=1, b=2, data-c =
33
+ "Dette er en stor verdi")
34
+ END
35
+ result.should eq '<tag a="1" b="2" data-c="Dette er en stor verdi"></tag>'
36
+ end
37
+
38
+ it "inserts children as sub nodes and concatenates sibling nodes" do
39
+ result = Fu.to_mustache <<-END
40
+ %section
41
+ %p
42
+ This is
43
+ some text
44
+ for this paragraph.
45
+ This is added too
46
+ But this is outside
47
+ END
48
+ result.should eq "<section><p>This is some text for this paragraph. This is added too</p>But this is outside</section>"
49
+ end
50
+
51
+ it "handles complex hierarchies with attributes and cdata and multiple siblings at the root level" do
52
+ result = Fu.to_mustache <<-END
53
+ %section
54
+ %h1.header This is a header
55
+ %p(data-bananas="healthy but radioactive")
56
+ This is body
57
+ %details
58
+ %ul.big_list
59
+ %li Item 1
60
+ %li Item 2
61
+ %li Item 3
62
+ %details.secondary
63
+ %p
64
+ Some details
65
+ %section.number2
66
+ Other stuff
67
+ END
68
+ result.should eq '<section><h1 class="header">This is a header</h1><p data-bananas="healthy but radioactive">This is body<details><ul class="big_list"><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul></details><details class="secondary"><p>Some details</p></details></p></section><section class="number2">Other stuff</section>'
69
+ end
70
+
71
+ it "handles mustache sections" do
72
+ result = Fu.to_mustache <<-END
73
+ {{#children}}
74
+ {{name}} and {{address}}
75
+ END
76
+ result.should eq "{{#children}}{{name}} and {{address}}{{/children}}"
77
+ end
78
+
79
+ it "handles mustache inverted sections" do
80
+ result = Fu.to_mustache <<-END
81
+ {{^children}}
82
+ {{name}} and {{address}}
83
+ END
84
+ result.should eq "{{^children}}{{name}} and {{address}}{{/children}}"
85
+ end
86
+
87
+
88
+ it "handles mustache in attributes" do
89
+ Fu.to_mustache('%p(data-bingo="{{bingo}}")').should eq '<p data-bingo="{{bingo}}"></p>'
90
+ end
91
+
92
+ it "handles escaped quote characters in attribute values" do
93
+ Fu.to_mustache('%p(data-quoted="\\"")').should eq '<p data-quoted="\""></p>'
94
+ end
95
+
96
+ end
@@ -0,0 +1,24 @@
1
+ require 'spec_helper'
2
+ require 'sinatra'
3
+ require 'rack/test'
4
+ require 'fu/tilt'
5
+
6
+ class FuApp < Sinatra::Base
7
+ set :root, File.dirname(__FILE__)+"/fixtures"
8
+ get "/list" do
9
+ fu :list, :locals => {:children => [{:name => "Arne"}, {:name => "Bjarne"}]}
10
+ end
11
+ end
12
+
13
+ describe "API v1 posts" do
14
+ include Rack::Test::Methods
15
+
16
+ def app
17
+ FuApp
18
+ end
19
+
20
+ it "'s alive" do
21
+ get "/list"
22
+ last_response.body.should eq "<ul><li>Arne</li><li>Bjarne</li></ul>"
23
+ end
24
+ end
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler.require
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fu
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Simen Svale Skogsrud
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-12 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: &70166767622680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70166767622680
25
+ - !ruby/object:Gem::Dependency
26
+ name: sinatra
27
+ requirement: &70166767645740 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70166767645740
36
+ - !ruby/object:Gem::Dependency
37
+ name: sinatra
38
+ requirement: &70166767651400 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70166767651400
47
+ - !ruby/object:Gem::Dependency
48
+ name: rack-test
49
+ requirement: &70166768253840 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70166768253840
58
+ - !ruby/object:Gem::Dependency
59
+ name: mustache
60
+ requirement: &70166771869320 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70166771869320
69
+ description: Fu combines the logic–less portability of Mustache with the terse utility
70
+ of Haml.
71
+ email:
72
+ - simen@bengler.no
73
+ executables: []
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - .gitignore
78
+ - Gemfile
79
+ - README.md
80
+ - Rakefile
81
+ - fu.gemspec
82
+ - lib/fu.rb
83
+ - lib/fu/error.rb
84
+ - lib/fu/mustache.rb
85
+ - lib/fu/parser.rb
86
+ - lib/fu/tilt.rb
87
+ - lib/fu/version.rb
88
+ - spec/fixtures/views/list.fu
89
+ - spec/fu_spec.rb
90
+ - spec/fu_tilt_spec.rb
91
+ - spec/spec_helper.rb
92
+ homepage: ''
93
+ licenses: []
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ none: false
100
+ requirements:
101
+ - - ! '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ! '>='
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ requirements: []
111
+ rubyforge_project: fu
112
+ rubygems_version: 1.8.10
113
+ signing_key:
114
+ specification_version: 3
115
+ summary: Fu template engine
116
+ test_files:
117
+ - spec/fixtures/views/list.fu
118
+ - spec/fu_spec.rb
119
+ - spec/fu_tilt_spec.rb
120
+ - spec/spec_helper.rb