proton 0.3.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (126) hide show
  1. data/AUTHORS +6 -0
  2. data/HISTORY.md +200 -0
  3. data/README.md +75 -0
  4. data/Rakefile +5 -0
  5. data/TODO.md +6 -0
  6. data/bin/proton +7 -0
  7. data/data/new_site/Protonfile +12 -0
  8. data/data/new_site/README.md +10 -0
  9. data/data/new_site/_layouts/default.haml +28 -0
  10. data/data/new_site/index.haml +5 -0
  11. data/data/rack/Gemfile +2 -0
  12. data/data/rack/Gemfile.lock +40 -0
  13. data/data/rack/config.ru +24 -0
  14. data/lib/proton.rb +78 -0
  15. data/lib/proton/cli.rb +227 -0
  16. data/lib/proton/cli/helpers.rb +89 -0
  17. data/lib/proton/compass_support.rb +8 -0
  18. data/lib/proton/config.rb +116 -0
  19. data/lib/proton/helpers.rb +126 -0
  20. data/lib/proton/layout.rb +20 -0
  21. data/lib/proton/meta.rb +17 -0
  22. data/lib/proton/page.rb +431 -0
  23. data/lib/proton/partial.rb +12 -0
  24. data/lib/proton/project.rb +176 -0
  25. data/lib/proton/server.rb +99 -0
  26. data/lib/proton/set.rb +32 -0
  27. data/lib/proton/version.rb +13 -0
  28. data/test/fixture/build_options/control/page.html +0 -0
  29. data/test/fixture/build_options/control/style.css +1 -0
  30. data/test/fixture/build_options/hyde.conf +18 -0
  31. data/test/fixture/build_options/site/page.haml +0 -0
  32. data/test/fixture/build_options/site/style.scss +1 -0
  33. data/test/fixture/compass/hyde.conf +4 -0
  34. data/test/fixture/compass/site/style.scss +5 -0
  35. data/test/fixture/empty_config/hyde.conf +0 -0
  36. data/test/fixture/extensions/control/index.html +1 -0
  37. data/test/fixture/extensions/extensions/a/a.rb +1 -0
  38. data/test/fixture/extensions/extensions/hi.rb +1 -0
  39. data/test/fixture/extensions/hyde.conf +8 -0
  40. data/test/fixture/extensions/site/index.haml +1 -0
  41. data/test/fixture/fail_type/control/about/index.html +2 -0
  42. data/test/fixture/fail_type/control/about/us.html +2 -0
  43. data/test/fixture/fail_type/control/index.html +1 -0
  44. data/test/fixture/fail_type/hyde.conf +8 -0
  45. data/test/fixture/fail_type/site/index.haml +4 -0
  46. data/test/fixture/high_version/hyde.conf +1 -0
  47. data/test/fixture/high_version_2/hyde.conf +1 -0
  48. data/test/fixture/html/control/index.html +2 -0
  49. data/test/fixture/html/hyde.conf +8 -0
  50. data/test/fixture/html/site/index.html +2 -0
  51. data/test/fixture/ignores/control/about.html +1 -0
  52. data/test/fixture/ignores/hyde.conf +10 -0
  53. data/test/fixture/ignores/site/about.haml +1 -0
  54. data/test/fixture/ignores/site/hi.haml +1 -0
  55. data/test/fixture/metadata/control/index.html +4 -0
  56. data/test/fixture/metadata/hyde.conf +8 -0
  57. data/test/fixture/metadata/site/index.haml +8 -0
  58. data/test/fixture/nested_layout/control/index.html +2 -0
  59. data/test/fixture/nested_layout/hyde.conf +9 -0
  60. data/test/fixture/nested_layout/layouts/default.haml +2 -0
  61. data/test/fixture/nested_layout/layouts/post.haml +3 -0
  62. data/test/fixture/nested_layout/site/index.haml +4 -0
  63. data/test/fixture/one/control/about/index.css +1 -0
  64. data/test/fixture/one/control/about/us.html +1 -0
  65. data/test/fixture/one/control/cheers.html +5 -0
  66. data/test/fixture/one/control/css/bar.css +0 -0
  67. data/test/fixture/one/control/css/style.css +1 -0
  68. data/test/fixture/one/control/hello.html +5 -0
  69. data/test/fixture/one/control/hi.html +1 -0
  70. data/test/fixture/one/control/images/bar.gif +0 -0
  71. data/test/fixture/one/control/images/baz.png +0 -0
  72. data/test/fixture/one/control/images/foo.jpg +0 -0
  73. data/test/fixture/one/control/index.html +7 -0
  74. data/test/fixture/one/hyde.conf +9 -0
  75. data/test/fixture/one/layouts/default.haml +4 -0
  76. data/test/fixture/one/partials/menu.haml +3 -0
  77. data/test/fixture/one/public/about/index.css +1 -0
  78. data/test/fixture/one/public/about/us.html +1 -0
  79. data/test/fixture/one/public/cheers.html +5 -0
  80. data/test/fixture/one/public/css/bar.css +0 -0
  81. data/test/fixture/one/public/css/style.css +1 -0
  82. data/test/fixture/one/public/hello.html +5 -0
  83. data/test/fixture/one/public/hi.html +1 -0
  84. data/test/fixture/one/public/images/bar.gif +0 -0
  85. data/test/fixture/one/public/images/baz.png +0 -0
  86. data/test/fixture/one/public/images/foo.jpg +0 -0
  87. data/test/fixture/one/public/index.html +7 -0
  88. data/test/fixture/one/site/about/index.scss +1 -0
  89. data/test/fixture/one/site/about/us.haml +3 -0
  90. data/test/fixture/one/site/cheers.html.haml +1 -0
  91. data/test/fixture/one/site/css/bar.scss +0 -0
  92. data/test/fixture/one/site/css/style.scss +2 -0
  93. data/test/fixture/one/site/hello.haml +3 -0
  94. data/test/fixture/one/site/hi.html +3 -0
  95. data/test/fixture/one/site/images/bar.gif +0 -0
  96. data/test/fixture/one/site/images/baz.png +0 -0
  97. data/test/fixture/one/site/images/foo.jpg +0 -0
  98. data/test/fixture/one/site/index.haml +7 -0
  99. data/test/fixture/parent/control/about/index.html +2 -0
  100. data/test/fixture/parent/control/about/us.html +2 -0
  101. data/test/fixture/parent/control/index.html +1 -0
  102. data/test/fixture/parent/hyde.conf +8 -0
  103. data/test/fixture/parent/site/about/index.haml +3 -0
  104. data/test/fixture/parent/site/about/us.haml +3 -0
  105. data/test/fixture/parent/site/index.haml +3 -0
  106. data/test/fixture/sort/control/about.html +6 -0
  107. data/test/fixture/sort/control/about/hardy.html +1 -0
  108. data/test/fixture/sort/control/about/intrepid.html +1 -0
  109. data/test/fixture/sort/hyde.conf +8 -0
  110. data/test/fixture/sort/site/about.haml +3 -0
  111. data/test/fixture/sort/site/about/hardy.haml +4 -0
  112. data/test/fixture/sort/site/about/intrepid.haml +4 -0
  113. data/test/fixture/subclass/control/index.html +1 -0
  114. data/test/fixture/subclass/extensions/a/a.rb +12 -0
  115. data/test/fixture/subclass/hyde.conf +9 -0
  116. data/test/fixture/subclass/layouts/default.haml +1 -0
  117. data/test/fixture/subclass/layouts/post.haml +1 -0
  118. data/test/fixture/subclass/site/index.haml +4 -0
  119. data/test/helper.rb +36 -0
  120. data/test/unit/build_options_test.rb +18 -0
  121. data/test/unit/extensions_test.rb +17 -0
  122. data/test/unit/fixture_test.rb +122 -0
  123. data/test/unit/page_test.rb +58 -0
  124. data/test/unit/proton_test.rb +27 -0
  125. data/test/unit/set_test.rb +26 -0
  126. metadata +301 -0
data/lib/proton/cli.rb ADDED
@@ -0,0 +1,227 @@
1
+ # Class: Proton::CLI (Proton)
2
+ # Command line runner.
3
+
4
+ class Proton
5
+ class CLI < Shake
6
+ autoload :Helpers, "#{PREFIX}/proton/cli/helpers"
7
+
8
+ extend Helpers
9
+ include Defaults
10
+
11
+ task(:create) do
12
+ wrong_usage unless params.size == 1
13
+ template = File.expand_path('../../../data/new_site', __FILE__)
14
+ target = params.first
15
+
16
+ if target == '.'
17
+ pass "This is already a Proton project." if @protonfile
18
+ FileUtils.cp_r File.join(template, 'Protonfile'), target
19
+ say_status :create, 'Protonfile'
20
+ pass
21
+ end
22
+
23
+ pass "Error: target directory already exists." if File.directory?(target)
24
+
25
+ puts "Creating files in #{target}:"
26
+ puts
27
+
28
+ FileUtils.cp_r template, target
29
+ Dir[File.join(target, '**', '*')].sort.each do |f|
30
+ say_status :create, f if File.file?(f)
31
+ end
32
+
33
+ puts ""
34
+ puts "Done! You've created a new project in #{target}."
35
+ puts "Get started now:"
36
+ puts ""
37
+ puts " $ cd #{target}"
38
+ puts " $ #{executable} start"
39
+ puts ""
40
+ puts "Or build the HTML files:"
41
+ puts ""
42
+ puts " $ #{executable} build"
43
+ puts ""
44
+ end
45
+
46
+ task.description = "Starts a new Proton project"
47
+ task.usage = "create NAME"
48
+ task.category = :create
49
+
50
+ task(:build) do
51
+ pre = project.config.output_path
52
+
53
+ project.build { |page|
54
+ c, handler = if page.tilt?
55
+ [ 33, "#{page.tilt_engine_name.downcase}" ]
56
+ else
57
+ [ 30, '*' ]
58
+ end
59
+
60
+ puts ("\033[0;#{c}m%10s\033[0;32m #{pre}\033[0;m%s" % [ handler, page.path ]).strip
61
+ }
62
+ project.send :build_cleanup
63
+ end
64
+
65
+ task.description = "Builds the current project"
66
+ task.category = :project
67
+
68
+ task(:start) do
69
+ project
70
+
71
+ port = (params.extract('-p') || 4833).to_i
72
+ host = (params.extract('-o') || '0.0.0.0')
73
+ daemon = (!! params.delete('-D'))
74
+
75
+ require 'proton/server'
76
+
77
+ if daemon
78
+ pid = fork { Proton::Server.run! :Host => host, :Port => port, :quiet => true }
79
+ sleep 2
80
+ puts
81
+ puts "Listening on #{host}:#{port} on pid #{pid}."
82
+ puts "To stop: kill #{pid}"
83
+ else
84
+ Proton::Server.run! :Host => host, :Port => port
85
+ end
86
+ end
87
+
88
+ task.description = "Starts the server"
89
+ task.category = :project
90
+ task.help = %{
91
+ Usage:
92
+
93
+ #{executable} start [-p PORT] [-o HOST] [-D]
94
+
95
+ Starts an HTTP server so you may rapidly test your project locally.
96
+
97
+ If the -p and/or -o is specified, it will listen on the specified HOST:PORT.
98
+ Otherwise, the default is 0.0.0.0:4833.
99
+
100
+ If -D is specified, it goes into daemon mode.
101
+ }.gsub(/^ {4}/, '').strip.split("\n")
102
+
103
+ task(:rack) do
104
+ project
105
+
106
+ from = File.expand_path("#{PREFIX}/../data/rack/*")
107
+ files = Dir[from]
108
+
109
+ files.each do |f|
110
+ FileUtils.cp f, '.'
111
+ say_status :create, File.basename(f)
112
+ end
113
+ end
114
+
115
+ task.description = "Makes a project Rack-compatible."
116
+ task.category = :project
117
+
118
+ task(:version) do
119
+ puts "Proton #{Proton::VERSION}"
120
+ end
121
+
122
+ task.description = "Shows the current version"
123
+ task.category = :misc
124
+
125
+ task(:help) do
126
+ show_help_for(params.first) and pass if params.any?
127
+
128
+ show_task = Proc.new { |name, t| err " %-20s %s" % [ t.usage || name, t.description ] }
129
+
130
+ err "Usage: #{executable} <command>"
131
+
132
+ unless project?
133
+ err "\nCommands:"
134
+ tasks_for(:create).each &show_task
135
+ end
136
+
137
+ if project?
138
+ err "\nProject commands:"
139
+ tasks_for(:project).each &show_task
140
+ end
141
+
142
+ if other_tasks.any?
143
+ err "\nOthers:"
144
+ other_tasks.each &show_task
145
+ end
146
+ err "\nMisc commands:"
147
+ tasks_for(:misc).each &show_task
148
+
149
+ unless project?
150
+ err
151
+ err "Get started by typing:"
152
+ err " $ #{executable} create my_project"
153
+ end
154
+ err
155
+ err "Type `#{executable} help COMMAND` for additional help on a command."
156
+ end
157
+
158
+ task.description = "Shows help for a given command"
159
+ task.usage = "help [COMMAND]"
160
+ task.category = :misc
161
+
162
+ invalid do
163
+ task = task(command)
164
+ if task
165
+ err "Invalid usage."
166
+ err "Try: #{executable} #{task.usage}"
167
+ err
168
+ err "Type `#{executable} help` for more info."
169
+ else
170
+ err "Invalid command: #{command}"
171
+ err "Type `#{executable} help` for more info."
172
+ end
173
+ end
174
+
175
+ def self.run(*argv)
176
+ return invoke(:version) if argv == ['-v'] || argv == ['--version']
177
+ trace = (!!argv.delete('--trace'))
178
+
179
+
180
+ begin
181
+ super *argv
182
+
183
+ rescue SyntaxError => e
184
+ raise e if trace
185
+ err
186
+ say_error e.message.split("\n").last
187
+ err
188
+ say_error "You have a syntax error."
189
+ say_info "Use --trace for more info."
190
+
191
+ # Convert 'can't load redcloth' to a friendly 'please gem install RedCloth'
192
+ rescue LoadError => e
193
+ raise e if trace
194
+ show_needed_gem gem_name(e)
195
+
196
+ # Print generic errors as something friendlier
197
+ rescue => e
198
+ raise e if trace
199
+
200
+ # Can't assume that HAML is always available.
201
+ if Object.const_defined?(:Haml) && e.is_a?(Haml::Error)
202
+ # Convert HAML's "Can't run XX filter; required 'yy'" messages
203
+ # to something friendlier
204
+ needed = %w(rdiscount stringio sass/plugin redcloth)
205
+ needed.detect { |what| show_needed_gem(what) && true if e.message.include?(what) }
206
+ else
207
+ err
208
+ say_error "#{e.class}: #{e.message}"
209
+ say_info "#{e.backtrace.first}"
210
+ err
211
+ say_error "Oops! An error occured."
212
+ say_info "Use --trace for more info."
213
+ end
214
+ end
215
+ end
216
+
217
+ def self.find_config_file
218
+ Proton::CONFIG_FILES.inject(nil) { |a, fname| a ||= find_in_project(fname) }
219
+ end
220
+
221
+ def self.run!(options={})
222
+ @config_file = options[:file] || find_config_file
223
+ Proton::Project.new rescue nil
224
+ super *[]
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,89 @@
1
+ class Proton
2
+ class CLI
3
+ module Helpers
4
+ def show_help_for(name)
5
+ task = task(name)
6
+ pass "No such command. Try: #{executable} help" unless task
7
+
8
+ help = task.help
9
+ if help
10
+ help.each { |line| err line }
11
+ err
12
+ else
13
+ err "Usage: #{executable} #{task.usage || name}"
14
+ err "#{task.description}." if task.description
15
+ end
16
+ end
17
+
18
+ def tasks_for(category)
19
+ tasks.select { |name, t| t.category == category }
20
+ end
21
+
22
+ def other_tasks
23
+ tasks.select { |name, t| t.category.nil? }
24
+ end
25
+
26
+ def say_info(str)
27
+ say_status '*', str, 30
28
+ end
29
+
30
+ def say_error(str)
31
+ say_status 'error', str, 31
32
+ end
33
+
34
+ def say_status(what, cmd, color=32)
35
+ c1 = "\033[0;#{color}m"
36
+ c0 = "\033[0;m"
37
+ puts "#{c1}%10s#{c0} %s" % [ what, cmd ]
38
+ end
39
+
40
+ def show_needed_gem(name)
41
+ err
42
+ say_error "You will need additional gems for this project."
43
+ say_info "To install: gem install #{name}"
44
+ end
45
+
46
+ def no_project
47
+ "Error: no Proton config file found.\n" +
48
+ "(Looked for #{Proton::CONFIG_FILES.join(', ')})\n\n" +
49
+ "You start by creating a config file for this project:\n" +
50
+ " $ #{executable} create .\n\n" +
51
+ "You may also create an empty project in a new directory:\n" +
52
+ " $ #{executable} create NAME\n"
53
+ end
54
+
55
+ def project?
56
+ !! @config_file
57
+ end
58
+
59
+ # Gets the gem name from a LoadError exception.
60
+ def gem_name(e)
61
+ name = e.message.split(' ').last
62
+ name = 'RedCloth' if name == 'redcloth'
63
+ name = 'haml' if name == 'sass/plugin'
64
+ name
65
+ end
66
+
67
+ def project
68
+ @project ||= begin
69
+ pass no_project unless project?
70
+ Dir.chdir File.dirname(@config_file)
71
+
72
+ begin
73
+ project = Proton.project || Proton::Project.new
74
+ pass no_project unless project.config_file
75
+ rescue LegacyError
76
+ err "This is a legacy Hyde project."
77
+ err "To force it, try editing `hyde.conf` and upgrade the version line to `hyde_requirement: 0.1`."
78
+ pass
79
+ rescue VersionError => e
80
+ err e.message
81
+ pass
82
+ end
83
+
84
+ project
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,8 @@
1
+ require 'compass'
2
+
3
+ opts = Compass.sass_engine_options
4
+
5
+ [:sass, :scss].each do |type|
6
+ Proton.project.config.tilt_options(type)[:load_paths] ||= Array.new
7
+ Proton.project.config.tilt_options(type)[:load_paths] += opts[:load_paths]
8
+ end
@@ -0,0 +1,116 @@
1
+ class Proton
2
+ # Class: Proton::Config
3
+ # Configuration.
4
+ #
5
+ # ## Common usage
6
+ #
7
+ # Access it via `Proton.project`.
8
+ #
9
+ # Proton.project.config
10
+ #
11
+ # You may access config variables as attributes.
12
+ #
13
+ # Proton.project.config.site_path
14
+ # Proton.project.config.layouts_path
15
+ # Proton.project.config.extensions_path
16
+ # Proton.project.config.output_path
17
+ #
18
+ # Tilt options:
19
+ #
20
+ # Proton.project.config.tilt_options('sass')[:load_path]
21
+ # Proton.project.config.tilt_options_for('filename.haml')[:style]
22
+ #
23
+ class Config
24
+ DEFAULTS = {
25
+ :site_path => '.',
26
+ :layouts_path => '_layouts',
27
+ :extensions_path => '_extensions',
28
+ :partials_path => '_layouts',
29
+ :output_path => '_output',
30
+ :tilt_options => {
31
+ :haml => {
32
+ :escape_html => true
33
+ },
34
+ :sass => {
35
+ :load_paths => ['css', '.'],
36
+ :style => :compact,
37
+ :line_numbers => true
38
+ },
39
+ :scss => {
40
+ :load_paths => ['css', '.'],
41
+ :style => :compact,
42
+ :line_numbers => true
43
+ },
44
+ },
45
+ :tilt_build_options => {
46
+ :scss => {
47
+ :style => :compressed,
48
+ :line_numbers => false
49
+ },
50
+ :sass => {
51
+ :style => :compressed,
52
+ :line_numbers => false
53
+ }
54
+ }
55
+ }
56
+
57
+ def self.load(config_file)
58
+ new(YAML::load_file(config_file)) rescue new
59
+ end
60
+
61
+ def initialize(options={})
62
+ # Try to emulate proper .merge behavior in Ruby 1.8
63
+ #DEFAULTS.each { |k, v| options[k] ||= v }
64
+ @table = Hashie::Mash.new
65
+ @table.deep_merge! DEFAULTS
66
+ @table.deep_merge! options
67
+ end
68
+
69
+ # Passthru
70
+ def method_missing(meth, *args, &blk)
71
+ @table.send meth, *args
72
+ end
73
+
74
+ def requirement
75
+ # Backward compatibility: this config option used to be called
76
+ # `hyde_requirement` before the project was renamed to Proton.
77
+ self[:requirement] || self[:hyde_requirement]
78
+ end
79
+
80
+ # Method: tilt_options_for (Proton::Config)
81
+ # Returns tilt options for a given file.
82
+ #
83
+ # ## Usage
84
+ # tilt_options_for(filename, options={})
85
+ #
86
+ # ## Example
87
+ # tilt_options_for('index.haml') # { :escape_html => ... }
88
+ #
89
+ def tilt_options_for(file, options={})
90
+ ext = file.split('.').last.downcase
91
+ opts = tilt_options(ext) || Hash.new
92
+ opts = opts.merge(tilt_options(ext, :tilt_build_options)) if options[:build]
93
+
94
+ to_hash opts
95
+ end
96
+
97
+ # Method: tilt_options (Proton::Config)
98
+ # Returns tilt options for a given engine.
99
+ #
100
+ # ## Usage
101
+ # tilt_options(engine_name)
102
+ #
103
+ # ## Example
104
+ # tilt_options('haml') # { :escape_html => ... }
105
+ #
106
+ def tilt_options(what, key=:tilt_options)
107
+ @table[key] ||= Hash.new
108
+ @table[key][what.to_s] ||= Hash.new
109
+ end
110
+
111
+ private
112
+ def to_hash(mash)
113
+ mash.inject(Hash.new) { |h, (k, v)| h[k.to_sym] = v; h }
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,126 @@
1
+ # Module: Proton::Helpers (Proton)
2
+ # Helpers you can use in your pages.
3
+ #
4
+ # ## Creating your own helpers
5
+ # To create for own helpers, make an extension. See [Extending Proton:
6
+ # Helpers][1] for more info.
7
+ #
8
+ # [1]: /extending/helpers.html
9
+
10
+ class Proton
11
+ module Helpers
12
+
13
+ # Method: partial (Proton::Helpers)
14
+ # Renders a partial.
15
+ #
16
+ # ## Usage
17
+ # <%= partial path, locals %>
18
+ #
19
+ # ## Description
20
+ # See [Introduction: Partials](/introduction/partials.html) for more
21
+ # info.
22
+ #
23
+ # ## Example
24
+ #
25
+ # If your `_layouts/_banner.erb` looks like this:
26
+ #
27
+ # <div class='banner'>
28
+ # Welcome to <%= start.title %>
29
+ # </div>
30
+ #
31
+ # ...Then this will embed the partial `_layouts/_nav.erb`. The partial
32
+ # will be rendered with `start` being set to the current page.
33
+ #
34
+ # <%= partial '_banner', :start => page %>
35
+ #
36
+ def partial(path, locals={})
37
+ partial = Partial[path.to_s, page] or return ''
38
+ partial.to_html locals.merge(:page => self)
39
+ end
40
+
41
+ # Method: rel (Proton::Helpers)
42
+ # Turns a path into a relative path.
43
+ #
44
+ # ## Usage
45
+ # <%= rel(path) %>
46
+ #
47
+ # ## Description
48
+ # `rel` takes a given absolute path that begins with a `/` (for instance,
49
+ # `/x/y/z.html`) and returns a relative path (maybe `../y/z.html`). This is
50
+ # useful if your site will not be be hosted on it's own domain.
51
+ #
52
+ # ## Example
53
+ # <% page.children.each do |child| %>
54
+ # <a href="<%= rel(child.path) %>">
55
+ # <%= child.title %>
56
+ # </a>
57
+ # <% end %>
58
+ #
59
+ # This may output:
60
+ #
61
+ # <a href="../../foo.html">
62
+ # Foobar
63
+ # </a>
64
+ #
65
+ # ...where the `../../` depends on the current page's path.
66
+ #
67
+ def rel(path)
68
+ depth = page.path.count('/')
69
+ dotdot = depth > 1 ? ('../' * (depth-1)) : './'
70
+ (dotdot[0...-1] + path)
71
+ end
72
+
73
+ # Method: content_for (Proton::Helpers)
74
+ # Content for.
75
+ #
76
+ # ## See also
77
+ #
78
+ # * {Proton::Helpers::has_content?}
79
+ # * {Proton::Helpers::content_for}
80
+ # * {Proton::Helpers::yield_content}
81
+ #
82
+ def content_for(key, &blk)
83
+ content_blocks[key.to_sym] = blk
84
+ end
85
+
86
+ def content_blocks
87
+ $content_blocks ||= Hash.new
88
+ $content_blocks[page.path] ||= Hash.new
89
+ end
90
+
91
+ # Method: has_content? (Proton::Helpers)
92
+ # Checks if there's something defined for a given content block.
93
+ #
94
+ # ## Example
95
+ # See {Proton::Helpers::content_for} for an example.
96
+ #
97
+ # ## See also
98
+ # * {Proton::Helpers::has_content?}
99
+ # * {Proton::Helpers::content_for}
100
+ # * {Proton::Helpers::yield_content}
101
+ #
102
+ def has_content?(key)
103
+ content_blocks.member? key.to_sym
104
+ end
105
+
106
+ # Method: yield_content (Proton::Helpers)
107
+ # Yield
108
+ #
109
+ # ## Example
110
+ # See {Proton::Helpers::content_for} for an example.
111
+ #
112
+ # ## See also
113
+ # * {Proton::Helpers::has_content?}
114
+ # * {Proton::Helpers::content_for}
115
+ # * {Proton::Helpers::yield_content}
116
+ #
117
+ def yield_content(key, *args)
118
+ content = content_blocks[key.to_sym]
119
+ if respond_to?(:block_is_haml?) && block_is_haml?(content)
120
+ capture_haml(*args, &content)
121
+ elsif content
122
+ content.call(*args)
123
+ end
124
+ end
125
+ end
126
+ end