webby 0.9.0 → 0.9.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.
Files changed (76) hide show
  1. data/History.txt +11 -0
  2. data/Manifest.txt +45 -2
  3. data/Rakefile +1 -2
  4. data/examples/webby/content/css/site.css +1 -1
  5. data/examples/webby/content/learn/index.txt +1 -1
  6. data/examples/webby/content/release-notes/index.txt +21 -0
  7. data/examples/webby/content/release-notes/rel-0-9-0/index.txt +1 -0
  8. data/examples/webby/content/release-notes/rel-0-9-1/index.txt +93 -0
  9. data/examples/webby/content/tips_and_tricks/index.txt +14 -13
  10. data/examples/webby/content/tutorial/index.txt +13 -9
  11. data/examples/webby/content/user-manual/index.txt +8 -8
  12. data/examples/webby/layouts/default.txt +1 -1
  13. data/lib/webby.rb +2 -1
  14. data/lib/webby/apps/main.rb +25 -13
  15. data/lib/webby/auto_builder.rb +2 -0
  16. data/lib/webby/builder.rb +2 -5
  17. data/lib/webby/filters.rb +5 -13
  18. data/lib/webby/renderer.rb +7 -5
  19. data/lib/webby/resources.rb +54 -14
  20. data/lib/webby/resources/db.rb +4 -4
  21. data/lib/webby/resources/layout.rb +14 -23
  22. data/lib/webby/resources/meta_file.rb +208 -0
  23. data/lib/webby/resources/page.rb +21 -58
  24. data/lib/webby/resources/partial.rb +10 -4
  25. data/lib/webby/resources/resource.rb +68 -27
  26. data/lib/webby/resources/static.rb +6 -22
  27. data/lib/webby/stelan/paginator.rb +17 -2
  28. data/spec/data/Sitefile +9 -0
  29. data/spec/data/content/_partial.txt +10 -0
  30. data/spec/data/content/css/coderay.css +111 -0
  31. data/spec/data/content/css/site.css +67 -0
  32. data/spec/data/content/css/tumblog.css +308 -0
  33. data/spec/data/content/images/tumblog/permalink.gif +0 -0
  34. data/spec/data/content/images/tumblog/rss.gif +0 -0
  35. data/spec/data/content/index.txt +19 -0
  36. data/spec/data/content/photos.txt +21 -0
  37. data/spec/data/content/tumblog/200806/the-noble-chicken/index.txt +12 -0
  38. data/spec/data/content/tumblog/200807/historical-perspectives-on-the-classic-chicken-joke/index.txt +12 -0
  39. data/spec/data/content/tumblog/200807/mad-city-chickens/index.txt +10 -0
  40. data/spec/data/content/tumblog/200807/the-wisdom-of-the-dutch/index.txt +11 -0
  41. data/spec/data/content/tumblog/200807/up-a-tree/index.txt +13 -0
  42. data/spec/data/content/tumblog/index.txt +37 -0
  43. data/spec/data/content/tumblog/rss.txt +37 -0
  44. data/spec/data/hooligans/bad_meta_data_1.txt +34 -0
  45. data/spec/data/hooligans/bad_meta_data_2.txt +34 -0
  46. data/spec/data/layouts/default.txt +58 -0
  47. data/spec/data/layouts/tumblog/default.txt +44 -0
  48. data/spec/data/layouts/tumblog/post.txt +15 -0
  49. data/spec/data/lib/breadcrumbs.rb +28 -0
  50. data/spec/data/lib/tumblog_helper.rb +32 -0
  51. data/spec/data/tasks/tumblog.rake +30 -0
  52. data/spec/data/templates/_partial.erb +10 -0
  53. data/spec/data/templates/atom_feed.erb +40 -0
  54. data/spec/data/templates/page.erb +18 -0
  55. data/spec/data/templates/presentation.erb +40 -0
  56. data/spec/data/templates/tumblog/conversation.erb +12 -0
  57. data/spec/data/templates/tumblog/link.erb +10 -0
  58. data/spec/data/templates/tumblog/photo.erb +13 -0
  59. data/spec/data/templates/tumblog/post.erb +12 -0
  60. data/spec/data/templates/tumblog/quote.erb +11 -0
  61. data/spec/spec_helper.rb +37 -0
  62. data/spec/webby/apps/generator_spec.rb +4 -0
  63. data/spec/webby/apps/main_spec.rb +16 -3
  64. data/spec/webby/filters/textile_spec.rb +20 -0
  65. data/spec/webby/renderer_spec.rb +139 -0
  66. data/spec/webby/resources/db_spec.rb +250 -0
  67. data/spec/webby/resources/layout_spec.rb +83 -0
  68. data/spec/webby/resources/meta_file_spec.rb +157 -0
  69. data/spec/webby/resources/page_spec.rb +111 -0
  70. data/spec/webby/resources/partial_spec.rb +58 -0
  71. data/spec/webby/resources/resource_spec.rb +214 -0
  72. data/spec/webby/resources/static_spec.rb +49 -0
  73. data/spec/webby/resources_spec.rb +55 -3
  74. metadata +64 -6
  75. data/lib/webby/resources/file.rb +0 -221
  76. data/spec/webby/resources/file_spec.rb +0 -104
data/lib/webby.rb CHANGED
@@ -18,9 +18,10 @@ Logging::Appender.stdout.layout = Logging::Layouts::Pattern.new(
18
18
  module Webby
19
19
 
20
20
  # :stopdoc:
21
- VERSION = '0.9.0' # :nodoc:
21
+ VERSION = '0.9.1' # :nodoc:
22
22
  LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
23
23
  PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
24
+ YAML_SEP = '---'
24
25
  # :startdoc:
25
26
 
26
27
  class Error < StandardError; end # :nodoc:
@@ -37,10 +37,12 @@ class Main
37
37
  opts.banner = 'Usage: webby [options] target [target args]'
38
38
 
39
39
  opts.separator ''
40
- opts.on('-D', '--describe [PATTERN]', 'describe the tasks (matching optional PATTERN), then exit') {|pattern| app.do_option('--describe', pattern)}
41
- opts.on('-P', '--prereqs', 'display the tasks and dependencies, then exit') {app.do_option('--prereqs', nil)}
42
- opts.on('-T', '--tasks [PATTERN]', 'display the tasks (matching optional PATTERN) with descriptions, then exit') {|pattern| app.do_option('--tasks', pattern)}
43
- opts.on('-t', '--trace', 'turn on invoke/execute tracing, enable full backtrace') {app.do_option('--trace', nil)}
40
+
41
+ desired_opts = %[--describe --prereqs --tasks --trace]
42
+ app.standard_rake_options.each do |options|
43
+ next unless desired_opts.include?(options.first)
44
+ opts.on(*options)
45
+ end
44
46
 
45
47
  opts.separator ''
46
48
  opts.separator 'common options:'
@@ -75,9 +77,10 @@ class Main
75
77
  #
76
78
  def init( args )
77
79
  # Make sure we're in a folder with a Sitefile
78
- app.do_option('--rakefile', 'Sitefile')
79
- app.do_option('--nosearch', nil)
80
- app.do_option('--silent', nil)
80
+ options = app.standard_rake_options
81
+ [['--rakefile', 'Sitefile'],
82
+ ['--no-search', nil],
83
+ ['--silent', nil]].each {|opt, value| options.assoc(opt).last.call(value)}
81
84
 
82
85
  unless app.have_rakefile
83
86
  raise RuntimeError, "Sitefile not found"
@@ -104,6 +107,12 @@ class Main
104
107
  Rake.application
105
108
  end
106
109
 
110
+ # Returns the options hash from the Rake application object.
111
+ #
112
+ def options
113
+ app.options
114
+ end
115
+
107
116
  # Search for the "Sitefile" starting in the current directory and working
108
117
  # upwards through the filesystem until the root of the filesystem is
109
118
  # reached. If a "Sitefile" is not found, a RuntimeError is raised.
@@ -144,12 +153,15 @@ class Main
144
153
  dir = ::File.dirname(dashed)
145
154
 
146
155
  args.dir = ('.' == dir ? '' : dir)
147
- args.slug = ::Webby::Resources::File.basename(dashed).to_url
148
- args.title = ::Webby::Resources::File.basename(spaced).titlecase
156
+ args.slug = ::Webby::Resources.basename(dashed).to_url
157
+ args.title = ::Webby::Resources.basename(spaced).titlecase
149
158
 
150
159
  # page should be dir/slug without leading /
151
160
  args.page = ::File.join(args.dir, args.slug).gsub(/^\//, '')
152
161
 
162
+ ext = ::File.extname(dashed)
163
+ args.page << ext unless ext.empty?
164
+
153
165
  ::Webby.site.args = args
154
166
  Object.const_set(:SITE, Webby.site)
155
167
  args
@@ -184,10 +196,10 @@ class Rake::Application
184
196
  end
185
197
  else
186
198
  width = displayable_tasks.collect { |t| t.name_with_args.length }.max || 10
187
- max_column = 80 - name.size - width - 7
199
+ max_column = truncate_output? ? terminal_width - name.size - width - 7 : nil
188
200
  displayable_tasks.each do |t|
189
201
  printf "#{name} %-#{width}s # %s\n",
190
- t.name_with_args, truncate(t.comment, max_column)
202
+ t.name_with_args, max_column ? truncate(t.comment, max_column) : t.comment
191
203
  end
192
204
  end
193
205
  end
@@ -199,12 +211,12 @@ class Rake::Application
199
211
  rescue SystemExit => ex
200
212
  # Exit silently with current status
201
213
  exit(ex.status)
202
- rescue SystemExit, GetoptLong::InvalidOption => ex
214
+ rescue SystemExit, OptionParser::InvalidOption => ex
203
215
  # Exit silently
204
216
  exit(1)
205
217
  rescue Exception => ex
206
218
  # Exit with error message
207
- $stderr.puts "webby aborted!"
219
+ $stderr.puts "#{name} aborted!"
208
220
  $stderr.puts ex.message
209
221
  if options.trace
210
222
  $stderr.puts ex.backtrace.join("\n")
@@ -30,6 +30,8 @@ class AutoBuilder
30
30
  @log = Logging::Logger[self]
31
31
 
32
32
  @builder = Builder.new
33
+ @builder.load_files
34
+
33
35
  @watcher = DirectoryWatcher.new '.', :interval => 2
34
36
  @watcher.add_observer self
35
37
 
data/lib/webby/builder.rb CHANGED
@@ -35,8 +35,8 @@ class Builder
35
35
  tmpl = opts[:from]
36
36
  raise Error, "template not given" unless tmpl
37
37
 
38
- name = ::Webby::Resources::File.basename(page)
39
- ext = ::Webby::Resources::File.extname(page)
38
+ name = ::Webby::Resources.basename(page)
39
+ ext = ::Webby::Resources.extname(page)
40
40
  dir = ::File.dirname(page)
41
41
  dir = '' if dir == '.'
42
42
 
@@ -157,9 +157,6 @@ class Builder
157
157
  nil
158
158
  end
159
159
 
160
-
161
- private
162
-
163
160
  # Scan the <code>layouts/</code> folder and the <code>content/</code>
164
161
  # folder and create a new Resource object for each file found there.
165
162
  #
data/lib/webby/filters.rb CHANGED
@@ -5,7 +5,7 @@ module Filters
5
5
 
6
6
  # Register a handler for a filter
7
7
  def register( filter, &block )
8
- handlers[filter.to_s] = block
8
+ _handlers[filter.to_s] = block
9
9
  end
10
10
 
11
11
  # Process input through filters
@@ -16,15 +16,11 @@ module Filters
16
16
 
17
17
  # Access a filter handler
18
18
  def []( name )
19
- handlers[name]
19
+ _handlers[name]
20
20
  end
21
21
 
22
- #######
23
- private
24
- #######
25
-
26
22
  # The registered filter handlers
27
- def handlers
23
+ def _handlers
28
24
  @handlers ||= {}
29
25
  end
30
26
 
@@ -53,7 +49,7 @@ module Filters
53
49
  raise ::Webby::Error, "unknown filter: #{filter.inspect}" if handler.nil?
54
50
 
55
51
  args = [result, self][0, handler.arity]
56
- handle(filter, handler, *args)
52
+ _handle(filter, handler, *args)
57
53
  end
58
54
  ensure
59
55
  @renderer.instance_variable_set(:@_cursor, @prev_cursor)
@@ -69,12 +65,8 @@ module Filters
69
65
  filters[@processed]
70
66
  end
71
67
 
72
- #######
73
- private
74
- #######
75
-
76
68
  # Process arguments through a single filter
77
- def handle(filter, handler, *args)
69
+ def _handle(filter, handler, *args)
78
70
  result = handler.call(*args)
79
71
  @processed += 1
80
72
  result
@@ -120,6 +120,8 @@ class Renderer
120
120
  ::Webby::Renderer.new(resource)._render_page
121
121
  when Resources::Partial
122
122
  _render_partial(resource, opts)
123
+ when Resources::Static
124
+ resource._read
123
125
  else
124
126
  raise ::Webby::Error, "expecting a page or a partial but got '#{resource.class.name}'"
125
127
  end
@@ -189,7 +191,7 @@ class Renderer
189
191
  #
190
192
  def _render_page
191
193
  _track_rendering(@page.path) {
192
- Filters.process(self, @page, ::Webby::Resources::File.read(@page.path))
194
+ Filters.process(self, @page, @page._read)
193
195
  }
194
196
  end
195
197
 
@@ -203,7 +205,7 @@ class Renderer
203
205
  def _render_partial( part, opts = {} )
204
206
  _track_rendering(part.path) {
205
207
  _configure_locals(opts[:locals])
206
- Filters.process(self, part, ::Webby::Resources::File.read(part.path))
208
+ Filters.process(self, part, part._read)
207
209
  }
208
210
  end
209
211
 
@@ -248,8 +250,7 @@ class Renderer
248
250
  return if lyt.nil?
249
251
 
250
252
  _track_rendering(lyt.path) {
251
- @content = Filters.process(
252
- self, lyt, ::Webby::Resources::File.read(lyt.path))
253
+ @content = Filters.process(self, lyt, lyt._read)
253
254
  _render_layout_for(lyt)
254
255
  }
255
256
  end
@@ -270,7 +271,8 @@ class Renderer
270
271
  @_content_for.clear
271
272
  @_bindings.clear
272
273
  else
273
- @page.number = nil
274
+ @pager.pager.done
275
+ @pager = nil
274
276
  return false
275
277
  end
276
278
 
@@ -44,25 +44,36 @@ module Webby::Resources
44
44
  end
45
45
 
46
46
  # see if we are dealing with a partial
47
- filename = ::Webby::Resources::File.basename(fn)
47
+ filename = self.basename(fn)
48
48
  if %r/\A_/o =~ filename
49
49
  r = ::Webby::Resources::Partial.new(fn)
50
50
  self.partials << r
51
51
  return r
52
52
  end
53
53
 
54
- # see if we are dealing with a static resource
55
- meta = ::Webby::Resources::File.meta_data(fn)
56
- if meta.nil?
57
- r = ::Webby::Resources::Static.new(fn)
58
- self.pages << r
59
- return r
54
+ begin
55
+ fd = ::File.open(fn, 'r')
56
+ mf = MetaFile.new fd
57
+
58
+ # see if we are dealing with a static resource
59
+ unless mf.meta_data?
60
+ r = ::Webby::Resources::Static.new(fn)
61
+ self.pages << r
62
+ return r
63
+ end
64
+
65
+ # this is a renderable page
66
+ mf.each do |meta_data|
67
+ r = ::Webby::Resources::Page.new(fn, meta_data)
68
+ self.pages << r
69
+ r
70
+ end
71
+ rescue MetaFile::Error => err
72
+ logger.error "error loading file #{fn.inspect}"
73
+ logger.error err
74
+ ensure
75
+ fd.close if fd
60
76
  end
61
-
62
- # this is a renderable page
63
- r = ::Webby::Resources::Page.new(fn)
64
- self.pages << r
65
- return r
66
77
  end
67
78
 
68
79
  # Returns a normalized path for the given filename.
@@ -77,7 +88,7 @@ module Webby::Resources
77
88
  def find_layout( filename )
78
89
  return if filename.nil?
79
90
 
80
- fn = ::Webby::Resources::File.basename(filename)
91
+ fn = self.basename(filename)
81
92
  dir = ::File.dirname(filename)
82
93
  dir = '.' == dir ? '' : dir
83
94
 
@@ -87,8 +98,37 @@ module Webby::Resources
87
98
  raise Webby::Error, "could not find layout #{filename.inspect}"
88
99
  end
89
100
 
90
- end # class << self
101
+ # Returns the directory component of the _filename_ with the content
102
+ # directory removed from the beginning if it is present.
103
+ #
104
+ def dirname( filename )
105
+ rgxp = %r/\A(?:#{::Webby.site.content_dir}|#{::Webby.site.layout_dir})\//o
106
+ dirname = ::File.dirname(filename)
107
+ dirname << '/' if dirname.index(?/) == nil
108
+ dirname.sub(rgxp, '')
109
+ end
110
+
111
+ # Returns the last component of the _filename_ with any extension
112
+ # information removed.
113
+ #
114
+ def basename( filename )
115
+ ::File.basename(filename, '.*')
116
+ end
117
+
118
+ # Returns the extension (the portion of file name in path after the
119
+ # period). This method excludes the period from the extension name.
120
+ #
121
+ def extname( filename )
122
+ ::File.extname(filename).tr('.', '')
123
+ end
91
124
 
125
+ # :stopdoc:
126
+ def logger
127
+ @logger ||= ::Logging::Logger[self]
128
+ end
129
+ # :startdoc:
130
+
131
+ end # class << self
92
132
  end # module Webby::Resources
93
133
 
94
134
  Webby.require_all_libs_relative_to(__FILE__)
@@ -23,7 +23,7 @@ class DB
23
23
  # time if it already exists in the database.
24
24
  #
25
25
  def add( page )
26
- ary = @db[page.dir]
26
+ ary = @db[page.directory]
27
27
 
28
28
  # make sure we don't duplicate pages
29
29
  ary.delete page if ary.include? page
@@ -183,7 +183,7 @@ class DB
183
183
  # Reverse the order of the results
184
184
  #
185
185
  def siblings( page, opts = {} )
186
- ary = @db[page.dir].dup
186
+ ary = @db[page.directory].dup
187
187
  ary.delete page
188
188
  return ary unless opts.has_key? :sort_by
189
189
 
@@ -207,7 +207,7 @@ class DB
207
207
  # Reverse the order of the results
208
208
  #
209
209
  def children( page, opts = {} )
210
- rgxp = Regexp.new "\\A#{page.dir}/[^/]+"
210
+ rgxp = Regexp.new "\\A#{page.directory}/[^/]+"
211
211
 
212
212
  keys = @db.keys.find_all {|k| rgxp =~ k}
213
213
  ary = keys.map {|k| @db[k]}
@@ -229,7 +229,7 @@ class DB
229
229
  # of the current directory or the next directory up the chain.
230
230
  #
231
231
  def parent_of( page )
232
- dir = page.dir
232
+ dir = page.directory
233
233
 
234
234
  loop do
235
235
  if @db.has_key? dir
@@ -16,41 +16,32 @@ class Layout < Resource
16
16
  def initialize( fn )
17
17
  super
18
18
 
19
- @mdata = ::Webby::Resources::File.meta_data(@path)
20
- @mdata ||= {}
21
- @mdata.sanitize!
19
+ @_meta_data = MetaFile.meta_data(@path)
20
+ @_meta_data ||= {}
21
+ @_meta_data.sanitize!
22
22
  end
23
23
 
24
- # call-seq:
25
- # destination => string
26
- #
27
- # The output file destination for the layout. This is the ".cairn" file in
28
- # the output folder. It is used to determine if the layout is newer than
29
- # the build products.
30
- #
31
- def destination
32
- ::Webby.cairn
33
- end
34
-
35
- # call-seq:
36
- # extension => string or nil
37
- #
38
24
  # Returns the extension to be applied to output files rendered by the
39
25
  # layotut. This will either be a string or +nil+ if the layout does not
40
26
  # specify an extension to use.
41
27
  #
42
28
  def extension
43
- return @mdata['extension'] if @mdata.has_key? 'extension'
29
+ return _meta_data['extension'] if _meta_data.has_key? 'extension'
44
30
 
45
- if @mdata.has_key? 'layout'
46
- lyt = ::Webby::Resources.find_layout(@mdata['layout'])
47
- ext = lyt ? lyt.extension : nil
31
+ if _meta_data.has_key? 'layout'
32
+ lyt = ::Webby::Resources.find_layout(_meta_data['layout'])
33
+ lyt ? lyt.extension : nil
48
34
  end
49
35
  end
50
36
 
51
- # call-seq:
52
- # url => nil
37
+ # The output file destination for the layout. This is the ".cairn" file in
38
+ # the output folder. It is used to determine if the layout is newer than
39
+ # the build products.
53
40
  #
41
+ def destination
42
+ ::Webby.cairn
43
+ end
44
+
54
45
  # Layouts do not have a URL. This method will alwasy return +nil+.
55
46
  #
56
47
  def url
@@ -0,0 +1,208 @@
1
+ require 'yaml'
2
+
3
+ module Webby::Resources
4
+
5
+ # The MetaFile class is used to read meta-data and content from files. The
6
+ # meta-data is in a YAML block located at the top of the file. The content
7
+ # is the remainder of the file (everything after the YAML block).
8
+ #
9
+ # The meta-data data must be found between two YAML block separators "---",
10
+ # each on their own line.
11
+ #
12
+ # Example:
13
+ #
14
+ # ---
15
+ # layout: blog
16
+ # filter: markdown
17
+ # tags:
18
+ # - ruby
19
+ # - web development
20
+ # ---
21
+ # This is a blog entry formatted using MarkDown and tagged as "ruby" and
22
+ # "web development". The layout being used is the "blog" format.
23
+ #
24
+ class MetaFile
25
+
26
+ class Error < StandardError; end
27
+
28
+ META_SEP = %r/\A---\s*(?:\r\n|\n)?\z/ # :nodoc:
29
+ ERR_MSG = "corrupt meta-data (perhaps there is an errant YAML marker '---' in the file)" # :nodoc:
30
+
31
+ # call-seq:
32
+ # MetaFile.read( filename ) => string
33
+ #
34
+ # Opens the file identified by _filename_ and returns the contents of the
35
+ # file as a string. Any meta-data at the top of the file is skipped and
36
+ # is not included in the returned string. If the file contains no
37
+ # meta-data, then this method behaves the same as File#read.
38
+ #
39
+ def self.read( name )
40
+ ::File.open(name, 'r') {|fd| MetaFile.new(fd).read}
41
+ end
42
+
43
+ # call-seq:
44
+ # MetaFile.meta_data( filename ) => object or nil
45
+ #
46
+ # Opens the file identified by _filename_ and returns the meta-data
47
+ # located at the top of the file. If the file contains no meta-data, then
48
+ # +nil+ is returned.
49
+ #
50
+ def self.meta_data( name )
51
+ ::File.open(name, 'r') {|fd| MetaFile.new(fd).meta_data}
52
+ end
53
+
54
+ # call-seq:
55
+ # MetaFile.meta_data?( filename ) => true or false
56
+ #
57
+ # Opens the file identified by _filename_ and returns true if there is a
58
+ # meta-data block at the top of the file, and returns false if there is
59
+ # not a meta-data block at the top of the file.
60
+ #
61
+ def self.meta_data?( name )
62
+ ::File.open(name, 'r') {|fd| MetaFile.new(fd).meta_data?}
63
+ end
64
+
65
+ # Creates a new MetaFile parser that will read from the given _io_ stream.
66
+ #
67
+ def initialize( io )
68
+ raise ArgumentError, "expecting an IO stream" unless io.respond_to? :gets
69
+ @io = io
70
+ @meta_count = 0
71
+ end
72
+
73
+ # Returns the entire contents of the IO stream exluding any meta-data
74
+ # found at the beginning of the stream.
75
+ #
76
+ def read
77
+ @io.seek(meta_end || 0)
78
+ @io.read
79
+ end
80
+
81
+ # Reads in each meta-data section and yields it to the given block. The
82
+ # first meta-data section is yielded "as is", but subsequent meta-data
83
+ # sections are merged with this first section and then yielded. This
84
+ # allows the user to define common items in the first meta-data section
85
+ # and only include items that are different in the subsequent sections.
86
+ #
87
+ # Example:
88
+ #
89
+ # ---
90
+ # title: First Title
91
+ # author: me
92
+ # directory: foo/bar/baz
93
+ # ---
94
+ # title: Second Title
95
+ # author: you
96
+ # ---
97
+ # title: Third Title
98
+ # author: them
99
+ # ---
100
+ #
101
+ # and parsing the meta-data above yields ...
102
+ #
103
+ # meta_file.each do |hash|
104
+ # pp hash
105
+ # end
106
+ #
107
+ # the following output
108
+ #
109
+ # { 'title' => 'First Title',
110
+ # 'author' => 'me',
111
+ # 'directory' => 'foo/bar/baz' }
112
+ #
113
+ # { 'title' => 'Second Title',
114
+ # 'author' => 'you',
115
+ # 'directory' => 'foo/bar/baz' }
116
+ #
117
+ # { 'title' => 'Third Title',
118
+ # 'author' => 'them',
119
+ # 'directory' => 'foo/bar/baz' }
120
+ #
121
+ # Even though the "directory" item only appears in the first meta-data
122
+ # block, it is copied to all the subsequent blocks.
123
+ #
124
+ def each
125
+ return unless meta_data?
126
+
127
+ first, count = nil, 0
128
+ @io.seek 0
129
+
130
+ buffer = @io.gets
131
+ while count < @meta_count
132
+ while (line = @io.gets) !~ META_SEP
133
+ buffer << line
134
+ end
135
+
136
+ h = YAML.load(buffer)
137
+ raise Error, ERR_MSG unless h.instance_of?(Hash)
138
+
139
+ if first then h = first.merge(h)
140
+ else first = h.dup end
141
+
142
+ buffer = line
143
+ count += 1
144
+
145
+ yield h
146
+ end
147
+ rescue ArgumentError => err
148
+ msg = ERR_MSG.dup << "\n\t-- " << err.message
149
+ raise Error, msg
150
+ end
151
+
152
+ # Returns the meta-data defined at the top of the file. Returns +nil+ if
153
+ # no meta-data is defined. The meta-data is returned as Ruby objects
154
+ #
155
+ # Meta-data is stored in YAML format between two YAML separators "---" on
156
+ # their own lines.
157
+ #
158
+ def meta_data
159
+ return if meta_end.nil?
160
+
161
+ @io.seek 0
162
+ return YAML.load(@io)
163
+ end
164
+
165
+ # Returns true if the IO stream contains meta-data. Returns false if the
166
+ # IO stream does not contain meta-data.
167
+ #
168
+ def meta_data?
169
+ meta_end.nil? ? false : true
170
+ end
171
+
172
+ # Returns the number of meta-data blocks at the top of the file.
173
+ #
174
+ def meta_count
175
+ meta_end
176
+ @meta_count
177
+ end
178
+
179
+ # Returns the position in the IO stream where the meta-data ends and the
180
+ # regular data begins. If there is no meta-data in the stream, returns +nil+.
181
+ #
182
+ def meta_end
183
+ return @meta_end if defined? @meta_end
184
+ @meta_end = nil
185
+
186
+ @io.seek 0
187
+ line = @io.read(4)
188
+ return unless META_SEP =~ line
189
+
190
+ @io.seek 0
191
+ pos = nil
192
+ buffer = @io.gets.length
193
+ while line = @io.gets
194
+ buffer += line.length
195
+ if META_SEP =~ line
196
+ pos = buffer
197
+ @meta_count += 1
198
+ end
199
+ end
200
+ return if pos.nil?
201
+
202
+ @meta_end = pos
203
+ end
204
+
205
+ end # class MetaFile
206
+ end # module Webby::Resources
207
+
208
+ # EOF