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
Is this file a binary file? Defaults to true if config passed to Page.new.
Is this file a binary file? Defaults to true if config passed to Page.new.
The parent page of this page. Can be nil.
The path to this source file.
The shared site instance.
The pages directly below this page. Can be empty.
Public Class Methods
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
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 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 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
# File lib/zenweb/plugins/google.rb, line 64 def analytics [google_analytics, gauges_analytics].compact.join "\n\n" end
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
# 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
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
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
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
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
# File lib/zenweb/page.rb, line 176 def date_str fmt ||= self.config["date_fmt"] || "%Y-%m" # REFACTOR: yuck self.date.strftime fmt end
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
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
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
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
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
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
# File lib/zenweb/plugins/markdown.rb, line 26 def extend_md extend Zenweb::Page::MarkdownHelpers end
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
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 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
# 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
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
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.
# File lib/zenweb/plugins/google.rb, line 7 def google_ad slot, width = 468, height = 60 <<-"EOM".gsub(/^ {6}/, '') <script><!-- google_ad_client = "#{self["google_ad_client"]}"; google_ad_slot = "#{slot}"; google_ad_width = #{width}; google_ad_height = #{height}; //--> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script> EOM end
# 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
Returns true if this is an html page.
# File lib/zenweb/page.rb, line 213 def html? path =~ /\.html/ end
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
Returns true if this page is an index page.
# File lib/zenweb/page.rb, line 295 def index? url.end_with? "index.html" end
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
Stupid helper method to make declaring header link lines cleaner
# File lib/zenweb/page.rb, line 335 def link_head **kws %(<link #{kws.map { |k,v| "#{k}=#{v.inspect}" }.join " "} />) end
Convenience function to create an html link for this page.
# File lib/zenweb/page.rb, line 320 def link_html title = self.title %(<a href="#{clean_url}">#{title}</a>) end
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
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
# File lib/zenweb/page.rb, line 357 def no_index? config["no_index"] end
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 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 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 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 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
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
# File lib/zenweb/page.rb, line 444 def series_page series = self.config[:series] Zenweb::SeriesPage.all[series] if series end
# File lib/zenweb/page.rb, line 374 def stale? file(url_path).needed? end
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
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
# File lib/zenweb/page.rb, line 440 def tag_pages (self.config[:tags] || []).map { |t| Zenweb::TagDetail.all[t] }.compact end
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
The directory portion of the url.
# File lib/zenweb/page.rb, line 429 def url_dir File.dirname url_path end
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 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