proton 0.3.0.rc1

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 (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