fumbler 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -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 fumbler.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,98 @@
1
+ Fumbler
2
+ =======
3
+
4
+ This is the start of a ruby template engine based on the Tumblr syntax.
5
+
6
+ It's not production ready, and this is my first gem so please try it out and let me know if I'm doing anything wrong.
7
+
8
+ Some better examples are coming, checkout the specs for basic usage.
9
+
10
+
11
+ Overview
12
+ --------
13
+
14
+ The aim is to create a safe-evaluating template engine based on the designer friendly Tumblr syntax. It will be a lot more restrictive than ERB and even Liquid, but slightly more evaluating than Mustache.
15
+
16
+ It will enable you to give the designer limited control flow, safely evaluating contexts, nest-able blocks, and a few extensions to make things easier.
17
+
18
+
19
+ Whats Implemented
20
+ -----------------
21
+
22
+
23
+ ###Model
24
+
25
+ Inherit from the Model class to create your context which the template can access, it includes a little attr_reader style helper called 'fumbleable' to make stuff available to the template so you can apply it to existing classes and not worry about giving access to db saves etc.
26
+
27
+
28
+
29
+ class Cage < Fumbler::Model
30
+ fumbleable :inside,:current
31
+
32
+ def initialize(c)
33
+ @current = c
34
+ end
35
+
36
+ def inside
37
+ "im trapped"
38
+ end
39
+
40
+ def current
41
+ @current
42
+ end
43
+ end
44
+
45
+ class Context < Fumbler::Model
46
+
47
+ fumbleable :you, :cage, :cages
48
+
49
+ def you
50
+ "dave"
51
+ end
52
+
53
+ def hidden
54
+ "you cant access me"
55
+ end
56
+
57
+ def cage
58
+ Cage.new(0)
59
+ end
60
+
61
+ def cages
62
+ [Cage.new(1),Cage.new(2)]
63
+ end
64
+ end
65
+ @context = Context.new
66
+
67
+
68
+
69
+ ###Template
70
+
71
+ This is based on [Tilt](https://github.com/rtomayko/tilt) but not everything is implemented yet.
72
+
73
+ The current syntax supports basic {tags} and {block:items}{tagsinparent}or{tagsinhere}{/block:items}, blocks can be nested and will always look to the parent for missing tags.
74
+
75
+ t = Fumbler::Template.new {"hello {you}, {block:cage}{current}{/block:cage} {block:cages}{you}{current},{/block:cages}]"}
76
+ t.render(@context)
77
+
78
+
79
+
80
+ TODO
81
+ ----
82
+
83
+ * Attributes
84
+ * Meta-settings
85
+ * Partials
86
+ * Layout Inheritance
87
+ * File handling
88
+ * Rails Integration via Tilt
89
+ * Re-write the regex as a token scanner
90
+ * Lots More!
91
+
92
+
93
+
94
+ Acknowledgements
95
+ ----------------
96
+
97
+ This is based on the [Temple](https://github.com/judofyr/temple) framework, taking a few tips from [Mustache](https://github.com/defunkt/mustache). The syntax aims to resemble that of [Tumblr](http://www.tumblr.com) and (Posterous)[http://www.posterous.com].
98
+
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
data/fumbler.gemspec ADDED
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "fumbler/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "fumbler"
7
+ s.version = Fumbler::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Anthony Corcutt"]
10
+ s.email = ["acorcutt@me.com"]
11
+ s.homepage = "https://github.com/acorcutt/fumbler"
12
+ s.summary = %q{Tumblr style template engine for ruby}
13
+
14
+ s.rubyforge_project = "fumbler"
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
+ s.add_dependency("temple")
22
+ s.add_dependency("tilt")
23
+
24
+ s.add_development_dependency("rspec", ["~> 2.4"])
25
+ end
data/lib/fumbler.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'fumbler/version'
2
+
3
+ module Fumbler
4
+ autoload :Engine, 'fumbler/engine'
5
+ autoload :Parser, 'fumbler/parser'
6
+ autoload :Filter, 'fumbler/filter'
7
+ autoload :Template, 'fumbler/template'
8
+ autoload :Model, 'fumbler/model'
9
+ end
@@ -0,0 +1,16 @@
1
+ require 'temple'
2
+ require 'fumbler'
3
+
4
+ module Fumbler
5
+ class Engine < Temple::Engine
6
+ use Fumbler::Parser
7
+ use Fumbler::Filter
8
+
9
+ #filter :EscapeHTML, :use_html_safe
10
+ filter :MultiFlattener
11
+ filter :StaticMerger
12
+ filter :DynamicInliner
13
+
14
+ generator :ArrayBuffer
15
+ end
16
+ end
@@ -0,0 +1,45 @@
1
+ require 'temple'
2
+
3
+ module Fumbler
4
+ #
5
+ # We just take our array and turn it into something Temple understands
6
+ #
7
+ # We prefix any calls with fumble_ which will be defined in our context class, and any children
8
+ #
9
+ #
10
+ class Filter < Temple::Filter
11
+ def on_multi(*exps)
12
+ exps.each_with_index do |exp,i|
13
+ name = exp[1]
14
+ case exp[0].to_sym
15
+ when :dynamic
16
+ exps[i] = [:dynamic,"fumble_#{name}"]
17
+ when :block
18
+ if exp[1] == "end"
19
+ #just strip any attributes and double up end tags to match block
20
+ exps[i] = [:block,"end;end"]
21
+ else
22
+ exps[i]=[:static,"block"]
23
+
24
+ # TODO - attributes, first we need to fix the parser to grab the whole attribute including quotes, also allow fumble_*params
25
+ atr = exp[2..-1]
26
+
27
+ # TODO - handle Arrays - think we just need to add it and also end;end above? or inject :multi
28
+ exps[i]= [:block,<<-code]
29
+ c = #{name}
30
+ c = [c] unless c.is_a?(Array)
31
+ c.each do |b|
32
+ b.fumble(self) do
33
+ code
34
+ end
35
+ end
36
+ end
37
+ return [:multi, *exps]
38
+ end
39
+
40
+ # Create evaluating string
41
+ def ev(s)
42
+ "#\{#{s}}"
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,41 @@
1
+ module Fumbler
2
+
3
+ class Model
4
+
5
+ def self.fumbleable(*accessors)
6
+ accessors.each do |m|
7
+ class_eval <<-EOS
8
+ def fumble_#{m}
9
+ self.#{m}
10
+ end
11
+ EOS
12
+ end
13
+ end
14
+
15
+ # What I'm trying todo is make blocks work inside blocks, and still be able to access
16
+ # the parents attributes, instance_eval lets us do something similar to javascripts with(o){}
17
+ # In this example we cant access 'you' inside the cage context how we would like to
18
+ # as its not a local, but see how 'outside' works. So we need to catch the missing_method
19
+ # and push send up to the parent, which might be a bit slow but it simplifies the block filter.
20
+ #
21
+ # @context.fumble(self){
22
+ # puts you #defined in root context class
23
+ # outside = "outside"
24
+ # cage.fumble(self){
25
+ # puts outside
26
+ # puts inside #defined in cage class
27
+ # puts you #this errors unless we catch method_missing
28
+ # }
29
+ # }
30
+ def fumble(parent,&block)
31
+ @parent = parent
32
+ instance_eval &block
33
+ end
34
+
35
+ def method_missing(sym, *args,&block)
36
+ if @parent
37
+ @parent.send(sym, *args,&block)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,86 @@
1
+ require 'temple'
2
+
3
+ module Fumbler
4
+ #
5
+ # Handles parsing of our template string
6
+ #
7
+ # The Basics:
8
+ #
9
+ #
10
+ # Display something:
11
+ #
12
+ # {title}
13
+ #
14
+ # Blocks:
15
+ #
16
+ # {block:item limit="10"}
17
+ # {title}
18
+ # {/block:item}
19
+ #
20
+ #
21
+ # We allow a few tag formats and special html style wrapper to hide from rich html editors
22
+ # {block:items} {{block:items}} <{block:items}> <!--{block:items}--> are all equivelant,
23
+ # The surrounding tag will always be stripped from the output.
24
+ #
25
+ # Escape things with
26
+ # {{{ <{im}> {escaped} .css{color:red} }}}
27
+ #
28
+ # TODO - make tag patterns customizable so we can do ${tag} etc.
29
+ # TODO - combine the block and tag pattern, then allow tag & block delimiters to be differerent e.g. erb style
30
+ # TODO - or switch to scanning for opening and closing tags so we can do things like {block:tag a="{" b="}"} and ignore {tag}}
31
+
32
+ class Parser
33
+ include Temple::Mixins::Options
34
+
35
+ TAG_PATTERN = /{{{(.*?)}}}|({|{{|<{|<!--{)\s*?(\/?\b.+?)\s*?(}-->|}>|}}|})/m #order of the { {{ <{ <!--{ tag groups is important
36
+
37
+ BLOCK_PATTERN = /(\/?\b\w+\b)\s*?:\s*?(\b\w+\b)\s*?(.*)/m
38
+
39
+ ATTRIBUTE_PATTERN = /\s+(\b\w+\b)\s*=\s*(?:(['"])(.*?)\2)/m #a='1' b="2"
40
+
41
+ def compile(input)
42
+ result = [:multi]
43
+ return result unless input
44
+
45
+ pos = 0
46
+ blocks = 0
47
+ input.scan(TAG_PATTERN) do |escaped,s, tag, e|
48
+ m = Regexp.last_match
49
+ text = input[pos...m.begin(0)]
50
+ pos = m.end(0)
51
+ result << [:static, text] if !text.empty?
52
+ if escaped
53
+ result << [:static, escaped]
54
+ else
55
+ m = tag.match(BLOCK_PATTERN)
56
+ if m
57
+ t = m[2]
58
+ a = []
59
+ m[3].scan(ATTRIBUTE_PATTERN) do |key,q,value|
60
+ a << [key,value]
61
+ end
62
+ case m[1]
63
+ when "block"
64
+ result << [:block,t,a]
65
+ blocks+=1
66
+ when "/block"
67
+ result << [:block,"end"]
68
+ blocks-=1
69
+ end
70
+ else
71
+ result << [:dynamic,tag]
72
+ end
73
+ end
74
+ end
75
+ text = input[pos..-1]
76
+ result << [:static, text] if !text.empty?
77
+
78
+ #close any blocks left over
79
+ blocks.times do
80
+ result << [:block,"end"]
81
+ end
82
+
83
+ result
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,8 @@
1
+ require 'temple'
2
+ require 'fumbler'
3
+
4
+ module Fumbler
5
+ class Template < Temple::Template
6
+ engine Fumbler::Engine
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Fumbler
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,44 @@
1
+ require 'fumbler/model'
2
+
3
+ describe Fumbler::Model do
4
+ before(:all) do
5
+
6
+ class Context < Fumbler::Model
7
+
8
+ fumbleable :you, :cage
9
+
10
+ def you
11
+ "dave"
12
+ end
13
+
14
+ class Cage < Fumbler::Model
15
+ fumbleable :inside
16
+
17
+ def inside
18
+ "im trapped"
19
+ end
20
+ end
21
+
22
+ def cage
23
+ Cage.new
24
+ end
25
+ end
26
+
27
+
28
+ end
29
+
30
+ it "should find all attributes" do
31
+ @context = Context.new
32
+
33
+ @context.fumble(self){
34
+ you.inspect
35
+ outside = "outside"
36
+ cage.fumble(self){
37
+ outside.inspect
38
+ inside.inspect
39
+ you.inspect
40
+ }
41
+ }
42
+ end
43
+
44
+ end
@@ -0,0 +1,51 @@
1
+ require 'fumbler/engine'
2
+ require 'fumbler/parser'
3
+
4
+ describe Fumbler::Engine do
5
+ it "should make a new temple engine" do
6
+ Fumbler::Engine.new
7
+ end
8
+ end
9
+
10
+ describe Fumbler::Parser do
11
+ it "should make a new parser" do
12
+ Fumbler::Parser.new
13
+ end
14
+
15
+ it "should parse empty string" do
16
+ Fumbler::Parser.new.compile("").should eql([:multi])
17
+ end
18
+
19
+ it "should parse some static text" do
20
+ Fumbler::Parser.new.compile("some text").should eql([:multi,[:static,"some text"]])
21
+ end
22
+
23
+ it "should parse escaped tags" do
24
+ Fumbler::Parser.new.compile("hello {{{ <{im}> {escaped} .css{color:red} }}}").should eql([:multi,[:static,"hello "],[:static," <{im}> {escaped} .css{color:red} "]])
25
+ end
26
+
27
+ it "should parse a text tag" do
28
+ Fumbler::Parser.new.compile("hello {name}-{ age }").should eql([:multi,[:static,"hello "],[:dynamic,"name"],[:static,"-"],[:dynamic,"age"]])
29
+ end
30
+
31
+ it "should parse a text tag across multi-lines" do
32
+ Fumbler::Parser.new.compile("hello {name\nhere}").should eql([:multi,[:static,"hello "],[:dynamic, "name\nhere"]])
33
+ end
34
+
35
+ it "should parse a text tag in all tag formats" do
36
+ Fumbler::Parser.new.compile("hello {a}{{b}}<{c}><!--{d}-->").should eql([:multi,[:static,"hello "],[:dynamic,"a"],[:dynamic,"b"],[:dynamic,"c"],[:dynamic,"d"]])
37
+ end
38
+
39
+ it "should parse a block tag" do
40
+ Fumbler::Parser.new.compile("hello {block:item}{name}{/block:item}").should eql([:multi,[:static,"hello "],[:block,"item",[]],[:dynamic,"name"],[:block,"end"]])
41
+ end
42
+
43
+ it "should parse a spaced block tag" do
44
+ Fumbler::Parser.new.compile("hello { block : item}{name}{/block : item }").should eql([:multi,[:static,"hello "],[:block,"item",[]],[:dynamic,"name"],[:block,"end"]])
45
+ end
46
+
47
+ it "should parse a block tag with attributes" do
48
+ Fumbler::Parser.new.compile("hello {block:item a=\"1\" b='2'}{name}{/block:item}").should eql([:multi,[:static,"hello "],[:block,"item",[["a","1"],["b","2"]]],[:dynamic,"name"],[:block,"end"]])
49
+ end
50
+
51
+ end
@@ -0,0 +1,69 @@
1
+ require 'fumbler/template'
2
+ require 'fumbler/model'
3
+
4
+ describe Fumbler::Template do
5
+ before(:all) do
6
+ class Context < Fumbler::Model
7
+
8
+ fumbleable :you, :cage, :cages
9
+
10
+ def you
11
+ "dave"
12
+ end
13
+
14
+ def hidden
15
+ "you cant access me"
16
+ end
17
+
18
+ class Cage < Fumbler::Model
19
+ fumbleable :inside,:current
20
+
21
+ def initialize(c)
22
+ @current = c
23
+ end
24
+
25
+ def inside
26
+ "im trapped"
27
+ end
28
+
29
+ def current
30
+ @current
31
+ end
32
+ end
33
+
34
+ def cage
35
+ Cage.new(0)
36
+ end
37
+
38
+ def cages
39
+ [Cage.new(1),Cage.new(2)]
40
+ end
41
+ end
42
+ @context = Context.new
43
+
44
+ end
45
+
46
+ it "should make a new temple" do
47
+ Fumbler::Template.new {}
48
+ end
49
+
50
+ it "should render some static text" do
51
+ Fumbler::Template.new{"hello from template"}.render.should eql("hello from template")
52
+ end
53
+
54
+ it "should render a tag" do
55
+ t = Fumbler::Template.new {"hello {you}"}
56
+ t.render(@context).should eql("hello dave")
57
+ end
58
+
59
+ it "should render a block" do
60
+ t = Fumbler::Template.new {"hello {you}, {block:cage}{you} {inside}{/block:cage}"}
61
+ t.render(@context).should eql("hello dave, dave im trapped")
62
+ end
63
+
64
+ it "should render a block a few times" do
65
+ t = Fumbler::Template.new {"hello {you}, [{block:cages}{you}{current},{/block:cages}]"}
66
+ t.render(@context).should eql("hello dave, [dave1,dave2,]")
67
+ end
68
+
69
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fumbler
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Anthony Corcutt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-03-06 00:00:00 +00:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: temple
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ">="
23
+ - !ruby/object:Gem::Version
24
+ version: "0"
25
+ type: :runtime
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: tilt
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: "0"
36
+ type: :runtime
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: rspec
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: "2.4"
47
+ type: :development
48
+ version_requirements: *id003
49
+ description:
50
+ email:
51
+ - acorcutt@me.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - .gitignore
60
+ - Gemfile
61
+ - README.md
62
+ - Rakefile
63
+ - fumbler.gemspec
64
+ - lib/fumbler.rb
65
+ - lib/fumbler/engine.rb
66
+ - lib/fumbler/filter.rb
67
+ - lib/fumbler/model.rb
68
+ - lib/fumbler/parser.rb
69
+ - lib/fumbler/template.rb
70
+ - lib/fumbler/version.rb
71
+ - spec/fumbler_model_spec.rb
72
+ - spec/fumbler_parser_spec.rb
73
+ - spec/fumbler_templater_spec.rb
74
+ has_rdoc: true
75
+ homepage: https://github.com/acorcutt/fumbler
76
+ licenses: []
77
+
78
+ post_install_message:
79
+ rdoc_options: []
80
+
81
+ require_paths:
82
+ - lib
83
+ required_ruby_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ required_rubygems_version: !ruby/object:Gem::Requirement
90
+ none: false
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ requirements: []
96
+
97
+ rubyforge_project: fumbler
98
+ rubygems_version: 1.6.1
99
+ signing_key:
100
+ specification_version: 3
101
+ summary: Tumblr style template engine for ruby
102
+ test_files:
103
+ - spec/fumbler_model_spec.rb
104
+ - spec/fumbler_parser_spec.rb
105
+ - spec/fumbler_templater_spec.rb