Class: Zenweb::Page

Inherits:
Object
  • Object
show all
Includes:
Rake::DSL
Defined in:
lib/zenweb/page.rb,
lib/zenweb/plugins/erb.rb,
lib/zenweb/plugins/less.rb,
lib/zenweb/plugins/disqus.rb,
lib/zenweb/plugins/google.rb,
lib/zenweb/plugins/markdown.rb

Overview

Page represents pretty much any type of file that goes on your website or is needed by other pages to build your website. Each page can have a YAML header that contains configuration data or variables used in the page.

Defined Under Namespace

Modules: MarkdownHelpers

Constant Summary collapse

KRAMDOWN_CONFIG =

:nodoc:

{ # :nodoc:
  :toc_levels    => '2..4',

  :entity_output => :symbolic,

  :coderay_wrap               => :div,
  :coderay_line_numbers       => :table,
  :coderay_tab_width          => 4,
  :coderay_css                => :class,
}

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(site, path, config = nil) ⇒ Page

:nodoc:



51
52
53
54
55
56
57
58
59
60
61
# File 'lib/zenweb/page.rb', line 51

def initialize site, path, config = nil # :nodoc:
  # TODO: make sure that creating page /a.html strips leading / from path
  @site, @path = site, path
  @config = config if config

  self.filetypes.each do |type|
    send "extend_#{type}" if self.respond_to? "extend_#{type}"
  end

  @subpages = []
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(msg, *args) ⇒ Object

Access a config variable and only warn if it isn’t accessible. If msg starts with render, go ahead and pass that up to the default method_missing.



311
312
313
314
315
316
317
318
# File 'lib/zenweb/page.rb', line 311

def method_missing msg, *args # :nodoc:
  case msg.to_s
  when /=|^render_|^to_a(?:ry)?$/ then # to_a/ry for 1.9 only. :(
    super
  else
    self[msg]
  end
end

Instance Attribute Details

#parentObject

The parent page of this page. Can be nil.



35
36
37
# File 'lib/zenweb/page.rb', line 35

def parent
  @parent
end

#pathObject (readonly)

The path to this source file.



25
26
27
# File 'lib/zenweb/page.rb', line 25

def path
  @path
end

#siteObject (readonly)

The shared site instance.



20
21
22
# File 'lib/zenweb/page.rb', line 20

def site
  @site
end

#subpagesObject (readonly)

The pages directly below this page. Can be empty.



30
31
32
# File 'lib/zenweb/page.rb', line 30

def subpages
  @subpages
end

Class Method Details

.renderers_reObject

Returns a regexp that will match file extensions for all known renderer types.



41
42
43
44
45
46
47
48
49
# File 'lib/zenweb/page.rb', line 41

def self.renderers_re
  @renderers_re ||=
    begin
      ext = instance_methods.grep(/^render_/).map { |s|
        s.to_s.sub(/render_/, '')
      }
      /(?:\.(#{ext.join "|"}))+$/
    end
end

Instance Method Details

#[](k) ⇒ Object

Helper method to access the config value named k.



66
67
68
# File 'lib/zenweb/page.rb', line 66

def [] k
  config[k] or warn("#{self.inspect} does not define #{k.inspect}")
end

#all_subpagesObject

All pages below this page, recursively.



73
74
75
# File 'lib/zenweb/page.rb', line 73

def all_subpages
  subpages.map { |p| [p, p.all_subpages] }
end

#analyticsObject



64
65
66
# File 'lib/zenweb/plugins/google.rb', line 64

def analytics
  [google_analytics, gauges_analytics].compact.join "\n\n"
end

#bodyObject

Returns the actual content of the file minus the optional YAML header.



80
81
82
83
84
85
86
# File 'lib/zenweb/page.rb', line 80

def body
  # TODO: add a test for something with --- without a yaml header.
  @body ||= begin
              _, body = Zenweb::Config.split path
              body.strip
            end
end

Returns an array of all parent pages of this page, including self.



99
100
101
102
103
104
105
106
107
108
# File 'lib/zenweb/page.rb', line 99

def breadcrumbs
  pages = [self]
  loop do
    parent = pages.first.parent
    break unless parent and parent != pages.first
    pages.unshift parent
  end
  pages.pop # take self back off
  pages
end

#change_frequencyObject



173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/zenweb/page.rb', line 173

def change_frequency
  days_old = (Time.now - self.date).to_i / 86400

  case days_old
  when 0...14 then
    "daily"
  when 14...56 then
    "weekly"
  when 56...365 then
    "monthly"
  else
    "yearly"
  end
end

#clean_urlObject

Return the url as users normally enter them (ie, no index.html).



113
114
115
# File 'lib/zenweb/page.rb', line 113

def clean_url
  url.sub(/\/index.html$/, '/')
end

#configObject

Returns the closest Config instance for this file. That could be the YAML prefix in the file or it could be a _config.yml file in the file’s directory or above.



122
123
124
125
126
127
128
# File 'lib/zenweb/page.rb', line 122

def config
  unless defined? @config then
    @config = Config.new site, path
    @config = @config.parent unless content.start_with? "---"
  end
  @config
end

#contentObject

Returns the entire (raw) content of the file.



133
134
135
136
# File 'lib/zenweb/page.rb', line 133

def content
  # TODO: this has the same drawbacks as Config.split
  @content ||= File.read path
end

#dateObject

Returns either:

+ The value of the date config value + The date embedded in the filename itself (eg: 2012-01-02-blah.html). + The last modified timestamp of the file itself.



145
146
147
# File 'lib/zenweb/page.rb', line 145

def date
  config['date'] || date_from_path || File.stat(path).mtime
end

#date_from_pathObject

:nodoc:



149
150
151
152
# File 'lib/zenweb/page.rb', line 149

def date_from_path # :nodoc:
  date = path[/\d\d\d\d-\d\d-\d\d/]
  Time.local(*date.split(/-/).map(&:to_i)) if date
end

#date_strObject



154
155
156
157
# File 'lib/zenweb/page.rb', line 154

def date_str
  fmt ||= self.config["date_fmt"] || "%Y-%m" # REFACTOR: yuck
  self.date.strftime fmt
end

#dated?Boolean

Returns true if this page has a date (via config or within the path).



162
163
164
# File 'lib/zenweb/page.rb', line 162

def dated?
  config['date'] || date_from_path
end

#dated_path?Boolean

Is this a dated page? (ie, does it have YYYY-MM-DD in the path?)



169
170
171
# File 'lib/zenweb/page.rb', line 169

def dated_path?
  path[/\d\d\d\d-\d\d-\d\d/]
end

#depends_on(deps) ⇒ Object

Wires up additional dependencies for this Page. from_deps may be a Hash (eg site.pages), an Array (eg. site.categories.blog), or a single page.



200
201
202
203
204
205
206
207
208
209
# File 'lib/zenweb/page.rb', line 200

def depends_on deps
  if String === deps then
    file self.path => deps
  else
    deps = deps.values if Hash === deps
    deps = Array(deps)

    file self.url_path => deps.map(&:url_path) - [self.url_path]
  end
end

#disqus(shortname) ⇒ Object

Returns a javascript blob to add a disqus comments block to the page.



6
7
8
9
# File 'lib/zenweb/plugins/disqus.rb', line 6

def disqus shortname
  '<div id="disqus_thread"></div>' +
    run_js_script("http://#{shortname}.disqus.com/embed.js")
end

#disqus_counts(shortname) ⇒ Object

Returns a javascript blob to convert properly formatted links to disqus comment counts.



15
16
17
# File 'lib/zenweb/plugins/disqus.rb', line 15

def disqus_counts shortname
  run_js_script "http://#{shortname}.disqus.com/count.js"
end

#erb(content, source, binding = TOPLEVEL_BINDING) ⇒ Object

Render erb in content for source with +binding.

Personally, I find erb’s delimiters a bit annoying, so for now, I’ve added additional gsub’s to the content to make it a bit more palatable.

{{ ... }} becomes <%= ... %>
{% ... %} becomes <%  ... %>

Unfortunately, those are the delimiters from liquid, so if someone goes and makes a liquid plugin it could clash. But why you’d have liquid and erb on the same file is beyond me… so it prolly won’t become a legitimate issue.



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# File 'lib/zenweb/plugins/erb.rb', line 24

def erb content, source, binding = TOPLEVEL_BINDING
  require 'erb'
  extend ERB::Util

  unless defined? @erb then
    content = content.
      gsub(/\{\{/, "<%=").
      gsub(/\}\}/, "%>").
      gsub(/\{%/,  "<%").
      gsub(/%\}/,  "%>").
      gsub(/\\([{}%])/, '\1')

    @erb = ERB.new(content, nil, "-")
  end

  @erb.filename = source.inspect
  @erb.result binding
end

#extend_mdObject



21
22
23
# File 'lib/zenweb/plugins/markdown.rb', line 21

def extend_md
  extend Zenweb::Page::MarkdownHelpers
end

#filetype(name = self.path) ⇒ Object

Returns the extension (without the ‘.’) of name, defaulting to self.path.



215
216
217
# File 'lib/zenweb/page.rb', line 215

def filetype name = self.path
  File.extname(name)[1..-1]
end

#filetypesObject

Returns an array of extensions (in reverse order) of this page that match known renderers. For example:

Given renderer methods render_erb and render_md, the file “index.html.md.erb” would return %w[erb md], but the file “index.html” would return [].

Additional renderers can be added via Site.load_plugins.



229
230
231
232
233
# File 'lib/zenweb/page.rb', line 229

def filetypes
  @filetypes ||= path[self.class.renderers_re].split(/\./)[1..-1].reverse
rescue
  []
end

#format_date(s) ⇒ Object

Format a date string s using the config value date_fmt or YYYY/MM/DD.



238
239
240
241
# File 'lib/zenweb/page.rb', line 238

def format_date s
  fmt = self.config["date_fmt"] || "%Y/%m/%d"
  Time.local(*s.split(/-/).map(&:to_i)).strftime(fmt)
end

#gauges_analyticsObject



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/zenweb/plugins/google.rb', line 44

def gauges_analytics
  if site.config["gauges_id"] then
    "      <script type=\"text/javascript\">\n        var _gauges = _gauges || [];\n        (function() {\n          var t   = document.createElement('script');\n          t.type  = 'text/javascript';\n          t.async = true;\n          t.id    = 'gauges-tracker';\n          t.setAttribute('data-site-id', '\#{site.gauges_id}');\n          t.src = '//secure.gaug.es/track.js';\n          var s = document.getElementsByTagName('script')[0];\n          s.parentNode.insertBefore(t, s);\n        })();\n      </script>\n    EOM\n  end\nend\n".gsub(/^ {8}/, '')

#generateObject

Render and write the result to #url_path.



246
247
248
249
250
251
252
253
254
# File 'lib/zenweb/page.rb', line 246

def generate
  warn "Rendering #{url_path}"

  content = self.render

  open url_path, "w" do |f|
    f.puts content
  end
end

Returns a javascript blob to add a google ad to the page. You need to provide the configuration param “google_ad_client” to your site config for this to work.



7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/zenweb/plugins/google.rb', line 7

def google_ad slot, width = 468, height = 60
  "    <script><!--\n    google_ad_client = \"\#{self[\"google_ad_client\"]}\";\n    google_ad_slot   = \"\#{slot}\";\n    google_ad_width  = \#{width};\n    google_ad_height = \#{height};\n    //-->\n    </script>\n    <script src=\"http://pagead2.googlesyndication.com/pagead/show_ads.js\">\n    </script>\n  EOM\nend\n".gsub(/^ {6}/, '')

#google_analyticsObject



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/zenweb/plugins/google.rb', line 21

def google_analytics
  if site.config["google_ua"] then
    "      <script type=\"text/javascript\">\n        var _gaq = _gaq || [];\n        _gaq.push(['_setAccount', '\#{site.google_ua}']);\n        _gaq.push(['_trackPageview']);\n\n        (function() {\n        var ga = document.createElement('script');\n        ga.type = 'text/javascript';\n        ga.async = true;\n        ga.src = ('https:' == document.location.protocol ?\n                  'https://ssl' : 'http://www') +\n                 '.google-analytics.com/ga.js';\n        (document.getElementsByTagName('head')[0] ||\n         document.getElementsByTagName('body')[0]).appendChild(ga);\n        })();\n      </script>\n    EOM\n  end\nend\n".gsub(/^ {8}/, '')

#html?Boolean

Returns true if this is an html page.



191
192
193
# File 'lib/zenweb/page.rb', line 191

def html?
  path =~ /\.html/
end

#include(name, page) ⇒ Object

Render a named file from _includes.

category: XXX



261
262
263
264
# File 'lib/zenweb/page.rb', line 261

def include name, page
  incl = Page.new(site, File.join("_includes", name))
  incl.subrender page
end

#index?Boolean

Returns true if this page is an index page.



269
270
271
# File 'lib/zenweb/page.rb', line 269

def index?
  url.end_with? "index.html"
end

#inspectObject Also known as: to_s

:nodoc:



273
274
275
# File 'lib/zenweb/page.rb', line 273

def inspect # :nodoc:
  "Page[#{path.inspect}]"
end

#layoutObject

Return a layout Page named in the config key layout.

TODO: expand



284
285
286
287
288
289
# File 'lib/zenweb/page.rb', line 284

def layout
  unless defined? @layout then
    @layout = site.layout self.config["layout"]
  end
  @layout
end

Convenience function to create an html link for this page.



294
295
296
# File 'lib/zenweb/page.rb', line 294

def link_html title = self.title
  %(<a href="#{clean_url}">#{title}</a>)
end

#markdown(content, no_line_numbers = false) ⇒ Object

Render markdown content.

I cheated and added some additional gsubs. I prefer ““‘ lang” so that works now.



31
32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/zenweb/plugins/markdown.rb', line 31

def markdown content, no_line_numbers = false
  require "kramdown"
  require "coderay/zenweb_extensions"

  content = content.
   gsub(/^\`\`\` *(\w+)/) { "~~~ #$1" }.
   gsub(/^\`\`\`/, '~~~')

  config = KRAMDOWN_CONFIG.dup
  config[:coderay_line_numbers] = nil if no_line_numbers

  Kramdown::Document.new(content, config).to_html
end

#meta(key, name = key, label = "name") ⇒ Object

Stupid helper method to make declaring header meta lines cleaner



301
302
303
304
# File 'lib/zenweb/page.rb', line 301

def meta key, name=key, label="name"
  val = self.config[key]
  %(<meta #{label}="#{name}" content="#{val}">) if val
end

#parent_url(url = self.url) ⇒ Object

Returns the parent url of a particular url (or self).



91
92
93
94
# File 'lib/zenweb/page.rb', line 91

def parent_url url = self.url
  url = File.dirname url if File.basename(url) == "index.html"
  File.join File.dirname(url), "index.html"
end

#render(page = self, content = nil) ⇒ Object

Render this page as a whole. This includes rendering the page’s content into a layout if one has been specified via config.



324
325
326
327
328
329
330
331
# File 'lib/zenweb/page.rb', line 324

def render page = self, content = nil
  content = subrender page, content

  layout  = self.layout # TODO: make nullpage to avoid 'if layout' tests
  content = layout.render page, content if layout

  content
end

#render_erb(page, content) ⇒ Object

Render a page’s erb and return the result



5
6
7
# File 'lib/zenweb/plugins/erb.rb', line 5

def render_erb page, content
  erb body, self, binding
end

#render_less(page, content) ⇒ Object

Render less source to css.



5
6
7
8
9
# File 'lib/zenweb/plugins/less.rb', line 5

def render_less page, content
  require "less"

  Less::Engine.new(content || body).to_css
end

#render_md(page, content) ⇒ Object

Render markdown page content using kramdown.



16
17
18
19
# File 'lib/zenweb/plugins/markdown.rb', line 16

def render_md page, content
  no_line_numbers = page.config["no_line_numbers"]
  markdown(content || self.body, no_line_numbers)
end

#run_js_script(url) ⇒ Object

TODO: move this and others to plugins/html_toys.rb (or something)



351
352
353
354
355
356
357
358
359
360
361
362
363
# File 'lib/zenweb/page.rb', line 351

def run_js_script url
  "  <script type=\"text/javascript\">\n    (function() {\n      var s   = document.createElement('script');\n      s.type  = 'text/javascript';\n      s.async = true;\n      s.src   = '\#{url}';\n      (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(s);\n    })();\n  </script>\n".gsub(/^ {6}/, '')
end

#stale?Boolean



333
334
335
# File 'lib/zenweb/page.rb', line 333

def stale?
  file(url_path).needed?
end

#subrender(page = self, content = nil) ⇒ Object

Render a Page instance based on its filetypes. For example, index.html.md.erb will essentially call:

render_md(render_erb(content))


343
344
345
346
347
# File 'lib/zenweb/page.rb', line 343

def subrender page = self, content = nil
  self.filetypes.inject(content) { |cont, type|
    send "render_#{type}", page, cont
  } || self.body
end

#urlObject

Return the url for this page. The url is based entirely on its location in the file-system.

TODO: expand



371
372
373
374
375
376
# File 'lib/zenweb/page.rb', line 371

def url
  @url ||= self.path.
    sub(/^/, '/').
    sub(/(\d\d\d\d)-(\d\d)-(\d\d)-/) { |s| "#{format_date s}/" }.
    gsub(self.class.renderers_re, '')
end

#url_dirObject

The directory portion of the url.



381
382
383
# File 'lib/zenweb/page.rb', line 381

def url_dir
  File.dirname url_path
end

#url_pathObject

The real file path for the generated file.



388
389
390
# File 'lib/zenweb/page.rb', line 388

def url_path
  @url_path ||= File.join(".site", self.url)
end

#wireObject

Wire up this page to the rest of the rake dependencies. If you have extra dependencies for this file (ie, an index page that links to many other pages) you can add them by creating a rake task named :extra_wirings and using #depends_on. Eg:

task :extra_wirings do |x|
  site = $website
  page = site.pages

  page["sitemap.xml.erb"].    depends_on site.html_pages
  page["atom.xml.erb"].       depends_on site.pages_by_date.first(30)
  page["blog/index.html.erb"].depends_on site.categories.blog
end


407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'lib/zenweb/page.rb', line 407

def wire
  @wired ||= false # HACK
  return if @wired
  @wired = true

  file self.path

  conf = self.config
  conf = conf.parent if self.path == conf.path

  file self.path => conf.path if conf.path
  conf.wire

  if self.layout then
    file self.path => self.layout.path
    self.layout.wire
  end

  file url_path => all_subpages.flatten.map(&:url_path) if url =~ /index.html/

  unless url_dir =~ %r%/_% then
    directory url_dir
    file url_path => url_dir
    file url_path => path do
      self.generate
    end

    task :site => url_path
  end
end