fu 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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