From 71ec0edea3f87fb69222dbb6fe025c2211402ca2 Mon Sep 17 00:00:00 2001 From: elijah Date: Tue, 12 Feb 2013 21:33:39 -0800 Subject: added capacity for pulling static pages from multiple directory source trees. --- lib/config.rb | 9 --- lib/menu.rb | 133 +++++++++++++++++++++++++++++------------ lib/site.rb | 93 +++++++++++++++++++++++++++++ lib/site_configuration.rb | 50 ++++++++++++++++ lib/site_mount_point.rb | 59 ++++++++++++++++++ lib/static_page.rb | 149 ++++++++++++++++++++++------------------------ lib/static_page_array.rb | 32 ++++++++++ 7 files changed, 401 insertions(+), 124 deletions(-) delete mode 100644 lib/config.rb create mode 100644 lib/site.rb create mode 100644 lib/site_configuration.rb create mode 100644 lib/site_mount_point.rb create mode 100644 lib/static_page_array.rb (limited to 'lib') diff --git a/lib/config.rb b/lib/config.rb deleted file mode 100644 index a579631..0000000 --- a/lib/config.rb +++ /dev/null @@ -1,9 +0,0 @@ -# -# Here go any application configuration -# - -SITE_TITLE = "LEAP" - -PAGE_DIRECTORY = File.expand_path('../../app/views/pages', __FILE__) - -PAGINATION_SIZE = 20 diff --git a/lib/menu.rb b/lib/menu.rb index 71690a4..c156e07 100644 --- a/lib/menu.rb +++ b/lib/menu.rb @@ -1,47 +1,45 @@ -class Menu < Array - attr_accessor :parent +# +# A navigation menu class +# - # - # class methods - # +class Menu + attr_accessor :parent + attr_accessor :children + attr_accessor :name # - # load the menu.txt file, recursively, and build the in-memory menu array + # load the menu.txt file and build the in-memory menu array # - def self.load(menu_file_path) - @menu = Menu.new + def load(menu_file_path) File.open(menu_file_path) do |file| - append_menu_item(@menu, file) + parse_menu(file) end end - def self.menu - @menu || Menu.new + def initialize(name, parent=nil) + self.name = name + self.parent = parent + self.children = [] end - def self.create(elements=[], parent=nil) - menu = Menu[elements] - menu.parent = parent - return menu - end + ## + ## public methods + ## # - # public methods + # returns the menu under the item that matches item_name. # - def submenu(item_name=nil) if item_name - submenu = detect {|item| item[0] == item_name} || Menu.new + self.children.detect {|child| child.name == item_name} || Menu.new else - submenu = self + self.children end - return submenu[1..-1] # strip of first element - end - - def name - first end + # + # returns path from root to this leaf as an array + # def path @path ||= begin if parent == nil @@ -52,27 +50,86 @@ class Menu < Array end end + def each(&block) + children.each(&block) + end + + def size + children.size + end + + # + # returns true if menu's path starts with +path_prefix+ + # + def path_starts_with?(path_prefix) + array_starts_with?(path, path_prefix) + end + + def path_prefix_of?(full_path) + array_starts_with?(full_path, path) + end + + # + # returns true if this menu item is the terminus menu item for path. + # (meaning that there are no children that match more path segments) + # + def leaf_for_path?(path) + return false unless path_prefix_of?(path) + next_path_segment = (path - self.path).first + return false if next_path_segment.nil? + return !children.detect {|i| i.name == next_path_segment} + end + + def inspect(indent=0) + lines = [] + lines << ' '*indent + '- ' + self.name + self.children.each do |child| + lines << child.inspect(indent+1) + end + lines.join("\n") + end + + # + # private & protected methods + # + + protected + + def add_child(name) + self.children << Menu.new(name, self) + end + private - def self.append_menu_item(menu, file) - begin + def array_starts_with?(big_array, small_array) + small_array.length.times do |i| + if small_array[i] != big_array[i] + return false + end + end + return true + end + + + def parse_menu(file) + while true item = file.readline - rescue EOFError - # do nothing - else - if item !~ /^\s*#/ + if item.strip.chars.any? && item !~ /^\s*#/ depth = item.scan(" ").size - sub_menu = sub_menu_for_depth(menu, depth) - sub_menu << Menu.create(item.strip, sub_menu) + last_menu_at_depth(depth).add_child(item.strip) end - append_menu_item(menu, file) end + rescue EOFError + # done loading end - def self.sub_menu_for_depth(menu, depth) - sub_menu = menu - depth.times { sub_menu = sub_menu[-1] } - sub_menu + # + # returns the last list of children at the specified depth + # + def last_menu_at_depth(depth) + menu = self + depth.times { menu = menu.children.last } + menu end end \ No newline at end of file diff --git a/lib/site.rb b/lib/site.rb new file mode 100644 index 0000000..a3e8ac3 --- /dev/null +++ b/lib/site.rb @@ -0,0 +1,93 @@ + +class Site + extend Forwardable + + attr_accessor :pages + attr_accessor :page_list + attr_accessor :root + attr_accessor :menu + + def_delegators :@config, :title, :pagination_size + + def initialize + @config = SiteConfiguration.load("#{Rails.root}/site.rb") + end + + def load_pages + @root = nil + @pages = {} + @page_list = StaticPageArray.new + @menu = Menu.new('root') + @config.mount_points.each do |mp| + add_mount_point(mp) + mp.reset_timestamp + end + end + + def reload_pages_if_needed + if @pages.nil? || @config.pages_changed? + puts "Reloading pages ................." + load_pages + end + end + + #def menu + # @menu ||= Menu.new + #end + + def find_pages(filter) + StaticPage.find(self, filter) + end + + def find_page(filter) + find_pages(filter) + end + + def all_pages + @page_list + end + + private + + def add_mount_point(mp) + # create base_page + base_page = begin + if mp.path == '/' + @root = StaticPage.new(nil, 'root', mp.directory) + else + name = File.basename(mp.path) + page = StaticPage.new(find_parent(mp.path), name, File.join(mp.directory, name)) + add_page(page) + page + end + end + base_page.mount_point = mp + + # load menu and locals + menu.load(mp.menu_file) if mp.menu_file + I18n.load_path += Dir[File.join(mp.locales_dir, '/*.{rb,yml,yaml}')] if mp.locales_dir + + # add the full directory tree + base_page.scan do |page| + add_page(page) + end + end + + def add_page(page) + @pages[page.name] = page + @pages[page.path.join('/')] = page + @page_list << page + end + + def find_parent(path) + so_far = [] + path.split('/').compact.each do |path_segment| + so_far << path_segment + if page = @pages[so_far.join('/')] + return page + end + end + return @root + end + +end diff --git a/lib/site_configuration.rb b/lib/site_configuration.rb new file mode 100644 index 0000000..62ad854 --- /dev/null +++ b/lib/site_configuration.rb @@ -0,0 +1,50 @@ +# +# A class for a site's configuration. +# Site configuration file is eval'ed in the context of an instance of SiteConfiguration +# + +require 'pathname' + +class SiteConfiguration + + attr_accessor :title + attr_accessor :pagination_size + attr_accessor :mount_points + attr_reader :file_path + + ## + ## CLASS METHODS + ## + + def self.load(config_file) + SiteConfiguration.new(config_file) + end + + ## + ## INSTANCE METHODS + ## + + # + # accepts a file_path to a configuration file. + # + def initialize(file_path) + @file_path = file_path + @site_title = "untitled" + @pagination_size = 20 + @mount_points = [] + self.eval + end + + def pages(directory_source, options={}) + @mount_points << SiteMountPoint.new(self, directory_source, options) + end + + def pages_changed? + @mount_points.detect {|mp| mp.changed?} + end + + def eval + self.instance_eval(File.read(@file_path), @file_path) + end + +end diff --git a/lib/site_mount_point.rb b/lib/site_mount_point.rb new file mode 100644 index 0000000..0b8224a --- /dev/null +++ b/lib/site_mount_point.rb @@ -0,0 +1,59 @@ +# +# A site can have many 'mount points' -- places in the site tree where different directories are inserted. +# +# At a minimum, every site needs a mount point for '/' +# +class SiteMountPoint + + attr_accessor :directory + attr_accessor :relative_directory + attr_accessor :path + attr_accessor :options + attr_accessor :menu_file + attr_accessor :locales_dir + attr_accessor :timestamp + + def initialize(site_config, directory_source, options={}) + if directory_source.starts_with?('/') + @directory = directory_source + else + @directory = File.expand_path(directory_source, File.dirname(site_config.file_path)) + end + + @path = options[:path] + @relative_directory = relative_dir_path(@directory) + @menu_file = file_path('menu.txt') + @locales_dir = file_path('locales') + @options = options + reset_timestamp + end + + def changed? + File.mtime(@directory) > @timestamp + end + + def reset_timestamp + @timestamp = File.mtime(@directory) + end + + private + + def file_path(file) + path = File.join(@directory, file) + if File.exists?(path) + path + else + nil + end + end + + # + # returns path relative to app/views + # + def relative_dir_path(directory) + if Rails.root + Pathname.new(directory).relative_path_from(Pathname.new(Rails.root + 'app/views')).to_s + end + end + +end \ No newline at end of file diff --git a/lib/static_page.rb b/lib/static_page.rb index 1c5336e..eb32319 100644 --- a/lib/static_page.rb +++ b/lib/static_page.rb @@ -1,72 +1,50 @@ +# +# class StaticPage +# +# represents a static website page. +# +# + require 'property_set' require 'i18n' require 'pathname' class StaticPage - - class PageArray < Array - def limit(num) - PageArray.new(self[0..(num-1)]) - end - def order_by(attr, options={}) - locale = options[:locale] || I18n.locale - direction = options[:direction] || :asc - array = sort do |a,b| - if direction == :desc - a, b = b, a - end - a_prop = a.props.locale(locale).send(attr) - b_prop = b.props.locale(locale).send(attr) - if a_prop.nil? && b_prop.nil? - 0 - elsif a_prop.nil? - 1 - elsif b_prop.nil? - -1 - else - a_prop <=> b_prop - end - end - array.delete_if do |page| - page.props.locale(locale).send(attr).nil? - end - return PageArray.new.replace array - end - end - - attr_accessor :path, :children, :name, :props, :parent + attr_accessor :path, :children, :name, :file_path, :props, :parent, :mount_point ## ## CLASS METHODS ## - def self.find(filter) + def self.find(site, filter) if filter =~ /\// path = filter.split('/').map{|segment| segment.gsub(/[^0-9a-z_-]/, '')} - page = @@pages[path.join('/')] + page = site.pages[path.join('/')] if page return page else - return @@pages[path.last] + return site.pages[path.last] end else - @@pages[filter] + site.pages[filter] end end - def self.all - @@pages_array - end - - def self.load(directory) - @@pages = {} - @@page_array = PageArray.new - @@root_directory = directory - @@relative_root_directory = relative_to_rails_view_root(directory) - scan_directory(directory) do |page| - @@pages[page.name] ||= page - @@pages[page.path.join('/')] = page - @@page_array << page + # + # loads a directory, creating StaticPages from the directory structure + # + def scan(&block) + Dir.chdir(file_path) do + Dir.glob("*").each do |child_name| + if File.directory?(child_name) + child = StaticPage.new(self, child_name) + yield child + child.scan(&block) + elsif is_simple_page?(child_name) + child = StaticPage.new(self, file_without_suffix(child_name)) + yield child + end + end end end @@ -74,17 +52,26 @@ class StaticPage ## INSTANCE METHODS ## - def initialize(parent, name) + def initialize(parent, name, file_path=nil) @children = [] @name = name if parent @parent = parent + @mount_point = @parent.mount_point @parent.add_child(self) @path = [@parent.path, @name].flatten.compact - @props = load_properties(file_path) else @path = [] end + 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) + @props = load_properties end def add_child(page) @@ -92,21 +79,25 @@ class StaticPage end def all_children - PageArray.new(child_tree.flatten.compact) + StaticPageArray.new(child_tree.flatten.compact) end # # e.g. /home/user/dev/leap-public-site/app/views/pages/about-us/contact # - def file_path - "#{@@root_directory}/#{@path.join('/')}" - end + #def file_path + # "#{@mount_point.directory}/#{@path.join('/')}" + #end # # e.g. pages/about-us/contact/en # def template_path(locale=I18n.locale) - "#{@@relative_root_directory}/#{@path.join('/')}/#{locale}" + if @simple_page + "#{@mount_point.relative_directory}/#{@path.join('/')}" + else + "#{@mount_point.relative_directory}/#{@path.join('/')}/#{locale}" + end end def inspect @@ -133,32 +124,19 @@ class StaticPage private - def self.scan_directory(directory, parent=nil, &block) - parent ||= StaticPage.new(nil, 'root') - Dir.chdir directory do - Dir.glob("*").each do |child_dir| - next unless File.directory?(child_dir) - page = StaticPage.new(parent, child_dir) - yield page - scan_directory(child_dir, page, &block) - end - end - end + #def self.relative_to_rails_view_root(absolute_path) + # if Rails.root + # absolute = Pathname.new(absolute_path) + # rails_view_root = Pathname.new(Rails.root + 'app/views') + # absolute.relative_path_from(rails_view_root).to_s + # end + #end - def self.relative_to_rails_view_root(absolute_path) - if Rails.root - absolute = Pathname.new(absolute_path) - rails_view_root = Pathname.new(Rails.root + 'app/views') - absolute.relative_path_from(rails_view_root).to_s - end - end - - def load_properties(file_path) + def load_properties props = PropertySet.new(self) Dir.glob(file_path + '/*.haml') do |content_file_path| locale = File.basename(content_file_path).sub(File.extname(content_file_path),'') #variable_header = "" - #p content_file_path #File.open(content_file_path) do |f| # while (line = f.gets) =~ /^- @/ # variable_header << line @@ -169,6 +147,23 @@ class StaticPage return props end + SUFFIXES = '(haml|md)' + + # + # returns true if the name of a file could be a 'simple' static page + # with only one translation. + # + # rules: + # * we include files that end in appriopriate suffixes + # * we exclude file names that are locales. + # + def is_simple_page?(name) + name =~ /\.#{SUFFIXES}$/ && name !~ /^(#{AVAILABLE_LANGUAGES.join('|')})\.#{SUFFIXES}$/ + end + + def file_without_suffix(name) + name.sub(/^(.*?)\.#{SUFFIXES}$/, "\\1") + end end diff --git a/lib/static_page_array.rb b/lib/static_page_array.rb new file mode 100644 index 0000000..33b11e0 --- /dev/null +++ b/lib/static_page_array.rb @@ -0,0 +1,32 @@ +# +# Array of StaticPages +# +class StaticPageArray < Array + def limit(num) + StaticPageArray.new(self[0..(num-1)]) + end + def order_by(attr, options={}) + locale = options[:locale] || I18n.locale + direction = options[:direction] || :asc + array = sort do |a,b| + if direction == :desc + a, b = b, a + end + a_prop = a.props.locale(locale).send(attr) + b_prop = b.props.locale(locale).send(attr) + if a_prop.nil? && b_prop.nil? + 0 + elsif a_prop.nil? + 1 + elsif b_prop.nil? + -1 + else + a_prop <=> b_prop + end + end + array.delete_if do |page| + page.props.locale(locale).send(attr).nil? + end + return StaticPageArray.new.replace array + end +end \ No newline at end of file -- cgit v1.2.3