ruhoh 0.1.3 → 0.2.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.
data/Gemfile CHANGED
@@ -1,13 +1,13 @@
1
1
  source "http://rubygems.org"
2
2
  gemspec
3
3
 
4
- gem 'rake'
5
4
  gem 'rack', "~> 1.4"
6
5
  gem 'directory_watcher', "~> 1.4"
7
6
  gem 'mustache', "~> 0.99"
8
7
  gem 'maruku', "~> 0.6"
9
- gem 'aws-s3'
8
+ gem 'psych', "~> 1.3"
10
9
 
11
10
  group :development do
12
11
  gem 'rspec'
13
- end
12
+ gem 'rake'
13
+ end
data/README.md CHANGED
@@ -7,3 +7,43 @@
7
7
 
8
8
  $ gem install ruhoh
9
9
  $ ruhoh help
10
+
11
+
12
+ - Ruhoh.setup :after_setup
13
+ - Ruhoh::DB.update\_all :after_db_update
14
+ - @page = Ruhoh::Page.new :after_page_initialize
15
+ - templater
16
+ - converter
17
+
18
+
19
+ - setup
20
+ - database
21
+ - page
22
+ - templater
23
+ - converter
24
+ - preview
25
+ - compiler
26
+
27
+ ### Plugins
28
+
29
+
30
+ 1. Mustache method additions
31
+ Extend the templating language
32
+ * Mounted onto the page object
33
+
34
+
35
+ 2. Add additional converter support (textile)
36
+ * Mounted onto the page object
37
+
38
+ 3. Generators ? compose extra pages?
39
+ I'm leaning toward why? any page should be able to be made
40
+ by calling a custom mustache method within a given textfile.
41
+ Jekyll's relevant example is to create mass-pages per each category.
42
+ Generating extra pages can be mounted onto the compiler
43
+
44
+
45
+
46
+
47
+
48
+
49
+
data/dash.html ADDED
@@ -0,0 +1,225 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <style type="text/css">
5
+ /*!
6
+ * Bootstrap v2.0.2
7
+ *
8
+ * Copyright 2012 Twitter, Inc
9
+ * Licensed under the Apache License v2.0
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Designed and built with all the love in the world @twitter by @mdo and @fat.
13
+ */
14
+ .clearfix{*zoom:1;}.clearfix:before,.clearfix:after{display:table;content:"";}
15
+ .clearfix:after{clear:both;}
16
+ .hide-text{overflow:hidden;text-indent:100%;white-space:nowrap;}
17
+ .input-block-level{display:block;width:100%;min-height:28px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box;}
18
+ article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block;}
19
+ audio,canvas,video{display:inline-block;*display:inline;*zoom:1;}
20
+ audio:not([controls]){display:none;}
21
+ html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;}
22
+ a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px;}
23
+ a:hover,a:active{outline:0;}
24
+ sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline;}
25
+ sup{top:-0.5em;}
26
+ sub{bottom:-0.25em;}
27
+ img{height:auto;border:0;-ms-interpolation-mode:bicubic;vertical-align:middle;}
28
+ button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle;}
29
+ button,input{*overflow:visible;line-height:normal;}
30
+ button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0;}
31
+ button,input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button;}
32
+ input[type="search"]{-webkit-appearance:textfield;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;}
33
+ input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none;}
34
+ textarea{overflow:auto;vertical-align:top;}
35
+ body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;line-height:18px;color:#333333;background-color:#ffffff;}
36
+ a{color:#0088cc;text-decoration:none;}
37
+ a:hover{color:#005580;text-decoration:underline;}
38
+
39
+ /* deep purple: #2E2633 */
40
+ /* deep red #99173C */
41
+ body, html {
42
+ font-size:14px;
43
+ height:100%;
44
+ color: #2E2633;
45
+ }
46
+ a, a:hover {
47
+ color: #2E2633;
48
+ text-decoration:none;
49
+ }
50
+ ul {
51
+ list-style:none;
52
+ margin:0; padding:0;
53
+ }
54
+ #wrapper {
55
+ margin:auto;
56
+ width:500px;
57
+ min-height:100%;
58
+ position:relative;
59
+ border-left:2px solid #99173C;
60
+ }
61
+ .page-pane {
62
+ padding:20px 0;
63
+ width:500px;
64
+ }
65
+
66
+ /* ----------------------------------------- */
67
+ #nav-wrapper {
68
+ position:absolute;
69
+ top:20px;
70
+ left:-202px;
71
+ }
72
+ #nav {
73
+ position:fixed;
74
+ line-height:2em;
75
+ width:200px;
76
+ }
77
+ #nav li {margin-bottom:3px;}
78
+ #nav li a {
79
+ display:block;
80
+ padding:0 25px;
81
+ line-height:50px;
82
+ border-radius:5px 0 0 5px;
83
+ font-size:20px;
84
+ font-weight:bold;
85
+ font-family:verdana;
86
+ text-align:right;
87
+ }
88
+ #nav li a:hover {
89
+ background:#DCE9BE;
90
+ }
91
+ #nav li a.active {
92
+ background:#99173C;
93
+ color:#FFF;
94
+ }
95
+
96
+ /* ----------------------------------------- */
97
+ ul.page-list {
98
+ line-height:1.5em;
99
+ }
100
+ ul.page-list li {
101
+ margin-bottom:3px;
102
+ }
103
+ ul.page-list li a{
104
+ display:block;
105
+ overflow:hidden;
106
+ padding:10px 25px;
107
+ font-family:courier;
108
+ border-radius:0 5px 5px 0;
109
+ line-height:24px;
110
+ }
111
+ ul.page-list li a:hover {
112
+ background:#DCE9BE;
113
+ }
114
+ ul.page-list li a span.title{
115
+ font-size:18px;
116
+ font-weight:bold;
117
+ }
118
+ ul.page-list li a span.date{
119
+ position:absolute;
120
+ right:25px;
121
+ font-size:12px;
122
+ }
123
+ ul.page-list li a span.id {
124
+ font-size:12px;
125
+ }
126
+ </style>
127
+ </head>
128
+ <body>
129
+ <div id="wrapper">
130
+
131
+ <div id="nav-wrapper">
132
+ <ul id="nav">
133
+ <li><a href="#drafts" class="active">Drafts</a></li>
134
+ <li><a href="#public">Posts</a></li>
135
+ <li><a href="#pages">Pages</a></li>
136
+ </ul>
137
+ </div>
138
+
139
+ <div id="drafts" class="page-pane">
140
+ <ul class="page-list">
141
+ {{# db.posts.drafts?to_posts }}
142
+ <li>
143
+ <a href="{{url}}">
144
+ <span class="title">{{title}}</span>
145
+ <span class="date">{{date}}</span>
146
+ <br><span class="id">{{id}}</span></small>
147
+ </a>
148
+ </li>
149
+ {{/ db.posts.drafts?to_posts }}
150
+ </ul>
151
+ </div>
152
+
153
+ <div id="public" class="page-pane" style="display:none">
154
+ <ul class="page-list">
155
+ {{#posts}}
156
+ {{^ type }}
157
+ <li>
158
+ <a href="{{url}}">
159
+ <span class="title">{{title}}</span>
160
+ <span class="date">{{date}}</span>
161
+ <br><span class="id">{{id}}</span></small>
162
+ </a>
163
+ </li>
164
+ {{/ type }}
165
+ {{/posts}}
166
+ </ul>
167
+ </div>
168
+
169
+ <div id="pages" class="page-pane" style="display:none">
170
+ <ul class="page-list">
171
+ {{#pages}}
172
+ <li>
173
+ <a href="{{url}}">
174
+ <span class="title">{{title}}</span>
175
+ <span class="date">{{date}}</span>
176
+ <br><span class="id">{{id}}</span></small>
177
+ </a>
178
+ </li>
179
+ {{/pages}}
180
+ </ul>
181
+ </div>
182
+
183
+ </div>
184
+
185
+ <script>
186
+ // Tabs is a small script for showing/hiding the different page lists.
187
+ // This mostly likely only works in modern browsers but
188
+ // I'd rather not add jQuery as a dependency until it's really warranted.
189
+ var Tabs = {
190
+ panes : document.getElementsByClassName('page-pane'),
191
+ nav : document.getElementById('nav'),
192
+ listEntries : this.nav.children,
193
+ links : [],
194
+
195
+ init : function(){
196
+ for (var i = 0; i < Tabs.listEntries.length; ++i) {
197
+ Tabs.links.push(Tabs.listEntries[i].firstChild);
198
+ }
199
+
200
+ Tabs.nav.addEventListener('click', function(e){
201
+ e.preventDefault();
202
+ if (e.target.tagName.toLowerCase() !== 'a') return;
203
+
204
+ Tabs.clearActive();
205
+ Tabs.hidePanes();
206
+ e.target.className = 'active';
207
+ var id = e.target.href.split('#')[1];
208
+ document.getElementById(id).style.display = 'block';
209
+ }, false)
210
+ },
211
+
212
+ clearActive : function(){
213
+ for (var i = 0; i < Tabs.links.length; ++i)
214
+ Tabs.links[i].className = '';
215
+ },
216
+
217
+ hidePanes : function(){
218
+ for (var i = 0; i < Tabs.panes.length; ++i)
219
+ Tabs.panes[i].style.display = 'none';
220
+ }
221
+ }
222
+ Tabs.init();
223
+ </script>
224
+ </body>
225
+ </html>
data/history.txt CHANGED
@@ -1,3 +1,18 @@
1
+
2
+ 4.5.2012
3
+ 0.2.0 - API changes:
4
+ - [change] Dates in post filenames are now optional but will be required in metadata.
5
+ - [remove] _draft folder. Drafts are now simply a 'type' of post.
6
+ - [remove] publish and un-publish in favor of specifying 'type' meta attribute.
7
+ - [add] titleize method to client which renames draft filenames to their titles if set.
8
+ - [change] /_draft panel is now /dash with updated UI.
9
+ - [change] - rackup configuration is now through Ruhoh::Program.preview.
10
+ - FEATURES:
11
+ - Maintain file extensions for files that don't respond to a converter.
12
+ - Add 'development'/'production' environment configuration flag.
13
+ - @ben-biddington Introduces local temporary directory as SampleSitePath.
14
+ - @ben-biddington adds Travis integration.
15
+ 0.1.4 - BUG: Fix invalid byte sequence in US-ASCII by forcing UTF-8
1
16
  19.4.2012
2
17
  0.1.3 - BUG: Fix drafts not maintaining file extension when publishing
3
18
  BUG: tags helper should use tags database.
@@ -7,6 +7,7 @@ class Ruhoh
7
7
  BlogScaffold = 'git://github.com/ruhoh/blog.git'
8
8
 
9
9
  def initialize(data)
10
+ @iterator = 0
10
11
  self.setup_paths
11
12
  self.setup_options(data)
12
13
 
@@ -58,68 +59,24 @@ class Ruhoh
58
59
  # Public: Create a new draft file.
59
60
  # Requires no settings as it is meant to be fastest way to create content.
60
61
  def draft
61
- filename = File.join(Ruhoh.paths.drafts, "#{Time.now.to_i}.#{@options.ext}")
62
- if File.exist?(filename)
63
- sleep 1 ; self.draft(args) ; exit
64
- end
62
+ begin
63
+ filename = File.join(Ruhoh.paths.posts, "untitled-#{@iterator}.#{@options.ext}")
64
+ @iterator += 1
65
+ end while File.exist?(filename)
65
66
 
66
67
  FileUtils.mkdir_p File.dirname(filename)
67
- File.open(@paths.post_template) do |template|
68
- File.open(filename, 'w') do |post|
69
- post.puts template.read
70
- end
71
- end
72
-
73
- Ruhoh::Friend.say {
74
- green "New draft:"
75
- green Ruhoh.relative_path(filename)
76
- green 'View drafts at the URL: /_drafts'
77
- }
78
- end
79
-
80
- # Public: Publishes the last active draft file.
81
- def publish
82
- id = self.last('draft')
83
- Ruhoh::Friend.say { yellow "No draft to publish." ; exit } if id.nil?
84
- Ruhoh::Friend.say { plain "Publishing draft: #{id}" }
85
- draft = Ruhoh::Parsers::Posts.process_file(id)
86
-
87
- Ruhoh::Friend.say { red "Draft title cannot be blank." ; exit } unless draft['data']['title']
88
- Ruhoh::Friend.say {
89
- red "Invalid date format: #{draft['data']['date']}"
90
- red "Date format must be YYYY-MM-DD."
91
- exit
92
- } unless draft['data']['date']
93
-
94
- draft['data']['ext'] = File.extname(id).gsub('.','')
95
- filename = Ruhoh::Parsers::Posts.to_filename(draft['data'])
96
68
 
97
- if File.exist?(filename)
98
- abort("\e[31m Aborted! \e[0m") if ask("#{filename} already exists. Do you want to overwrite?", ['y', 'n']) == 'n'
99
- end
69
+ output = File.open(@paths.post_template) { |f| f.read }
70
+ output = output.gsub('{{DATE}}', Ruhoh::Parsers::Posts.formatted_date(Time.now))
71
+ File.open(filename, 'w') {|f| f.puts output }
100
72
 
101
- FileUtils.mkdir_p File.dirname(filename)
102
- FileUtils.mv id, filename
103
-
104
73
  Ruhoh::Friend.say {
105
- green "Published post:"
74
+ green "New draft:"
106
75
  green Ruhoh.relative_path(filename)
76
+ green 'View drafts at the URL: /dash'
107
77
  }
108
78
  end
109
-
110
- # Public: Unpublishes the last active post file.
111
- def unpublish
112
- post = self.last('post')
113
- Ruhoh::Friend.say { yellow "No post to unpublish." ; exit } if post.nil?
114
- Ruhoh::Friend.say { plain "Unpublishing post: #{post}" }
115
-
116
- FileUtils.mv post, File.join(Ruhoh.paths.drafts, File.basename(post))
117
-
118
- Ruhoh::Friend.say {
119
- yellow "Unpublished post:"
120
- yellow Ruhoh.relative_path(post)
121
- }
122
- end
79
+ alias_method :post, :draft
123
80
 
124
81
  # Public: Create a new page file.
125
82
  def page
@@ -149,6 +106,19 @@ class Ruhoh
149
106
  }
150
107
  end
151
108
 
109
+ # Public: Update draft filenames to their corresponding titles.
110
+ def titleize
111
+ Ruhoh::Parsers::Posts.files.each do |file|
112
+ next unless File.basename(file) =~ /^untitled/
113
+ parsed_page = Ruhoh::Utils.parse_file(file)
114
+ next unless parsed_page['data']['title']
115
+ new_name = Ruhoh::Parsers::Posts.to_slug(parsed_page['data']['title'])
116
+ new_file = File.join(File.dirname(file), "#{new_name}#{File.extname(file)}")
117
+ FileUtils.mv(file, new_file)
118
+ Ruhoh::Friend.say { green "Renamed #{file} to: #{new_file}" }
119
+ end
120
+ end
121
+
152
122
  # Public: Compile to static website.
153
123
  def compile
154
124
  Ruhoh::Compiler.new(@args[1]).compile
@@ -256,7 +226,7 @@ class Ruhoh
256
226
  # Return the payload hash for inspection/study.
257
227
  def payload
258
228
  require 'pp'
259
- Ruhoh::DB.update!
229
+ Ruhoh::DB.update_all
260
230
  Ruhoh::Friend.say {
261
231
  plain Ruhoh::Templaters::Base.build_payload.pretty_inspect
262
232
  }
@@ -264,9 +234,20 @@ class Ruhoh
264
234
 
265
235
  # Internal: Outputs a list of the given data-type to the terminal.
266
236
  def list(type)
267
- Ruhoh::DB.update(type)
268
- data = Ruhoh::DB.__send__(type)
269
- data = data['dictionary'] if type == :posts
237
+ data = case type
238
+ when :posts
239
+ Ruhoh::DB.update(:posts)
240
+ Ruhoh::DB.posts['dictionary']
241
+ when :drafts
242
+ Ruhoh::DB.update(:posts)
243
+ drafts = Ruhoh::DB.posts['drafts']
244
+ h = {}
245
+ drafts.each {|id| h[id] = Ruhoh::DB.posts['dictionary'][id]}
246
+ h
247
+ when :pages
248
+ Ruhoh::DB.update(:pages)
249
+ Ruhoh::DB.pages
250
+ end
270
251
 
271
252
  if @options.verbose
272
253
  Ruhoh::Friend.say {
@@ -298,8 +279,6 @@ class Ruhoh
298
279
  case type
299
280
  when 'post'
300
281
  Ruhoh::Parsers::Posts.files
301
- when 'draft'
302
- Ruhoh::Parsers::Drafts.files
303
282
  when 'page'
304
283
  Ruhoh::Parsers::Pages.files
305
284
  else
@@ -15,22 +15,18 @@ commands:
15
15
  "desc" : |
16
16
  Compile to static website.
17
17
  -
18
- "command" : "draft"
18
+ "command" : "post | draft"
19
19
  "desc" : |
20
20
  Create a new draft.
21
- -
22
- "command" : "publish"
23
- "desc" : |
24
- Publish the last active draft file.
25
- -
26
- "command" : "unpublish"
27
- "desc" : |
28
- UnPublish the last active post file.
29
21
  -
30
22
  "command" : "page <path>"
31
23
  "desc" : |
32
24
  Create a new page at the given path.
33
25
  -
26
+ "command" : "titleize"
27
+ "desc" : |
28
+ Update draft filenames to their corresponding titles. Drafts without titles are ignored.
29
+ -
34
30
  "command" : "drafts"
35
31
  "desc" : |
36
32
  List all drafts.
@@ -3,7 +3,10 @@ class Ruhoh
3
3
  class Compiler
4
4
 
5
5
  def initialize(target_directory)
6
- Ruhoh::DB.update!
6
+ Ruhoh.config.env ||= 'production'
7
+ Ruhoh::Friend.say { plain "Compiling for environment: '#{Ruhoh.config.env}'" }
8
+
9
+ Ruhoh::DB.update_all
7
10
  @target = target_directory || "./#{Ruhoh.folders.compiled}"
8
11
  @page = Ruhoh::Page.new
9
12
  end
@@ -21,11 +24,11 @@ class Ruhoh
21
24
 
22
25
  def pages
23
26
  FileUtils.cd(@target) {
24
- Ruhoh::DB.posts['dictionary'].merge(Ruhoh::DB.pages).each_value do |p|
27
+ Ruhoh::DB.all_pages.each_value do |p|
25
28
  @page.change(p['id'])
26
29
 
27
30
  FileUtils.mkdir_p File.dirname(@page.compiled_path)
28
- File.open(@page.compiled_path, 'w') { |p| p.puts @page.render }
31
+ File.open(@page.compiled_path, 'w:UTF-8') { |p| p.puts @page.render }
29
32
 
30
33
  Ruhoh::Friend.say { green "processed: #{p['id']}" }
31
34
  end
data/lib/ruhoh/db.rb CHANGED
@@ -1,12 +1,8 @@
1
- require "observer"
2
-
3
1
  class Ruhoh
4
-
5
2
  # Public: Database class for interacting with "data" in Ruhoh.
6
3
  class DB
7
4
  class << self
8
- include Observable
9
- WhiteList = [:site, :posts, :drafts, :pages, :routes, :layouts, :partials]
5
+ WhiteList = [:site, :posts, :pages, :routes, :layouts, :partials]
10
6
  self.__send__ :attr_reader, *WhiteList
11
7
 
12
8
  def update(name)
@@ -18,8 +14,6 @@ class Ruhoh
18
14
  Ruhoh::Parsers::Routes.generate
19
15
  when :posts
20
16
  Ruhoh::Parsers::Posts.generate
21
- when :drafts
22
- Ruhoh::Parsers::Drafts.generate
23
17
  when :pages
24
18
  Ruhoh::Parsers::Pages.generate
25
19
  when :layouts
@@ -30,18 +24,18 @@ class Ruhoh
30
24
  raise "Data type: '#{name}' is not a valid data type."
31
25
  end
32
26
  )
33
- changed
34
- notify_observers(name)
35
27
  end
36
-
37
- def update!
28
+
29
+ def all_pages
30
+ self.posts['dictionary'].merge(self.pages)
31
+ end
32
+
33
+ def update_all
38
34
  WhiteList.each do |var|
39
35
  self.__send__ :update, var
40
36
  end
41
37
  end
42
38
 
43
39
  end #self
44
-
45
40
  end #DB
46
-
47
41
  end #Ruhoh
data/lib/ruhoh/page.rb CHANGED
@@ -1,5 +1,4 @@
1
1
  class Ruhoh
2
-
3
2
  class Page
4
3
  attr_reader :id, :data, :content, :sub_layout, :master_layout
5
4
  attr_accessor :templater, :converter
@@ -11,12 +10,10 @@ class Ruhoh
11
10
 
12
11
  # Public: Change this page using an id.
13
12
  def change(id)
14
- @data = nil
13
+ self.reset
15
14
  @path = id
16
15
  @data = if id =~ Regexp.new("^#{Ruhoh.folders.posts}")
17
16
  Ruhoh::DB.posts['dictionary'][id]
18
- elsif id =~ Regexp.new("^#{Ruhoh.folders.drafts}")
19
- Ruhoh::DB.drafts[id]
20
17
  else
21
18
  @path = "#{Ruhoh.folders.pages}/#{id}"
22
19
  Ruhoh::DB.pages[id]
@@ -26,26 +23,15 @@ class Ruhoh
26
23
  @id = id
27
24
  end
28
25
 
29
- # Public: Change this page using a URL.
30
- def change_with_url(url)
31
- id = if url =~ Regexp.new("^/#{Ruhoh.folders.drafts}")
32
- url.gsub(/^\//,'')
33
- else
34
- Ruhoh::DB.routes[url]
35
- end
36
- raise "Page id not found for url: #{url}" unless id
37
- self.change(id)
38
- end
39
-
40
26
  def render
41
- raise "ID is null: Id must be set via page.change(id) or page.change_with_url(url)" if @id.nil?
27
+ self.ensure_id
42
28
  self.process_layouts
43
29
  self.process_content
44
30
  @templater.render(self)
45
31
  end
46
32
 
47
33
  def process_layouts
48
- raise "ID is null: Id must be set via page.change(id) or page.change_with_url(url)" if @id.nil?
34
+ self.ensure_id
49
35
  if @data['layout']
50
36
  @sub_layout = Ruhoh::DB.layouts[@data['layout']]
51
37
  raise "Layout does not exist: #{@data['layout']}" unless @sub_layout
@@ -61,7 +47,7 @@ class Ruhoh
61
47
  # in order to invoke converters on the result.
62
48
  # Converters (markdown) always choke on the templating language.
63
49
  def process_content
64
- raise "ID is null: Id must be set via page.change(id) or page.change_with_url(url)" if @id.nil?
50
+ self.ensure_id
65
51
  data = Ruhoh::Utils.parse_file(Ruhoh.paths.site_source, @path)
66
52
  raise "Invalid Frontmatter in page: #{@path}" if data.empty?
67
53
 
@@ -72,7 +58,7 @@ class Ruhoh
72
58
  # Public: Return page attributes suitable for inclusion in the
73
59
  # 'payload' of the given templater.
74
60
  def attributes
75
- raise "ID is null: Id must be set via page.change(id) or page.change_with_url(url)" if @id.nil?
61
+ self.ensure_id
76
62
  @data['content'] = @content
77
63
  @data
78
64
  end
@@ -81,13 +67,24 @@ class Ruhoh
81
67
  #
82
68
  # Returns: [String] The relative path to the compiled file for this page.
83
69
  def compiled_path
84
- raise "ID is null: Id must be set via page.change(id) or page.change_with_url(url)" if @id.nil?
70
+ self.ensure_id
85
71
  path = CGI.unescape(@data['url']).gsub(/^\//, '') #strip leading slash.
86
72
  path = "index.html" if path.empty?
87
- path += '/index.html' unless path =~ /\.html$/
73
+ path += '/index.html' unless path =~ /\.\w+$/
88
74
  path
89
75
  end
90
76
 
77
+ def reset
78
+ @id = nil
79
+ @data = nil
80
+ @content = nil
81
+ @sub_layout = nil
82
+ @master_layout = nil
83
+ end
84
+
85
+ def ensure_id
86
+ raise '@page ID is null: ID must be set via page.change(id) or page.change_with_url(url)' if @id.nil?
87
+ end
88
+
91
89
  end #Page
92
-
93
90
  end #Ruhoh
@@ -5,13 +5,18 @@ class Ruhoh
5
5
  # Generate layouts only from the active theme.
6
6
  def self.generate
7
7
  layouts = {}
8
-
8
+ invalid = []
9
9
  self.files.each do |filename|
10
10
  id = File.basename(filename, File.extname(filename))
11
11
  layouts[id] = Ruhoh::Utils.parse_file(Ruhoh.paths.layouts, filename)
12
- raise "Invalid Frontmatter in layout: #{filename}" if layouts[id].empty?
12
+
13
+ if layouts[id].empty?
14
+ error = "Invalid YAML Front Matter. Ensure this page has valid YAML, even if it's empty."
15
+ invalid << [filename, error]
16
+ end
13
17
  end
14
18
 
19
+ Ruhoh::Utils.report('Layouts', layouts, invalid)
15
20
  layouts
16
21
  end
17
22
 
@@ -25,6 +30,7 @@ class Ruhoh
25
30
  }
26
31
  end
27
32
 
33
+
28
34
  end #Layouts
29
35
  end #Parsers
30
36
  end #Ruhoh