class Amber::StaticPage

Constants

FORBIDDEN_PAGE_CHARS_RE

INSTANCE METHODS

LOCALES_GLOB
LOCALES_RE

e.g. en, de, pt

LOCALE_FILE_MATCH_GLOB
LOCALE_FILE_MATCH_RE

e.g. en.haml or es.md or index.pt.text

PAGE_SUFFIXES_GLOB
PAGE_SUFFIXES_RE

e.g. haml, md, text

SIMPLE_FILE_MATCH_RE
SIMPLE_VAR_MATCH_RE
VAR_FILE_MATCH_GLOB
VAR_FILE_MATCH_RE
VAR_SUFFIXES_GLOB
VAR_SUFFIXES_RE

e.g. json, yaml

Attributes

children[RW]
config[RW]
file_path[RW]
name[RW]
parent[RW]
path[RW]
props[R]
site[RW]
valid[RW]
valid?[RW]

Public Class Methods

new(parent, name, file_path=nil, path_prefix="/") click to toggle source
# File lib/amber/static_page.rb, line 30
def initialize(parent, name, file_path=nil, path_prefix="/")
  @valid     = true
  @children  = PageArray.new  # array of StaticPages
  @nav_title = {} # key is locale
  @title     = {} # key is locale

  @name, @suffix = parse_source_file_name(name)

  # set @parent & @path
  if parent
    @parent = parent
    @config = @parent.config
    @parent.add_child(self)
    @path = [@parent.path, @name].flatten.compact
  else
    @path = (path_prefix||"").split('/')
  end

  if @name =~ FORBIDDEN_PAGE_CHARS_RE
    Amber.logger.error "Illegal page name #{@name} at path /#{self.path.join('/')} -- must not have symbols, uppercase, or periods."
    @valid = false
  end

  # set the @file_path
  if file_path
    @file_path = file_path
  elsif @parent && @parent.file_path
    @file_path = File.join(@parent.file_path, @name)
  else
    raise 'file path must be specified or in parent'
  end

  @simple_page = !File.directory?(@file_path)

  # eval the property headers, if any
  @props = load_properties()
end
scan_directory_tree(parent_page, absolute_dir_path, relative_dir_path) { |child_page, nil| ... } click to toggle source

Recursively decends the directory tree, yielding pages and directories it encounters.

yield has two arguments:

(1) StaticPage instance, or nil. (2) Directory path string if #1 is nil. Path is relative.

Directory paths are dirs in the tree that don't contain any pages.

# File lib/amber/static_page/filesystem.rb, line 26
def self.scan_directory_tree(parent_page, absolute_dir_path, relative_dir_path, &block)
  Dir.foreach(absolute_dir_path).each do |child_path|
    next if child_path =~ /^\./
    abs_path = File.join(absolute_dir_path, child_path)
    rel_path = File.join(relative_dir_path, child_path)
    if parent_page && is_directory_page?(abs_path)
      child_page = StaticPage.new(parent_page, child_path)
      if child_page.valid?
        yield child_page, nil
        scan_directory_tree(child_page, abs_path, rel_path, &block)
      end
    elsif parent_page && is_simple_page?(abs_path)
      child_page = StaticPage.new(parent_page, child_path)
      if child_page.valid?
        yield child_page, nil
      end
    elsif File.directory?(abs_path)
      yield nil, rel_path
      scan_directory_tree(nil, abs_path, rel_path, &block)
    end
  end
end

Private Class Methods

is_directory_page?(absolute_path) click to toggle source
# File lib/amber/static_page/filesystem.rb, line 141
def self.is_directory_page?(absolute_path)
  if File.directory?(absolute_path)
    Dir.glob(absolute_path + '/' + LOCALE_FILE_MATCH_GLOB).each do |file|
      return true
    end
  end
  return false
end
is_simple_page?(absolute_path) click to toggle source

returns true if the name of a file could be a 'simple' static page that is not a directory.

rules:

  • we include files that end in appriopriate suffixes

  • we exclude file names that are locales.

  • we exclude partials

# File lib/amber/static_page/filesystem.rb, line 136
def self.is_simple_page?(absolute_path)
  name = File.basename(absolute_path)
  name =~ /\.#{PAGE_SUFFIXES_RE}$/ && name !~ LOCALE_FILE_MATCH_RE && name !~ /^_/
end

Public Instance Methods

add_child(page) click to toggle source
# File lib/amber/static_page.rb, line 68
def add_child(page)
  @children << page
end
aliases(locale=I18n.default_locale) click to toggle source

returns an array of normalized aliases based on the :alias property defined for a page.

aliases are defined with a leading slash for absolute paths, or without a slash for relative paths. this method converts this to a format that amber uses (all absolute, with no leading slash, as an array instead of a string).

# File lib/amber/static_page.rb, line 144
def aliases(locale=I18n.default_locale)
  @aliases ||= begin
    aliases_hash = Hash.new([])
    @props.locales.each do |l|
      aliases = @props.prop_without_inheritance(l, :alias)
      aliases_hash[l] = begin
        if aliases.nil?
          []
        else
          [aliases].flatten.collect {|alias_path|
            if alias_path =~ /^\//
              alias_path.sub(/^\//, '').split('/')
            elsif @parent
              @parent.path + [alias_path]
            else
              alias_path.split('/')
            end
          }
        end
      end
    end
    aliases_hash
  end
  @aliases[locale]
end
all_children() click to toggle source
# File lib/amber/static_page.rb, line 72
def all_children
  PageArray.new(child_tree.flatten.compact)
end
child(name) click to toggle source

returns a child matching name, if any.

# File lib/amber/static_page.rb, line 107
def child(name)
  children.detect {|child| child.name == name}
end
content_file(locale) click to toggle source

full filesystem path name of the source content file e.g. /home/user/mysite/pages/about-us/contact/en.md

# File lib/amber/static_page/filesystem.rb, line 87
def content_file(locale)
  content_files[locale] || content_files[I18n.default_locale] || content_files.values.first
end
content_file_exists?(locale) click to toggle source
# File lib/amber/static_page/filesystem.rb, line 91
def content_file_exists?(locale)
  !!content_files[locale]
end
destination_file(dest_dir, locale) click to toggle source

full filesystem path name of the destination rendered file e.g. /home/user/mysite/public/about-us/contact/index.en.html

# File lib/amber/static_page/filesystem.rb, line 99
def destination_file(dest_dir, locale)
  File.join(dest_dir, *@path, "index.#{locale}.html")
end
explicit_title(locale) click to toggle source

returns title iff explicitly set.

# File lib/amber/static_page.rb, line 95
def explicit_title(locale)
  @props.prop_without_inheritance(locale, :title) ||
  @props.prop_without_inheritance(I18n.default_locale, :title)
end
id() click to toggle source
# File lib/amber/static_page.rb, line 100
def id
  self.name
end
inspect() click to toggle source
# File lib/amber/static_page.rb, line 76
def inspect
  "<'#{@path.join('/')}' #{children.inspect}>"
end
locales() click to toggle source

Returns array of locale symbols for all locales with properties set Note: there might be a content for a locale that does not show up in this array, if the content file does not set any properties.

# File lib/amber/static_page.rb, line 128
def locales
  @props.locales
end
nav_title(locale=I18n.locale) click to toggle source
path_str() click to toggle source
# File lib/amber/static_page.rb, line 132
def path_str
  self.path.join('/')
end
prop(*args) click to toggle source
# File lib/amber/static_page.rb, line 111
def prop(*args)
  @props.prop(*args)
end
render_to_file(dest_dir, options={}) click to toggle source

render a static copy

dest_dir - e.g. amber_root/public/

# File lib/amber/static_page/render.rb, line 34
def render_to_file(dest_dir, options={})
  render_content_files(dest_dir, options)
  render_assets(dest_dir)
  @props.locales.each do |locale|
    if aliases(locale).any?
      link_page_aliases(dest_dir, aliases(locale), locale)
    end
  end
end
scan_directory_tree(&block) click to toggle source
# File lib/amber/static_page/filesystem.rb, line 49
def scan_directory_tree(&block)
  StaticPage.scan_directory_tree(self, self.file_path, File.join(self.path), &block)
end
title(locale=I18n.locale) click to toggle source
# File lib/amber/static_page.rb, line 80
def title(locale=I18n.locale)
  @title[locale] ||= begin
    @props.prop_with_fallback(locale, [:title, :nav_title]) || @name
  end
end
var(name, locale=I18n.locale) click to toggle source
# File lib/amber/static_page.rb, line 119
def var(name, locale=I18n.locale)
  (vars[locale] || vars[I18n.default_locale] || {})[name.to_s]
end
vars() click to toggle source
# File lib/amber/static_page.rb, line 115
def vars
  @vars ||= load_variables
end

Protected Instance Methods

child_tree() click to toggle source
# File lib/amber/static_page.rb, line 172
def child_tree
  [self, children.collect{|child| child.child_tree}]
end

Private Instance Methods

asset_files() click to toggle source

returns an array of files in the folder that corresponds to this page that are not other pages. in other words, the assets in this folder

file paths are relative to @file_path

# File lib/amber/static_page/filesystem.rb, line 207
def asset_files
  if @simple_page
    []
  else
    Dir.foreach(@file_path).collect { |file|
      is_asset = \
        file &&
        file !~ /\.#{PAGE_SUFFIXES_RE}$/ &&
        file !~ /^#{VAR_FILE_MATCH_RE}$/ &&
        !File.directory?(File.join(@file_path, file))
      file if is_asset
    }.compact
  end
end
cleanup_properties(props, locale) click to toggle source
# File lib/amber/static_page/filesystem.rb, line 263
def cleanup_properties(props, locale)
  if props.prop(locale, :alias)
    props.set_prop(locale, :alias, [props.prop(locale, :alias)].flatten)
  end
end
content_files() click to toggle source

returns the files that compose the content for this page, a different file for each locale (or no locale)

returns a hash like so:

{
   :en => '/path/to/page/en.haml',
   :es => '/path/to/page/index.es.md'
}

Or this, if page is simple:

{

:en => '/path/to/page.haml'

}

# File lib/amber/static_page/filesystem.rb, line 181
def content_files
  @content_files ||= begin
    if @simple_page
      directory = File.dirname(@file_path)
      regexp = SIMPLE_FILE_MATCH_RE.call(@name)
    else
      directory = @file_path
      regexp = LOCALE_FILE_MATCH_RE
    end
    hsh = {}
    Dir.foreach(directory) do |file|
      if file && match = regexp.match(file)
        locale = match['locale'] || I18n.default_locale
        hsh[locale.to_sym] = File.join(directory, file)
      end
    end
    hsh
  end
end
load_properties() click to toggle source

scans the source content files for property headers in the form:

@variable = 'x'
- @variable = 'x'

(with or without leading hypen works)

This text is extracted and evaluated as ruby to set properties.

The first paragraph is loaded into the property “excerpt”.

# File lib/amber/static_page/filesystem.rb, line 242
def load_properties
  props = PageProperties.new(self)
  content_files.each do |locale, content_file|
    if type_from_path(content_file) == :haml
      props.eval(File.read(content_file, :encoding => 'UTF-8'), locale)
    else
      headers, excerpt = parse_headers(content_file)
      props.eval(headers, locale)
      if !excerpt.empty?
        props.set_prop(locale, "excerpt", excerpt)
      end
      props.set_prop(locale, "content_type", type_from_path(content_file))
    end
    cleanup_properties(props, locale)
  end
  unless props.prop_without_inheritance(I18n.default_locale, :name)
    props.set_prop(I18n.default_locale, :name, self.name)
  end
  return props
end
load_variables() click to toggle source
# File lib/amber/static_page/filesystem.rb, line 349
def load_variables
  vars = {}
  variable_files.each do |locale, var_file|
    begin
      if var_file =~ /\.ya?ml$/
        vars[locale] = YAML.load_file(var_file)
      elsif var_file =~ /\.json$/
        vars[locale] = JSON.parse(File.read(var_file))
      end
    rescue StandardError => exc
      Amber.logger.error('ERROR: could not load file #{var_file}: ' + exc.to_s)
    end
  end
  return vars
end
parse_headers(content_file) click to toggle source

parses a content_file's property headers and tries to extract the first paragraph.

# File lib/amber/static_page/filesystem.rb, line 273
def parse_headers(content_file)
  headers = []
  para1 = []
  para2 = []
  file_type = type_from_path(content_file)

  File.open(content_file, :encoding => 'UTF-8') do |f|
    while (line = f.gets) =~ /^(- |)@\w/
      if line !~ /^-/
        line = '- ' + line
      end
      headers << line
    end
    # eat empty lines
    while line = f.gets
      break unless line =~ /^\s*$/
    end
    # grab first two paragraphs
    para1 << line
    while line = f.gets
      break if line =~ /^\s*$/
      para1 << line
    end
    while line = f.gets
      break if line =~ /^\s*$/
      para2 << line
    end
  end

  headers = headers.join
  para1 = para1.join
  para2 = para2.join
  excerpt = ""

  # pick the first non-heading paragraph.
  # this is stupid, and chokes on nested headings.
  # but is also cheap and fast :)
  if file_type == :textile
    if para1 =~ /^h[1-5]\. /
      excerpt = para2
    else
      excerpt = para1
    end
  elsif file_type == :markdown
    if para1 =~ /^#+ / || para1 =~ /^(===+|---+)\s*$/m
      excerpt = para2
    else
      excerpt = para1
    end
  end
  return [headers, excerpt]
end
parse_source_file_name(name) click to toggle source

returns [name, suffix] called on new page initialization

# File lib/amber/static_page/filesystem.rb, line 154
def parse_source_file_name(name)
  matches = name.match(/^(?<name>.*?)(\.#{LOCALES_RE})?(\.#{PAGE_SUFFIXES_RE})$/)
  if matches
    [matches['name'], matches['suffix']]
  else
    [name, nil]
  end
end
realpath(pathname) click to toggle source
# File lib/amber/static_page/render.rb, line 109
def realpath(pathname)
  dir = pathname.dirname
  if dir.directory? || dir.symlink?
    dir.realpath + pathname.basename
  else
    pathname
  end
end
render_assets(dest_dir) click to toggle source

called only by render_to_file

# File lib/amber/static_page/render.rb, line 82
def render_assets(dest_dir)
  asset_files.each do |asset_file|
    src_file = File.join(@file_path, asset_file)
    dst_file = File.join(dest_dir, *@path, asset_file)
    Render::Asset.render(src_file, dst_file)
  end
end
render_content_files(dest_dir, options) click to toggle source

called only by render_to_file

# File lib/amber/static_page/render.rb, line 119
def render_content_files(dest_dir, options)
  view = Render::View.new(self, @config)
  @config.locales.each do |file_locale|
    content_file = content_file(file_locale)
    next unless content_file
    dest = destination_file(dest_dir, file_locale)
    unless Dir.exist?(File.dirname(dest))
      FileUtils.mkdir_p(File.dirname(dest))
    end
    if options[:force] || !File.exist?(dest) || File.mtime(content_file) > File.mtime(dest)
      File.open(dest, 'w') do |f|
        layout = @props.layout || 'default'
        f.write view.render({page: self, layout: layout}, {locale: file_locale})
      end
    end
  end
end
type_from_path(path) click to toggle source
# File lib/amber/static_page/filesystem.rb, line 365
def type_from_path(path)
  case File.extname(path)
  when ".text", ".textile"
    :textile
  when ".md", ".markdown"
    :markdown
  when ".haml"
    :haml
  when ".html"
    :html
  when ".erb"
    :erb
  else
    :unknown
  end
end
variable_files() click to toggle source

VARIABLES Variables are associated with a page, but unlike properties they are not inheritable. Variables are defined in a separate file.

# File lib/amber/static_page/filesystem.rb, line 331
def variable_files
  if @simple_page
    directory = File.dirname(@file_path)
    regexp = SIMPLE_VAR_MATCH_RE.call(@name)
  else
    directory = @file_path
    regexp = VAR_FILE_MATCH_RE
  end
  hsh = {}
  Dir.foreach(directory) do |file|
    if file && match = regexp.match(file)
      locale = match['locale'] || I18n.default_locale
      hsh[locale.to_sym] = File.join(directory, file)
    end
  end
  hsh
end