class Zenweb::Page

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.

Attributes

binary[RW]

Is this file a binary file? Defaults to true if config passed to Page.new.

binary?[RW]

Is this file a binary file? Defaults to true if config passed to Page.new.

parent[RW]

The parent page of this page. Can be nil.

path[R]

The path to this source file.

site[R]

The shared site instance.

subpages[R]

The pages directly below this page. Can be empty.

Public Class Methods

renderers_re() click to toggle source

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

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

Public Instance Methods

[](k) click to toggle source

Helper method to access the config value named k.

# File lib/zenweb/page.rb, line 70
def [] k
  warn("#{self.url} does not define #{k.inspect}") unless config.key?(k)
  config[k]
end
all_subpages(reversed = false) click to toggle source

All pages below this page, possibly reversed, recursively.

# File lib/zenweb/page.rb, line 78
def all_subpages reversed = false
  dated, normal = subpages.partition(&:dated_path?)
  dated = dated.reverse if reversed

  (normal + dated).map { |p| [p, p.all_subpages(reversed)] }
end
all_subpages_by_level(reversed = false) click to toggle source

All pages below this page, possibly reversed, recursively, with the depth of each subpage relative to the current page.

# File lib/zenweb/page.rb, line 89
def all_subpages_by_level reversed = false
  self.all_subpages(reversed).deep_each.map { |n, p| [(n-1)/2, p] }
end
analytics() click to toggle source
# File lib/zenweb/plugins/google.rb, line 64
def analytics
  [google_analytics, gauges_analytics].compact.join "\n\n"
end
body() click to toggle source

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

# File lib/zenweb/page.rb, line 96
def body
  # TODO: add a test for something with --- without a yaml header.
  @body ||= begin
              thing = File.file?(path) ? path : self
              _, body = Zenweb::Config.split thing
              if self.binary? then
                body
              else
                body.strip
              end
            end
end
breadcrumbs() click to toggle source

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

change_frequency() click to toggle source
# File lib/zenweb/page.rb, line 195
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_url() click to toggle source

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

# File lib/zenweb/page.rb, line 134
def clean_url
  url.sub(/\/index.html$/, '/')
end
config() click to toggle source

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.

# File lib/zenweb/page.rb, line 143
def config
  unless defined? @config then
    @config = Config.new site, path
    @config = @config.parent unless content.start_with? "---"
  end
  @config
end
content() click to toggle source

Returns the entire (raw) content of the file.

# File lib/zenweb/page.rb, line 154
def content
  # TODO: this has the same drawbacks as Config.split
  @content ||= File.read path
end
date() click to toggle source

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.

# File lib/zenweb/page.rb, line 166
def date
  config['date'] || date_from_path || File.stat(path).mtime
end
date_str() click to toggle source
# File lib/zenweb/page.rb, line 176
def date_str
  fmt ||= self.config["date_fmt"] || "%Y-%m" # REFACTOR: yuck
  self.date.strftime fmt
end
dated?() click to toggle source

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

# File lib/zenweb/page.rb, line 184
def dated?
  config['date'] || date_from_path
end
dated_path?() click to toggle source

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

# File lib/zenweb/page.rb, line 191
def dated_path?
  path[/\d\d\d\d[-\/]\d\d[-\/]\d\d/] || path[/\d\d\d\d(?:[-\/]\d\d)?\/index/]
end
depends_on(deps) click to toggle source

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.

# File lib/zenweb/page.rb, line 222
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) click to toggle source

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

# 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) click to toggle source

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

# 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) click to toggle source

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.

# 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 = if RUBY_VERSION >= "2.6.0" then
             ERB.new(content, trim_mode:"-")
           else
             ERB.new(content, nil, "-")
           end
  end

  @erb.filename = source.inspect
  @erb.result binding
end
extend_md() click to toggle source
# File lib/zenweb/plugins/markdown.rb, line 26
def extend_md
  extend Zenweb::Page::MarkdownHelpers
end
filetype(name = self.path) click to toggle source

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

# File lib/zenweb/page.rb, line 237
def filetype name = self.path
  File.extname(name)[1..-1]
end
filetypes() click to toggle source

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.

# File lib/zenweb/page.rb, line 251
def filetypes
  @filetypes ||= path[self.class.renderers_re].split(/\./)[1..-1].reverse
rescue
  []
end
format_date(s) click to toggle source

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

# File lib/zenweb/page.rb, line 260
def format_date s
  fmt = self.config["date_fmt"] || "%Y/%m/%d"
  Time.local(*s.split(/-/).map(&:to_i)).strftime(fmt)
end
gauges_analytics() click to toggle source
# File lib/zenweb/plugins/google.rb, line 44
  def gauges_analytics
    if site.config["gauges_id"] then
      <<-"EOM".gsub(/^ {8}/, '')
        <script type="text/javascript">
          var _gauges = _gauges || [];
          (function() {
            var t   = document.createElement('script');
            t.type  = 'text/javascript';
            t.async = true;
            t.id    = 'gauges-tracker';
            t.setAttribute('data-site-id', '#{site.gauges_id}');
            t.src = '//secure.gaug.es/track.js';
            var s = document.getElementsByTagName('script')[0];
            s.parentNode.insertBefore(t, s);
          })();
        </script>
      EOM
    end
  end
generate() click to toggle source

Render and write the result to url_path.

# File lib/zenweb/page.rb, line 268
def generate
  warn "Rendering #{url_path}"

  content = self.render

  open url_path, "w" do |f|
    if binary? then
      f.print content
    else
      f.puts content
    end
  end
end
google_ad(slot, width = 468, height = 60) click to toggle source

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.

google_analytics() click to toggle source
# File lib/zenweb/plugins/google.rb, line 21
  def google_analytics
    if site.config["google_ua"] then
      <<-"EOM".gsub(/^ {8}/, '')
        <script type="text/javascript">
          var _gaq = _gaq || [];
          _gaq.push(['_setAccount', '#{site.google_ua}']);
          _gaq.push(['_trackPageview']);

          (function() {
          var ga = document.createElement('script');
          ga.type = 'text/javascript';
          ga.async = true;
          ga.src = ('https:' == document.location.protocol ?
                    'https://ssl' : 'http://www') +
                   '.google-analytics.com/ga.js';
          (document.getElementsByTagName('head')[0] ||
           document.getElementsByTagName('body')[0]).appendChild(ga);
          })();
        </script>
      EOM
    end
  end
html?() click to toggle source

Returns true if this is an html page.

# File lib/zenweb/page.rb, line 213
def html?
  path =~ /\.html/
end
include(name, page) click to toggle source

Render a named file from _includes. You must pass in the current page. This can make its configuration available accessing it via page.

# File lib/zenweb/page.rb, line 287
def include name, page
  incl = Page.new(site, File.join("_includes", name))
  incl.subrender page
end
index?() click to toggle source

Returns true if this page is an index page.

# File lib/zenweb/page.rb, line 295
def index?
  url.end_with? "index.html"
end
layout() click to toggle source

Return a layout Page named in the config key layout.

# File lib/zenweb/page.rb, line 308
def layout
  unless defined? @layout then
    @layout = site.layout self.config["layout"]
  end
  @layout
rescue => e
  raise e.exception "%s for page %p" % [e.message, path]
end
markdown(content, no_line_numbers = false) click to toggle source

Render markdown content.

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

# File lib/zenweb/plugins/markdown.rb, line 36
def markdown content, no_line_numbers = false
  require "kramdown"
  require "kramdown-parser-gfm"
  require "kramdown-syntax-coderay"
  require "coderay/zenweb_extensions"

  config = KRAMDOWN_CONFIG.dup
  if no_line_numbers then
    config[:syntax_highlighter_opts] = config[:syntax_highlighter_opts].dup
    config[:syntax_highlighter_opts][:line_numbers] = nil
  end

  Kramdown::Document.new(content, config).to_html
end
meta(key, name=key, label="name") click to toggle source

Stupid helper method to make declaring header meta lines cleaner

# File lib/zenweb/page.rb, line 327
def meta key, name=key, label="name"
  val = self.config[key]
  %(<meta #{label}="#{name}" content="#{val}">) if val
end
no_index?() click to toggle source
# File lib/zenweb/page.rb, line 357
def no_index?
  config["no_index"]
end
parent_url(url = self.url) click to toggle source

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

# File lib/zenweb/page.rb, line 112
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) click to toggle source

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

# File lib/zenweb/page.rb, line 365
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) click to toggle source

Render a page’s erb and return the result

# File lib/zenweb/plugins/erb.rb, line 5
def render_erb page, content
  erb body, self, binding
end
render_less(page, content) click to toggle source

Render less source to css.

# File lib/zenweb/plugins/less.rb, line 5
def render_less page, content
  require "less"

  Less::Parser.new.parse(content || body).to_css
end
render_md(page, content) click to toggle source

Render markdown page content using kramdown.

# File lib/zenweb/plugins/markdown.rb, line 21
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) click to toggle source

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

# File lib/zenweb/page.rb, line 399
    def run_js_script url
      <<-"EOM".gsub(/^ {6}/, '')
      <script type="text/javascript">
        (function() {
          var s   = document.createElement('script');
          s.type  = 'text/javascript';
          s.async = true;
          s.src   = '#{url}';
          (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(s);
        })();
      </script>
      EOM
    end
series_page() click to toggle source
# File lib/zenweb/page.rb, line 444
def series_page
  series = self.config[:series]
  Zenweb::SeriesPage.all[series] if series
end
stale?() click to toggle source
# File lib/zenweb/page.rb, line 374
def stale?
  file(url_path).needed?
end
stylesheet(name) click to toggle source

Stupid helper method to make declaring stylesheets cleaner

# File lib/zenweb/page.rb, line 381
def stylesheet name
  link_head rel:"stylesheet", type:"text/css", href:"/css/#{name}.css"
end
subrender(page = self, content = nil) click to toggle source

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

render_md(render_erb(content))
# File lib/zenweb/page.rb, line 391
def subrender page = self, content = nil
  self.filetypes.inject(content) { |cont, type|
    send "render_#{type}", page, cont
  } || self.body
end
tag_pages() click to toggle source
# File lib/zenweb/page.rb, line 440
def tag_pages
  (self.config[:tags] || []).map { |t| Zenweb::TagDetail.all[t] }.compact
end
url() click to toggle source

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

TODO: expand

# File lib/zenweb/page.rb, line 419
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_dir() click to toggle source

The directory portion of the url.

# File lib/zenweb/page.rb, line 429
def url_dir
  File.dirname url_path
end
url_path() click to toggle source

The real file path for the generated file.

# File lib/zenweb/page.rb, line 436
def url_path
  @url_path ||= File.join(".site", self.url)
end
wire() click to toggle source

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
# File lib/zenweb/page.rb, line 464
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