webby 0.9.0 → 0.9.1

Sign up to get free protection for your applications and to get access to all the features.
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