tadpole 0.1.0

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.
Files changed (60) hide show
  1. data/LICENSE +22 -0
  2. data/README.html +222 -0
  3. data/README.markdown +259 -0
  4. data/benchmarks/eval-vs-non-eval.rb +13 -0
  5. data/benchmarks/require-vs-none.rb +10 -0
  6. data/benchmarks/run-caching.rb +20 -0
  7. data/examples/example1/custom/html/body/important.html +4 -0
  8. data/examples/example1/custom/html/body/setup.rb +6 -0
  9. data/examples/example1/custom/html/setup.rb +3 -0
  10. data/examples/example1/default/html/body/info.erb +3 -0
  11. data/examples/example1/default/html/body/setup.rb +11 -0
  12. data/examples/example1/default/html/main.haml +6 -0
  13. data/examples/example1/default/html/setup.rb +3 -0
  14. data/examples/example1/run.rb +6 -0
  15. data/examples/example2/run.rb +5 -0
  16. data/examples/example2/tadpole/html/content.haml +5 -0
  17. data/examples/example2/tadpole/html/main.haml +17 -0
  18. data/examples/example2/tadpole/html/readme/setup.rb +6 -0
  19. data/examples/example2/tadpole/html/setup.rb +6 -0
  20. data/examples/example2/tadpole/markdown/copyright.md +5 -0
  21. data/examples/example2/tadpole/markdown/examples.md +76 -0
  22. data/examples/example2/tadpole/markdown/quick/create.md +34 -0
  23. data/examples/example2/tadpole/markdown/quick/heirarchical.md +34 -0
  24. data/examples/example2/tadpole/markdown/quick/override.md +28 -0
  25. data/examples/example2/tadpole/markdown/quick/quick.erb +5 -0
  26. data/examples/example2/tadpole/markdown/quick/setup.rb +4 -0
  27. data/examples/example2/tadpole/markdown/readme/readme_notice.txt +7 -0
  28. data/examples/example2/tadpole/markdown/readme/setup.rb +4 -0
  29. data/examples/example2/tadpole/markdown/setup.rb +3 -0
  30. data/examples/example2/tadpole/markdown/title.md +5 -0
  31. data/examples/example2/tadpole/markdown/what.md +25 -0
  32. data/examples/example2/tadpole/markdown/why.md +41 -0
  33. data/lib/tadpole/filters.rb +50 -0
  34. data/lib/tadpole/local_template.rb +32 -0
  35. data/lib/tadpole/main.rb +125 -0
  36. data/lib/tadpole/providers/erb.rb +26 -0
  37. data/lib/tadpole/providers/file.rb +9 -0
  38. data/lib/tadpole/providers/haml.rb +21 -0
  39. data/lib/tadpole/providers/markaby.rb +7 -0
  40. data/lib/tadpole/providers/section_provider.rb +62 -0
  41. data/lib/tadpole/providers/template.rb +24 -0
  42. data/lib/tadpole/template.rb +258 -0
  43. data/lib/tadpole.rb +32 -0
  44. data/spec/basic_spec.rb +88 -0
  45. data/spec/examples/filters/a.txt +0 -0
  46. data/spec/examples/filters/b.txt +0 -0
  47. data/spec/examples/filters/setup.rb +9 -0
  48. data/spec/examples/render/1/a.txt +1 -0
  49. data/spec/examples/render/1/b.txt +1 -0
  50. data/spec/examples/render/1/d.erb +1 -0
  51. data/spec/examples/render/1/setup.rb +13 -0
  52. data/spec/examples/render/2/setup.rb +9 -0
  53. data/spec/examples/render/3/setup.rb +12 -0
  54. data/spec/examples/render/4/a.erb +1 -0
  55. data/spec/examples/render/4/setup.rb +9 -0
  56. data/spec/examples/render/5/setup.rb +17 -0
  57. data/spec/examples/render/6/setup.rb +12 -0
  58. data/spec/filters_spec.rb +37 -0
  59. data/spec/render_spec.rb +32 -0
  60. metadata +136 -0
@@ -0,0 +1,6 @@
1
+ inherits '../markdown'
2
+
3
+ def init
4
+ super
5
+ sections 'main', ['title', 'content', sections[1..-2], 'copyright']
6
+ end
@@ -0,0 +1,5 @@
1
+ Copyright & Licensing Information
2
+ ---------------------------------
3
+
4
+ _Copyright 2008 Loren Segal._
5
+ _All code licensed under the MIT License._
@@ -0,0 +1,76 @@
1
+ Some _Theoretically_-Real-World Examples of Tadpole in Action
2
+ -----------------------------------------------------------
3
+
4
+ 1. _Bob_ made a Blogging application called _"Boblog"_ and distributed it under the
5
+ MIT license over the internet. _Janet_ found this application and decided to
6
+ use it to power her upcoming "100 Carrot Recipes" blog. She was mostly happy with
7
+ the provided themes but wanted to customize the look of the sidebar by adding a
8
+ "Favourite Recipes" links section and writing a tidbit about herself. Now "Boblog"
9
+ was a simplistic blog tool and did not support a multitude of plugins, but did use
10
+ **Tadpole** for theming. Janet read about the way customization was done using "Boblog"
11
+ and got to work making her changes. Janet went into her custom templates directory and
12
+ created her own new template `janet` because she had a bad feeling about directly playing
13
+ with the existing template files (_and "Boblog" docs said she didn't need to_). Inside
14
+ that directory she created her new files `fav_recipes.html` and `about.html` where she
15
+ inserted her links to various world renowned Carrot Chefs and a story about her dreams
16
+ of one day meeting them. Now, she wanted her about section to go at the top of the sidebar,
17
+ but she wanted her own links section to go beneath the regular links section (already
18
+ provided by "Boblog"). So, as per the docs, she continued to add a `setup.rb` file which
19
+ would connect her new files together with the template. In this file, she simply wrote:
20
+
21
+ inherits 'default_theme'
22
+
23
+ def init
24
+ super
25
+ sections.unshift 'about'
26
+ sections.place('fav_recipes').before('links')
27
+ end
28
+
29
+ She then went into "Boblog"'s administration interface and selected the new `janet`
30
+ theme. Voila, her dream of tasty success would finally come true.
31
+
32
+ Three days later, _Bob_ got word from an anonymous tipster of a nasty bug in his software
33
+ that could potentially lead to harmful attacks if left unfixed. Guess what, that bug was
34
+ in the sidebar template that Janet was using! He quickly patched the bug and released a
35
+ fix, notifying all of his users of the changes (Janet was on his mailing list). Because
36
+ Janet never edited any of the files belonging to "Boblog", all she had to do was download
37
+ the patch and restart the application without ever having to remake all of her ever-so-
38
+ important theming changes to her blog.
39
+
40
+ 2. Midget Inc. is working on a colourful new site redesign for their mobile widget business.
41
+ They sell mobile widgets to customers all across the globe and have very strict legal
42
+ procedures they need to follow when advertising their mobile widgets. In one specific
43
+ region, they are required by law to show a disclaimer above any advertising images they
44
+ display. Rather than place region specific logic inside a partial view, they decide to
45
+ use **Tadpole** to handle their templating system. They decide to use a folder structure
46
+ of the following to display their advertising page:
47
+
48
+ advertising/
49
+ setup.rb
50
+ content.erb
51
+ images.erb
52
+ fr/
53
+ setup.rb
54
+ disclaimer.erb
55
+
56
+ `advertising/setup.rb` contains the following:
57
+
58
+ def init; super; sections 'content', 'images' end
59
+
60
+ The `fr/` subdirectory contains the specific content they need for the law-requiring region
61
+ and the logic to render this template only in that region is controlled by the controller.
62
+ Because the `fr/` template automatically inherits its sections from its parent, all they need
63
+ to do to set up this new logic is put the following in the `fr/setup.rb`:
64
+
65
+ def init
66
+ super
67
+ sections.place('disclaimer').before('images')
68
+ end
69
+
70
+ And the templates can be rendered with:
71
+
72
+ Tadpole('advertising').run
73
+ Tadpole('advertising/fr').run
74
+
75
+ Respectively.
76
+
@@ -0,0 +1,34 @@
1
+ ### Create a Template
2
+
3
+ Creating a template is literally as easy as 1-2-3:
4
+
5
+ 1. Create your templates in a directory. All directories are templates and all templates
6
+ are directories. The directory name will be the (or part of) the name of your template.
7
+ Example for template `mytemplate`:
8
+
9
+ templates/
10
+ mytemplate/
11
+ setup.rb
12
+ section1.erb
13
+ section2.haml
14
+ copyright.html
15
+
16
+ 2. Setup the "_table of contents_" of your sections in the `setup.rb`:
17
+
18
+ def init
19
+ super
20
+ sections 'section1', 'section2', 'copyright'
21
+ end
22
+
23
+ Your sections can include another template (directory). This will call whatever
24
+ sections were part of that other template.
25
+
26
+ A directory does not _require_ a `section.rb`. If it is not supplied, it will inherit
27
+ the setup file from its parent (including its sections).
28
+
29
+ 3. Register the `templates` path as your root template directory and run the template:
30
+
31
+ require 'tadpole'
32
+ Tadpole.register_template_path 'path/to/templates'
33
+ Tadpole('mytemplate').run
34
+
@@ -0,0 +1,34 @@
1
+ ### Heirarchical Sections
2
+
3
+ Sometimes you may need to encapsulate the output of some sections inside another one. An HTML
4
+ template, for example, will usually contain the page body inside the body tag of a more general
5
+ "header" template. To set this up, you use the following `sections` call:
6
+
7
+ sections 'header', ['section1', 'section2', 'copyright']
8
+
9
+ You can then call these from your `header.erb` file as simple yields. Each yield renders
10
+ one section in the sub-list:
11
+
12
+ <html>
13
+ <body>
14
+ <h1>Section 1</h1>
15
+ <%= yield %>
16
+
17
+ <h1>Section 2</h1>
18
+ <%= yield %>
19
+
20
+ <h1>Copyright</h1>
21
+ <%= yield %>
22
+ </body>
23
+ </html>
24
+
25
+ Alternatively you can yield all sub-sections with the convenience call `all_sections`
26
+ (in the following [Haml](http://haml.hamptoncatlin.com) example, yield param 's'
27
+ contains the section name which would serve as the li's id attribute):
28
+
29
+ %html
30
+ %body
31
+ %ol
32
+ - all_sections do |s|
33
+ %li{:id => s}= yield
34
+
@@ -0,0 +1,28 @@
1
+ ### Override a Template
2
+
3
+ You can override templates by simply registering another template_path and creating
4
+ a template of the same name in the new path. Using the `mytemplate` example from above
5
+ we can now make a directory:
6
+
7
+ custom_templates/
8
+ mytemplate/
9
+ setup.rb
10
+ header.erb
11
+
12
+ This template will _inherit_ from the template above. Our `setup.rb` will therefore
13
+ contain:
14
+
15
+ def init
16
+ super
17
+ sections.unshift 'header'
18
+ end
19
+
20
+ And to run this file all we need to do is:
21
+
22
+ require 'tadpole'
23
+ Tadpole.register_template_path 'path/to/templates' # Register base template path
24
+ Tadpole.register_template_path 'path/to/custom_templates' # Register overridden template path
25
+
26
+ # Running our template will now add our 'header' file to the output
27
+ Tadpole('mytemplate').run
28
+
@@ -0,0 +1,5 @@
1
+ Quick How-To's
2
+ --------------
3
+
4
+ <%= yieldall %>
5
+
@@ -0,0 +1,4 @@
1
+ def init
2
+ super
3
+ sections 'quick', ['create', 'override', 'heirarchical']
4
+ end
@@ -0,0 +1,7 @@
1
+ You Should Also Know
2
+ --------------------
3
+
4
+ That this `README` was generated by **Tadpole**. Try it:
5
+
6
+ ruby examples/example2/run.rb markdown/readme
7
+
@@ -0,0 +1,4 @@
1
+ def init
2
+ super
3
+ sections.place('readme_notice').before('copyright')
4
+ end
@@ -0,0 +1,3 @@
1
+ def init
2
+ sections 'title', :quick, 'what', 'why', 'examples', 'copyright'
3
+ end
@@ -0,0 +1,5 @@
1
+ Tadpole: A Small but Extensible Templating Engine for Ruby
2
+ ==========================================================
3
+
4
+ _Created by [Loren Segal](http://www.gnuu.org) in 2008_
5
+
@@ -0,0 +1,25 @@
1
+ What is Tadpole?
2
+ ----------------
3
+
4
+ **Tadpole** is a small templating engine that attempts to solve a problem that
5
+ no other templating engine does: _extensibility_. When dealing with templates,
6
+ most engines focus on the formatting of the output content while forgetting about
7
+ the important task a developer faces of hooking all these 'views' together. While
8
+ it may seem trivial and worth ignoring, in reality, many templates become plagued
9
+ with complexity and coupling due to the lack of support beyond the mere presentation
10
+ of a single file.
11
+
12
+ **Tadpole** deals not with the formatting or translation or markup, but rather
13
+ with the organization of the data as it is outputted. In fact, **Tadpole** is not
14
+ markup at all, nor does it care what markup you use, having out of the box support
15
+ for the obvious template languages in Ruby (ERB, [Haml](http://www.haml.hamptoncatlin.com),
16
+ [Markaby](http://code.whytheluckystiff.net/markaby), [Builder](http://builder.rubyforge.org))
17
+ and the ability to add more. **Tadpole is all about information organization, not formatting**.
18
+
19
+ If you need a good visualization of what **Tadpole** is, think of it as _the table of_
20
+ _contents for your views_. Just as it is important to designing each view and partial of
21
+ your template, it is important to decide in what order these views will ultimately be
22
+ organized. **Tadpole**'s job is to store nothing but your table of contents, and then
23
+ spit it out when you're ready to show it to the world. This technique becomes very
24
+ powerful in some potentially familiar scenarios. (_See the "Real World Examples"_)
25
+
@@ -0,0 +1,41 @@
1
+ Why Tadpole?
2
+ ------------
3
+
4
+ Sufficed to say, you might be wondering what the _big deal_ is. I mean, you're
5
+ probably getting along just fine without this new concept of tables of contents
6
+ ..._or so you think_. The truth is that there are a lot of real-world scenarios
7
+ where the old-style template production simply doesn't cut it. I can say this
8
+ because **Tadpole** was [born from one of them](http://www.github.com/lsegal/yard).
9
+
10
+ I'll be honest, **Tadpole** does not meet every use-case scenario, and it probably
11
+ never will. But if you're writing a _blog app_, _CMS_, customizable _forum software_ or
12
+ anything that will eventually support _customizable templates_ or _theming_,
13
+ _**Tadpole** was made for you_. Even if you're just dealing with a lot of _template_
14
+ _coupling_ or _internationalization code_, there's a good chance you're looking at the
15
+ right tool as well.
16
+
17
+ ### Good With Customizable Themes, You Say?
18
+
19
+ Anyone who writes customizable software knows that it requires a lot of de-coupled code.
20
+ While templates are sometimes considered support files rather than actual software, the
21
+ same law holds true for them. Coupled templates are bad for theming because your users
22
+ can't get at the data they want.
23
+
24
+ The standard solution to this problem is to split your templates up into many 'partials'.
25
+ That way, any user can just go into the right partial and easily edit what they need
26
+ without copying _all_ of the template data, right? __Wrong__. The problem starts when a
27
+ user wants to start adding or removing partials altogether. In fact, it's actually the
28
+ smallest changes that cause the biggest problems. Everytime they add one line to every
29
+ new partial, they pull in another entire file. Once _you_ update that file, the changes
30
+ no longer sync to the user. Fixed a typo in your template? Your user may never get the
31
+ memo if he pulled in the file you touched. However, because **Tadpole** never actually
32
+ deals with template _data_, the same setup in **Tadpole** would not be problematic.
33
+ Using **Tadpole**, the user would never even have to touch your templates given a good
34
+ set of insertion points.
35
+
36
+ The lesson is, when it comes to customization, there is no partial that is partial enough.
37
+ **Tadpole** inevitably suffers from this problem as well. However, once you start thinking
38
+ in terms of template organization you'll find that it's much harder to decide what part
39
+ of a view deserves a 'partial' than it is to simply split your template up into a series
40
+ of cohesive "_sections_".
41
+
@@ -0,0 +1,50 @@
1
+ module Tadpole
2
+ module Filters
3
+ module ClassMethods
4
+ def before_section(*args)
5
+ if args.size == 1
6
+ before_section_filters.push [nil, args.first]
7
+ elsif args.size == 2
8
+ before_section_filters.push(args)
9
+ else
10
+ raise ArgumentError, "before_section takes a section followed by a Proc/lambda or Symbol referencing the method name"
11
+ end
12
+ end
13
+
14
+ def before_section_filters
15
+ @before_section_filters ||= []
16
+ end
17
+
18
+ def before_run(meth)
19
+ before_run_filters.push(meth)
20
+ end
21
+
22
+ def before_run_filters
23
+ @before_run_filters ||= []
24
+ end
25
+ end
26
+
27
+ module InstanceMethods
28
+ def run_before_run
29
+ self.class.before_run_filters.each do |meth|
30
+ meth = method(meth) if meth.is_a?(Symbol)
31
+ result = meth.call
32
+ return result if result.is_a?(FalseClass)
33
+ end
34
+ end
35
+
36
+ def run_before_sections
37
+ self.class.before_section_filters.each do |info|
38
+ result, sec, meth = nil, *info
39
+ if sec.nil? || sec.to_s == current_section.to_s
40
+ meth = method(meth) if meth.is_a?(Symbol)
41
+ args = meth.arity == 0 ? [] : [current_section]
42
+ result = meth.call(*args)
43
+ end
44
+
45
+ return result if result.is_a?(FalseClass)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,32 @@
1
+ module Tadpole
2
+ module LocalTemplate
3
+ module ClassMethods
4
+ attr_accessor :path
5
+ def template_paths; @template_paths ||= [] end
6
+ def inherited_paths; @inherited_paths ||= [] end
7
+
8
+ def inherits(*templates)
9
+ templates.each do |template|
10
+ p = template.to_s[0,1] == '/' ? [template.to_s[1..-1]] : [path, template]
11
+ mod = Tadpole.template(*p)
12
+ include mod
13
+ before_run_filters.push(*mod.before_run_filters)
14
+ before_section_filters.push(*mod.before_section_filters)
15
+ inherited_paths.push(*mod.template_paths)
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.included(klass)
21
+ klass.extend ClassMethods
22
+ klass.extend Filters::ClassMethods
23
+ end
24
+
25
+ def T(extra_path, extra_opts = {})
26
+ opts = options.to_hash.update(extra_opts)
27
+ Template(path, extra_path).new(opts)
28
+ end
29
+
30
+ included(self)
31
+ end
32
+ end
@@ -0,0 +1,125 @@
1
+ class Array
2
+ def place(a) Insertion.new(self, a) end
3
+ end
4
+
5
+ class Insertion
6
+ def initialize(list, value) @list, @value = list, value end
7
+ def before(val) insertion(val, 0) end
8
+ def after(val) insertion(val, 1) end
9
+ private
10
+ def insertion(val, rel)
11
+ if index = @list.index(val)
12
+ @list[index+rel,0] = @value
13
+ end
14
+ @list
15
+ end
16
+ end
17
+
18
+ module Tadpole
19
+ class << self
20
+ attr_accessor :caching
21
+
22
+ def caching; @caching ||= true end
23
+
24
+ def template_paths
25
+ @@template_paths ||= []
26
+ end
27
+
28
+ def register_template_path(path)
29
+ template_paths.push(path)
30
+ end
31
+
32
+ def template(*path)
33
+ path = absolutize_path(*path)
34
+ name = template_mod_name(path)
35
+
36
+ remove_const(name) unless caching rescue NameError
37
+ return const_get(name) rescue NameError
38
+
39
+ exists = find_matching_template_paths(path)
40
+ if exists.empty?
41
+ raise ArgumentError, "no such template `#{path}'"
42
+ end
43
+
44
+ mod = create_template(path)
45
+ const_set(name, mod)
46
+ end
47
+
48
+ private
49
+
50
+ def absolutize_path(*path)
51
+ path = path.inject([]) do |p, s|
52
+ s = s.to_s; s[0, 1] == '/' ? p = [s.gsub(/^\/+/, '')] : p << s
53
+ end.join(File::SEPARATOR)
54
+ File.expand_path(File.join(path))[(Dir.pwd.length+1)..-1]
55
+ end
56
+
57
+ def create_template(path)
58
+ mod = Module.new
59
+ mod.send(:include, Template)
60
+ mod.path = path
61
+ mod.template_paths = []
62
+ mod.before_run_filters = LocalTemplate.before_run_filters.dup
63
+ mod.before_section_filters = LocalTemplate.before_section_filters.dup
64
+
65
+ inherited = []
66
+ path.split(File::SEPARATOR).inject([]) do |list, el|
67
+ list << el
68
+ total_list = File.join(list)
69
+ find_matching_template_paths(total_list).each do |subpath|
70
+ submod = create_template_mod(subpath, total_list)
71
+ mod.send :include, submod
72
+ #if total_list == path
73
+ mod.template_paths.unshift(*submod.template_paths)
74
+ mod.before_run_filters.push(*submod.before_run_filters)
75
+ mod.before_section_filters.push(*submod.before_section_filters)
76
+ inherited.push(*submod.inherited_paths)
77
+ #mod.sections = submod.sections
78
+ #end
79
+ end
80
+ list
81
+ end
82
+
83
+ mod.template_paths.push(*inherited)
84
+ mod.template_paths.uniq!
85
+ mod
86
+ end
87
+
88
+ def create_template_mod(full_path, path)
89
+ name = 'Local'+template_mod_name(full_path)
90
+
91
+ remove_const(name) unless caching rescue NameError
92
+ return const_get(name) rescue NameError
93
+
94
+ mod = Module.new
95
+ mod.send(:include, LocalTemplate)
96
+ mod.path = path
97
+ mod.template_paths.unshift(full_path)
98
+ load_setup_rb(full_path, mod)
99
+
100
+ const_set(name, mod)
101
+ end
102
+
103
+ def find_matching_template_paths(path)
104
+ template_paths.map do |template_path|
105
+ full_path = File.join(template_path, path)
106
+ File.directory?(full_path) ? full_path : nil
107
+ end.compact
108
+ end
109
+
110
+ def load_setup_rb(full_path, mod)
111
+ setup_file = File.join(full_path, 'setup.rb')
112
+ if File.file? setup_file
113
+ mod.module_eval(File.read(setup_file).taint, setup_file)
114
+ end
115
+ mod
116
+ end
117
+
118
+ def template_mod_name(full_path)
119
+ 'Template_' + absolutize_path(full_path).gsub(File::SEPARATOR, '_')
120
+ end
121
+ end
122
+ end
123
+
124
+ def Tadpole(*path) Tadpole.template(*path) end
125
+ alias Template Tadpole
@@ -0,0 +1,26 @@
1
+ require 'erb'
2
+
3
+ module Tadpole
4
+ module SectionProviders
5
+ class ERBProvider < SectionProvider
6
+ EXTENSIONS = ['.erb']
7
+
8
+ def initialize(full_path, owner)
9
+ super
10
+
11
+ erb = ERB.new(content, nil, '<>')
12
+ instance_eval(<<-eof, full_path, -4)
13
+ def render(locals = nil, &block)
14
+ if locals
15
+ opts = owner.options
16
+ owner.options = owner.options.to_hash.update(locals)
17
+ end
18
+ out = owner.instance_eval #{erb.src.inspect}
19
+ owner.options = opts if locals
20
+ out
21
+ end
22
+ eof
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ module Tadpole
2
+ module SectionProviders
3
+ class FileProvider < SectionProvider
4
+ EXTENSIONS = ['.txt', '.html', '.textile', '.md', '.markdown', 'markdn', '']
5
+
6
+ def render(locals = nil); content end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,21 @@
1
+ begin require 'haml'; rescue LoadError; end
2
+
3
+ module Tadpole
4
+ module SectionProviders
5
+ class HamlProvider < SectionProvider
6
+ EXTENSIONS = ['.haml']
7
+
8
+ def initialize(full_path, owner)
9
+ super
10
+ @haml = Haml::Engine.new(content)
11
+ rescue NameError => e
12
+ STDERR.puts "You're missing Haml! Install the gem with `gem install haml`."
13
+ exit
14
+ end
15
+
16
+ def render(locals = {}, &block)
17
+ @haml.render(owner, locals, &block)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,7 @@
1
+ module Tadpole
2
+ module SectionProviders
3
+ class MarkabyProvider < SectionProvider
4
+ EXTENSIONS = ['.mab']
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,62 @@
1
+ module Tadpole
2
+ module SectionProviders
3
+ class << self
4
+ def providers
5
+ @providers ||= []
6
+ end
7
+
8
+ def register_provider(*provs)
9
+ providers.push(*provs)
10
+ end
11
+ end
12
+
13
+ class SectionProvider
14
+ attr_accessor :owner, :content
15
+
16
+ EXTENSIONS = []
17
+
18
+ # You don't need to override this method unless
19
+ # you need custom functionality beyond checking
20
+ # if a file exists under one of the possible file
21
+ # extensions.
22
+ #
23
+ # @return [String, nil] The full pathname
24
+ def self.provides?(basename)
25
+ self.const_get("EXTENSIONS").any? do |ext|
26
+ path = basename + ext
27
+ return path if path_suitable?(path)
28
+ end
29
+ nil
30
+ end
31
+
32
+ # Override this if you want to provide another
33
+ # mechanism to detect that the path is suitable
34
+ # for use. Default just checks File.file?(full_path)
35
+ def self.path_suitable?(full_path)
36
+ File.file?(full_path)
37
+ end
38
+
39
+ # @abstract
40
+ def render(locals = {}, &block)
41
+ raise NotImplementedError, "abstract class"
42
+ end
43
+
44
+ def initialize(full_path, owner = nil)
45
+ self.owner = owner
46
+ self.content = File.read(full_path)
47
+ end
48
+
49
+ def inspect; "#<%s:0x%s>" % [self.class.to_s.split('::').last, object_id.to_s(16)] end
50
+
51
+ def method_missing(meth, *args, &block)
52
+ if owner.options.respond_to?(meth)
53
+ owner.options[meth]
54
+ elsif owner.respond_to?(meth)
55
+ owner.send(meth, *args, &block)
56
+ else
57
+ super
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,24 @@
1
+ module Tadpole
2
+ module SectionProviders
3
+ class TemplateProvider < SectionProvider
4
+ EXTENSIONS = ['']
5
+
6
+ def self.path_suitable?(full_path) File.directory?(full_path) end
7
+
8
+ def initialize(full_path, owner)
9
+ self.owner = owner
10
+ Tadpole.template_paths.each do |template_path|
11
+ if full_path.index(template_path) == 0
12
+ path = full_path[template_path.length..-1]
13
+ @template = Tadpole(owner.path, path).new(owner.options)
14
+ end
15
+ end
16
+ raise ArgumentError, "no template at `#{full_path}'" unless @template
17
+ end
18
+
19
+ def render(locals = {}, &block)
20
+ @template.run(locals)
21
+ end
22
+ end
23
+ end
24
+ end