nesta 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +13 -0
 - data/Gemfile +6 -0
 - data/Gemfile.lock +58 -0
 - data/LICENSE +19 -0
 - data/README.md +45 -0
 - data/Rakefile +12 -0
 - data/bin/nesta +67 -0
 - data/config.ru +9 -0
 - data/config/config.yml.sample +73 -0
 - data/config/deploy.rb.sample +62 -0
 - data/lib/nesta/app.rb +199 -0
 - data/lib/nesta/cache.rb +139 -0
 - data/lib/nesta/commands.rb +135 -0
 - data/lib/nesta/config.rb +87 -0
 - data/lib/nesta/models.rb +313 -0
 - data/lib/nesta/nesta.rb +0 -0
 - data/lib/nesta/overrides.rb +59 -0
 - data/lib/nesta/path.rb +11 -0
 - data/lib/nesta/plugins.rb +15 -0
 - data/lib/nesta/version.rb +3 -0
 - data/nesta.gemspec +49 -0
 - data/scripts/import-from-mephisto +207 -0
 - data/spec/atom_spec.rb +138 -0
 - data/spec/commands_spec.rb +220 -0
 - data/spec/config_spec.rb +69 -0
 - data/spec/model_factory.rb +94 -0
 - data/spec/models_spec.rb +445 -0
 - data/spec/overrides_spec.rb +113 -0
 - data/spec/page_spec.rb +428 -0
 - data/spec/path_spec.rb +28 -0
 - data/spec/sitemap_spec.rb +102 -0
 - data/spec/spec.opts +1 -0
 - data/spec/spec_helper.rb +72 -0
 - data/templates/Gemfile +8 -0
 - data/templates/Rakefile +35 -0
 - data/templates/config.ru +9 -0
 - data/templates/config/config.yml +73 -0
 - data/templates/config/deploy.rb +47 -0
 - data/views/analytics.haml +12 -0
 - data/views/atom.builder +28 -0
 - data/views/categories.haml +3 -0
 - data/views/comments.haml +8 -0
 - data/views/error.haml +13 -0
 - data/views/feed.haml +3 -0
 - data/views/index.haml +5 -0
 - data/views/layout.haml +27 -0
 - data/views/master.sass +246 -0
 - data/views/not_found.haml +13 -0
 - data/views/page.haml +29 -0
 - data/views/sidebar.haml +3 -0
 - data/views/sitemap.builder +15 -0
 - data/views/summaries.haml +14 -0
 - metadata +302 -0
 
    
        data/lib/nesta/cache.rb
    ADDED
    
    | 
         @@ -0,0 +1,139 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 2 
     | 
    
         
            +
            # require 'sinatra/base'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module Sinatra 
         
     | 
| 
      
 5 
     | 
    
         
            +
                
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Sinatra Caching module
         
     | 
| 
      
 7 
     | 
    
         
            +
              # 
         
     | 
| 
      
 8 
     | 
    
         
            +
              #  TODO:: Need to write documentation here 
         
     | 
| 
      
 9 
     | 
    
         
            +
              # 
         
     | 
| 
      
 10 
     | 
    
         
            +
              module Cache
         
     | 
| 
      
 11 
     | 
    
         
            +
                
         
     | 
| 
      
 12 
     | 
    
         
            +
                VERSION = 'Sinatra::Cache v0.2.0'
         
     | 
| 
      
 13 
     | 
    
         
            +
                def self.version; VERSION; end
         
     | 
| 
      
 14 
     | 
    
         
            +
                
         
     | 
| 
      
 15 
     | 
    
         
            +
                
         
     | 
| 
      
 16 
     | 
    
         
            +
                module Helpers 
         
     | 
| 
      
 17 
     | 
    
         
            +
                  
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # Caches the given URI to a html file in /public
         
     | 
| 
      
 19 
     | 
    
         
            +
                  # 
         
     | 
| 
      
 20 
     | 
    
         
            +
                  # <b>Usage:</b>
         
     | 
| 
      
 21 
     | 
    
         
            +
                  #    >> cache( erb(:contact, :layout => :layout))
         
     | 
| 
      
 22 
     | 
    
         
            +
                  #      =>  returns the HTML output written to /public/<CACHE_DIR_PATH>/contact.html
         
     | 
| 
      
 23 
     | 
    
         
            +
                  # 
         
     | 
| 
      
 24 
     | 
    
         
            +
                  # Also accepts an Options Hash, with the following options:
         
     | 
| 
      
 25 
     | 
    
         
            +
                  #  * :extension => in case you need to change the file extension
         
     | 
| 
      
 26 
     | 
    
         
            +
                  #  
         
     | 
| 
      
 27 
     | 
    
         
            +
                  #  TODO:: implement the opts={} hash functionality. What other options are needed?
         
     | 
| 
      
 28 
     | 
    
         
            +
                  # 
         
     | 
| 
      
 29 
     | 
    
         
            +
                  def cache(content, opts={})
         
     | 
| 
      
 30 
     | 
    
         
            +
                    return content unless options.cache_enabled
         
     | 
| 
      
 31 
     | 
    
         
            +
                    
         
     | 
| 
      
 32 
     | 
    
         
            +
                    unless content.nil?
         
     | 
| 
      
 33 
     | 
    
         
            +
                      content = "#{content}\n#{page_cached_timestamp}\n"
         
     | 
| 
      
 34 
     | 
    
         
            +
                      path = cache_page_path(request.path_info,opts)
         
     | 
| 
      
 35 
     | 
    
         
            +
                      FileUtils.makedirs(File.dirname(path))
         
     | 
| 
      
 36 
     | 
    
         
            +
                      open(path, 'wb+') { |f| f << content }
         
     | 
| 
      
 37 
     | 
    
         
            +
                      log("Cached Page: [#{path}]",:info) 
         
     | 
| 
      
 38 
     | 
    
         
            +
                      content
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
                  end
         
     | 
| 
      
 41 
     | 
    
         
            +
                  
         
     | 
| 
      
 42 
     | 
    
         
            +
                  # Expires the cached URI (as .html file) in /public
         
     | 
| 
      
 43 
     | 
    
         
            +
                  # 
         
     | 
| 
      
 44 
     | 
    
         
            +
                  # <b>Usage:</b>
         
     | 
| 
      
 45 
     | 
    
         
            +
                  #    >> cache_expire('/contact')
         
     | 
| 
      
 46 
     | 
    
         
            +
                  #      =>  deletes the /public/<CACHE_DIR_PATH>contact.html page
         
     | 
| 
      
 47 
     | 
    
         
            +
                  # 
         
     | 
| 
      
 48 
     | 
    
         
            +
                  #    get '/contact' do 
         
     | 
| 
      
 49 
     | 
    
         
            +
                  #     cache_expire   # deletes the /public/<CACHE_DIR_PATH>contact.html page as well
         
     | 
| 
      
 50 
     | 
    
         
            +
                  #    end
         
     | 
| 
      
 51 
     | 
    
         
            +
                  #
         
     | 
| 
      
 52 
     | 
    
         
            +
                  #  TODO:: implement the options={} hash functionality. What options are really needed ? 
         
     | 
| 
      
 53 
     | 
    
         
            +
                  def cache_expire(path = nil, opts={})
         
     | 
| 
      
 54 
     | 
    
         
            +
                    return unless options.cache_enabled
         
     | 
| 
      
 55 
     | 
    
         
            +
                    
         
     | 
| 
      
 56 
     | 
    
         
            +
                    path = (path.nil?) ? cache_page_path(request.path_info) : cache_page_path(path)
         
     | 
| 
      
 57 
     | 
    
         
            +
                    if File.exist?(path)
         
     | 
| 
      
 58 
     | 
    
         
            +
                      File.delete(path)
         
     | 
| 
      
 59 
     | 
    
         
            +
                      log("Expired Page deleted at: [#{path}]",:info)
         
     | 
| 
      
 60 
     | 
    
         
            +
                    else
         
     | 
| 
      
 61 
     | 
    
         
            +
                      log("No Expired Page was found at the path: [#{path}]",:info)
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
                  end
         
     | 
| 
      
 64 
     | 
    
         
            +
                  
         
     | 
| 
      
 65 
     | 
    
         
            +
                  # Prints a basic HTML comment with a timestamp in it, so that you can see when a file was cached last.
         
     | 
| 
      
 66 
     | 
    
         
            +
                  # 
         
     | 
| 
      
 67 
     | 
    
         
            +
                  # *NB!* IE6 does NOT like this to be the first line of a HTML document, so output
         
     | 
| 
      
 68 
     | 
    
         
            +
                  # inside the <head> tag. Many hours wasted on that lesson ;-)
         
     | 
| 
      
 69 
     | 
    
         
            +
                  # 
         
     | 
| 
      
 70 
     | 
    
         
            +
                  # <b>Usage:</b>
         
     | 
| 
      
 71 
     | 
    
         
            +
                  #    >> <%= page_cached_timestamp %>
         
     | 
| 
      
 72 
     | 
    
         
            +
                  #      => <!--  page cached: 2009-02-24 12:00:00 -->
         
     | 
| 
      
 73 
     | 
    
         
            +
                  #
         
     | 
| 
      
 74 
     | 
    
         
            +
                  def page_cached_timestamp
         
     | 
| 
      
 75 
     | 
    
         
            +
                    "<!-- page cached: #{Time.now.strftime("%Y-%d-%m %H:%M:%S")} -->\n" if options.cache_enabled
         
     | 
| 
      
 76 
     | 
    
         
            +
                  end
         
     | 
| 
      
 77 
     | 
    
         
            +
                  
         
     | 
| 
      
 78 
     | 
    
         
            +
                  
         
     | 
| 
      
 79 
     | 
    
         
            +
                  private
         
     | 
| 
      
 80 
     | 
    
         
            +
                    
         
     | 
| 
      
 81 
     | 
    
         
            +
                    # Establishes the file name of the cached file from the path given
         
     | 
| 
      
 82 
     | 
    
         
            +
                    # 
         
     | 
| 
      
 83 
     | 
    
         
            +
                    # TODO:: implement the opts={} functionality, and support for custom extensions on a per request basis. 
         
     | 
| 
      
 84 
     | 
    
         
            +
                    # 
         
     | 
| 
      
 85 
     | 
    
         
            +
                    def cache_file_name(path,opts={})
         
     | 
| 
      
 86 
     | 
    
         
            +
                      name = (path.empty? || path == "/") ? "index" : Rack::Utils.unescape(path.sub(/^(\/)/,'').chomp('/'))
         
     | 
| 
      
 87 
     | 
    
         
            +
                      name << options.cache_page_extension unless (name.split('/').last || name).include? '.'
         
     | 
| 
      
 88 
     | 
    
         
            +
                      return name
         
     | 
| 
      
 89 
     | 
    
         
            +
                    end
         
     | 
| 
      
 90 
     | 
    
         
            +
                    
         
     | 
| 
      
 91 
     | 
    
         
            +
                    # Sets the full path to the cached page/file
         
     | 
| 
      
 92 
     | 
    
         
            +
                    # Dependent upon Sinatra.options .public and .cache_dir variables being present and set.
         
     | 
| 
      
 93 
     | 
    
         
            +
                    # 
         
     | 
| 
      
 94 
     | 
    
         
            +
                    def cache_page_path(path,opts={})
         
     | 
| 
      
 95 
     | 
    
         
            +
                      # test if given a full path rather than relative path, otherwise join the public path to cache_dir 
         
     | 
| 
      
 96 
     | 
    
         
            +
                      # and ensure it is a full path
         
     | 
| 
      
 97 
     | 
    
         
            +
                      cache_dir = (options.cache_dir == File.expand_path(options.cache_dir)) ? 
         
     | 
| 
      
 98 
     | 
    
         
            +
                          options.cache_dir : File.expand_path("#{options.public}/#{options.cache_dir}")
         
     | 
| 
      
 99 
     | 
    
         
            +
                      cache_dir = cache_dir[0..-2] if cache_dir[-1,1] == '/'
         
     | 
| 
      
 100 
     | 
    
         
            +
                      "#{cache_dir}/#{cache_file_name(path,opts)}"
         
     | 
| 
      
 101 
     | 
    
         
            +
                    end
         
     | 
| 
      
 102 
     | 
    
         
            +
                    
         
     | 
| 
      
 103 
     | 
    
         
            +
                    #  TODO:: this implementation really stinks, how do I incorporate Sinatra's logger??
         
     | 
| 
      
 104 
     | 
    
         
            +
                    def log(msg,scope=:debug)
         
     | 
| 
      
 105 
     | 
    
         
            +
                      if options.cache_logging
         
     | 
| 
      
 106 
     | 
    
         
            +
                        "Log: msg=[#{msg}]" if scope == options.cache_logging_level
         
     | 
| 
      
 107 
     | 
    
         
            +
                      else
         
     | 
| 
      
 108 
     | 
    
         
            +
                        # just ignore the stuff...
         
     | 
| 
      
 109 
     | 
    
         
            +
                        # puts "just ignoring msg=[#{msg}] since cache_logging => [#{options.cache_logging.to_s}]"
         
     | 
| 
      
 110 
     | 
    
         
            +
                      end
         
     | 
| 
      
 111 
     | 
    
         
            +
                    end
         
     | 
| 
      
 112 
     | 
    
         
            +
                    
         
     | 
| 
      
 113 
     | 
    
         
            +
                end #/module Helpers
         
     | 
| 
      
 114 
     | 
    
         
            +
                
         
     | 
| 
      
 115 
     | 
    
         
            +
                
         
     | 
| 
      
 116 
     | 
    
         
            +
                # Sets the default options:
         
     | 
| 
      
 117 
     | 
    
         
            +
                # 
         
     | 
| 
      
 118 
     | 
    
         
            +
                #  * +:cache_enabled+ => toggle for the cache functionality. Default is: +true+
         
     | 
| 
      
 119 
     | 
    
         
            +
                #  * +:cache_page_extension+ => sets the default extension for cached files. Default is: +.html+
         
     | 
| 
      
 120 
     | 
    
         
            +
                #  * +:cache_dir+ => sets cache directory where cached files are stored. Default is: ''(empty) == root of /public.<br>
         
     | 
| 
      
 121 
     | 
    
         
            +
                #      set to empty, since the ideal 'system/cache/' does not work with Passenger & mod_rewrite :(
         
     | 
| 
      
 122 
     | 
    
         
            +
                #  * +cache_logging+ => toggle for logging the cache calls. Default is: +true+
         
     | 
| 
      
 123 
     | 
    
         
            +
                #  * +cache_logging_level+ => sets the level of the cache logger. Default is: <tt>:info</tt>.<br>
         
     | 
| 
      
 124 
     | 
    
         
            +
                #      Options:(unused atm) [:info, :warn, :debug]
         
     | 
| 
      
 125 
     | 
    
         
            +
                # 
         
     | 
| 
      
 126 
     | 
    
         
            +
                def self.registered(app)
         
     | 
| 
      
 127 
     | 
    
         
            +
                  app.helpers(Cache::Helpers)
         
     | 
| 
      
 128 
     | 
    
         
            +
                  app.set :cache_enabled, true
         
     | 
| 
      
 129 
     | 
    
         
            +
                  app.set :cache_page_extension, '.html'
         
     | 
| 
      
 130 
     | 
    
         
            +
                  app.set :cache_dir, ''
         
     | 
| 
      
 131 
     | 
    
         
            +
                  app.set :cache_logging, true
         
     | 
| 
      
 132 
     | 
    
         
            +
                  app.set :cache_logging_level, :info
         
     | 
| 
      
 133 
     | 
    
         
            +
                end
         
     | 
| 
      
 134 
     | 
    
         
            +
                
         
     | 
| 
      
 135 
     | 
    
         
            +
              end #/module Cache
         
     | 
| 
      
 136 
     | 
    
         
            +
              
         
     | 
| 
      
 137 
     | 
    
         
            +
              register(Sinatra::Cache)
         
     | 
| 
      
 138 
     | 
    
         
            +
              
         
     | 
| 
      
 139 
     | 
    
         
            +
            end #/module Sinatra
         
     | 
| 
         @@ -0,0 +1,135 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'erb'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'fileutils'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            require File.expand_path('app', File.dirname(__FILE__))
         
     | 
| 
      
 5 
     | 
    
         
            +
            require File.expand_path('path', File.dirname(__FILE__))
         
     | 
| 
      
 6 
     | 
    
         
            +
            require File.expand_path('version', File.dirname(__FILE__))
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            module Nesta
         
     | 
| 
      
 9 
     | 
    
         
            +
              module Commands
         
     | 
| 
      
 10 
     | 
    
         
            +
                class UsageError < RuntimeError; end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                module Command
         
     | 
| 
      
 13 
     | 
    
         
            +
                  def fail(message)
         
     | 
| 
      
 14 
     | 
    
         
            +
                    $stderr.puts "Error: #{message}"
         
     | 
| 
      
 15 
     | 
    
         
            +
                    exit 1
         
     | 
| 
      
 16 
     | 
    
         
            +
                  end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  def template_root
         
     | 
| 
      
 19 
     | 
    
         
            +
                    File.expand_path('../../templates', File.dirname(__FILE__))
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                  def copy_template(src, dest)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    FileUtils.mkdir_p(File.dirname(dest))
         
     | 
| 
      
 24 
     | 
    
         
            +
                    template = ERB.new(File.read(File.join(template_root, src)))
         
     | 
| 
      
 25 
     | 
    
         
            +
                    File.open(dest, 'w') { |file| file.puts template.result(binding) }
         
     | 
| 
      
 26 
     | 
    
         
            +
                  end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                  def copy_templates(templates)
         
     | 
| 
      
 29 
     | 
    
         
            +
                    templates.each { |src, dest| copy_template(src, dest) }
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                class New
         
     | 
| 
      
 34 
     | 
    
         
            +
                  include Command
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
                  def initialize(path, options = {})
         
     | 
| 
      
 37 
     | 
    
         
            +
                    path.nil? && (raise UsageError.new('path not specified'))
         
     | 
| 
      
 38 
     | 
    
         
            +
                    fail("#{path} already exists") if File.exist?(path)
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @path = path
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @options = options
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                  def make_directories
         
     | 
| 
      
 44 
     | 
    
         
            +
                    %w[content/attachments content/pages].each do |dir|
         
     | 
| 
      
 45 
     | 
    
         
            +
                      FileUtils.mkdir_p(File.join(@path, dir))
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                  def have_rake_tasks?
         
     | 
| 
      
 50 
     | 
    
         
            +
                    @options['heroku'] || @options['vlad']
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                  def execute
         
     | 
| 
      
 54 
     | 
    
         
            +
                    make_directories
         
     | 
| 
      
 55 
     | 
    
         
            +
                    templates = {
         
     | 
| 
      
 56 
     | 
    
         
            +
                      'config.ru' => "#{@path}/config.ru",
         
     | 
| 
      
 57 
     | 
    
         
            +
                      'config/config.yml' => "#{@path}/config/config.yml",
         
     | 
| 
      
 58 
     | 
    
         
            +
                      'Gemfile' => "#{@path}/Gemfile"
         
     | 
| 
      
 59 
     | 
    
         
            +
                    }
         
     | 
| 
      
 60 
     | 
    
         
            +
                    templates['Rakefile'] = "#{@path}/Rakefile" if have_rake_tasks?
         
     | 
| 
      
 61 
     | 
    
         
            +
                    if @options['vlad']
         
     | 
| 
      
 62 
     | 
    
         
            +
                      templates['config/deploy.rb'] = "#{@path}/config/deploy.rb"
         
     | 
| 
      
 63 
     | 
    
         
            +
                    end
         
     | 
| 
      
 64 
     | 
    
         
            +
                    copy_templates(templates)
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                module Theme
         
     | 
| 
      
 69 
     | 
    
         
            +
                  class Create
         
     | 
| 
      
 70 
     | 
    
         
            +
                    include Command
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                    def initialize(name, options = {})
         
     | 
| 
      
 73 
     | 
    
         
            +
                      name.nil? && (raise UsageError.new('name not specified'))
         
     | 
| 
      
 74 
     | 
    
         
            +
                      @name = name
         
     | 
| 
      
 75 
     | 
    
         
            +
                      @theme_path = Nesta::Path.themes(@name)
         
     | 
| 
      
 76 
     | 
    
         
            +
                      fail("#{@theme_path} already exists") if File.exist?(@theme_path)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                    def make_directories
         
     | 
| 
      
 80 
     | 
    
         
            +
                      FileUtils.mkdir_p(File.join(@theme_path, 'public', @name))
         
     | 
| 
      
 81 
     | 
    
         
            +
                      FileUtils.mkdir_p(File.join(@theme_path, 'views'))
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                    def execute
         
     | 
| 
      
 85 
     | 
    
         
            +
                      make_directories
         
     | 
| 
      
 86 
     | 
    
         
            +
                      copy_templates(
         
     | 
| 
      
 87 
     | 
    
         
            +
                        'themes/README.md' => "#{@theme_path}/README.md",
         
     | 
| 
      
 88 
     | 
    
         
            +
                        'themes/app.rb' => "#{@theme_path}/app.rb"
         
     | 
| 
      
 89 
     | 
    
         
            +
                      )
         
     | 
| 
      
 90 
     | 
    
         
            +
                    end
         
     | 
| 
      
 91 
     | 
    
         
            +
                  end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
                  class Install
         
     | 
| 
      
 94 
     | 
    
         
            +
                    include Command
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                    def initialize(url, options = {})
         
     | 
| 
      
 97 
     | 
    
         
            +
                      url.nil? && (raise UsageError.new('URL not specified'))
         
     | 
| 
      
 98 
     | 
    
         
            +
                      @url = url
         
     | 
| 
      
 99 
     | 
    
         
            +
                      @name = File.basename(url, '.git').sub(/nesta-theme-/, '')
         
     | 
| 
      
 100 
     | 
    
         
            +
                    end
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                    def execute
         
     | 
| 
      
 103 
     | 
    
         
            +
                      system('git', 'clone', @url, "themes/#{@name}")
         
     | 
| 
      
 104 
     | 
    
         
            +
                      FileUtils.rm_r(File.join("themes/#{@name}", '.git'))
         
     | 
| 
      
 105 
     | 
    
         
            +
                      enable(@name)
         
     | 
| 
      
 106 
     | 
    
         
            +
                    end
         
     | 
| 
      
 107 
     | 
    
         
            +
                  end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                  class Enable
         
     | 
| 
      
 110 
     | 
    
         
            +
                    include Command
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                    def initialize(name, options = {})
         
     | 
| 
      
 113 
     | 
    
         
            +
                      name.nil? && (raise UsageError.new('name not specified'))
         
     | 
| 
      
 114 
     | 
    
         
            +
                      @name = name
         
     | 
| 
      
 115 
     | 
    
         
            +
                    end
         
     | 
| 
      
 116 
     | 
    
         
            +
             
     | 
| 
      
 117 
     | 
    
         
            +
                    def execute
         
     | 
| 
      
 118 
     | 
    
         
            +
                      theme_config = /^\s*#?\s*theme:.*/
         
     | 
| 
      
 119 
     | 
    
         
            +
                      configured = false
         
     | 
| 
      
 120 
     | 
    
         
            +
                      File.open(Nesta::Config.yaml_path, 'r+') do |file|
         
     | 
| 
      
 121 
     | 
    
         
            +
                        output = ''
         
     | 
| 
      
 122 
     | 
    
         
            +
                        file.each_line do |line|
         
     | 
| 
      
 123 
     | 
    
         
            +
                          output << line.sub(theme_config, "theme: #{@name}")
         
     | 
| 
      
 124 
     | 
    
         
            +
                          configured = true if line =~ theme_config
         
     | 
| 
      
 125 
     | 
    
         
            +
                        end
         
     | 
| 
      
 126 
     | 
    
         
            +
                        output << "theme: #{@name}\n" unless configured
         
     | 
| 
      
 127 
     | 
    
         
            +
                        file.pos = 0
         
     | 
| 
      
 128 
     | 
    
         
            +
                        file.print(output)
         
     | 
| 
      
 129 
     | 
    
         
            +
                        file.truncate(file.pos)
         
     | 
| 
      
 130 
     | 
    
         
            +
                      end
         
     | 
| 
      
 131 
     | 
    
         
            +
                    end
         
     | 
| 
      
 132 
     | 
    
         
            +
                  end
         
     | 
| 
      
 133 
     | 
    
         
            +
                end
         
     | 
| 
      
 134 
     | 
    
         
            +
              end
         
     | 
| 
      
 135 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/nesta/config.rb
    ADDED
    
    | 
         @@ -0,0 +1,87 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "yaml"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "rubygems"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "sinatra"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            module Nesta
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Config
         
     | 
| 
      
 8 
     | 
    
         
            +
                @settings = %w[
         
     | 
| 
      
 9 
     | 
    
         
            +
                  title subtitle description keywords theme disqus_short_name
         
     | 
| 
      
 10 
     | 
    
         
            +
                  cache content google_analytics_code
         
     | 
| 
      
 11 
     | 
    
         
            +
                ]
         
     | 
| 
      
 12 
     | 
    
         
            +
                @author_settings = %w[name uri email]
         
     | 
| 
      
 13 
     | 
    
         
            +
                @yaml = nil
         
     | 
| 
      
 14 
     | 
    
         
            +
                
         
     | 
| 
      
 15 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 16 
     | 
    
         
            +
                  attr_accessor :settings, :author_settings, :yaml_conf
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
                
         
     | 
| 
      
 19 
     | 
    
         
            +
                def self.method_missing(method, *args)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  setting = method.to_s
         
     | 
| 
      
 21 
     | 
    
         
            +
                  if settings.include?(setting)
         
     | 
| 
      
 22 
     | 
    
         
            +
                    from_environment(setting) || from_yaml(setting)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  else
         
     | 
| 
      
 24 
     | 
    
         
            +
                    super
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
                
         
     | 
| 
      
 28 
     | 
    
         
            +
                def self.author
         
     | 
| 
      
 29 
     | 
    
         
            +
                  environment_config = {}
         
     | 
| 
      
 30 
     | 
    
         
            +
                  %w[name uri email].each do |setting|
         
     | 
| 
      
 31 
     | 
    
         
            +
                    variable = "NESTA_AUTHOR__#{setting.upcase}"
         
     | 
| 
      
 32 
     | 
    
         
            +
                    ENV[variable] && environment_config[setting] = ENV[variable]
         
     | 
| 
      
 33 
     | 
    
         
            +
                  end
         
     | 
| 
      
 34 
     | 
    
         
            +
                  environment_config.empty? ? from_yaml("author") : environment_config
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
                
         
     | 
| 
      
 37 
     | 
    
         
            +
                def self.content_path(basename = nil)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  get_path(content, basename)
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
                
         
     | 
| 
      
 41 
     | 
    
         
            +
                def self.page_path(basename = nil)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  get_path(File.join(content_path, "pages"), basename)
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
                
         
     | 
| 
      
 45 
     | 
    
         
            +
                def self.attachment_path(basename = nil)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  get_path(File.join(content_path, "attachments"), basename)
         
     | 
| 
      
 47 
     | 
    
         
            +
                end
         
     | 
| 
      
 48 
     | 
    
         
            +
                
         
     | 
| 
      
 49 
     | 
    
         
            +
                def self.yaml_path
         
     | 
| 
      
 50 
     | 
    
         
            +
                  File.expand_path('config/config.yml', Nesta::App.root)
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
                
         
     | 
| 
      
 53 
     | 
    
         
            +
                def self.from_environment(setting)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  value = ENV["NESTA_#{setting.upcase}"]
         
     | 
| 
      
 55 
     | 
    
         
            +
                  overrides = { "true" => true, "false" => false }
         
     | 
| 
      
 56 
     | 
    
         
            +
                  overrides.has_key?(value) ? overrides[value] : value
         
     | 
| 
      
 57 
     | 
    
         
            +
                end
         
     | 
| 
      
 58 
     | 
    
         
            +
                private_class_method :from_environment
         
     | 
| 
      
 59 
     | 
    
         
            +
                
         
     | 
| 
      
 60 
     | 
    
         
            +
                def self.yaml_exists?
         
     | 
| 
      
 61 
     | 
    
         
            +
                  File.exist?(yaml_path)
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
                private_class_method :yaml_exists?
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                def self.can_use_yaml?
         
     | 
| 
      
 66 
     | 
    
         
            +
                  ENV.keys.grep(/^NESTA/).empty? && yaml_exists?
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
                private_class_method :can_use_yaml?
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
                def self.from_yaml(setting)
         
     | 
| 
      
 71 
     | 
    
         
            +
                  if can_use_yaml?
         
     | 
| 
      
 72 
     | 
    
         
            +
                    self.yaml_conf ||= YAML::load(IO.read(yaml_path))
         
     | 
| 
      
 73 
     | 
    
         
            +
                    rack_env_conf = self.yaml_conf[Nesta::App.environment.to_s]
         
     | 
| 
      
 74 
     | 
    
         
            +
                    (rack_env_conf && rack_env_conf[setting]) || self.yaml_conf[setting]
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                rescue Errno::ENOENT  # config file not found
         
     | 
| 
      
 77 
     | 
    
         
            +
                  raise unless Nesta::App.environment == :test
         
     | 
| 
      
 78 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 79 
     | 
    
         
            +
                end
         
     | 
| 
      
 80 
     | 
    
         
            +
                private_class_method :from_yaml
         
     | 
| 
      
 81 
     | 
    
         
            +
                
         
     | 
| 
      
 82 
     | 
    
         
            +
                def self.get_path(dirname, basename)
         
     | 
| 
      
 83 
     | 
    
         
            +
                  basename.nil? ? dirname : File.join(dirname, basename)
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
                private_class_method :get_path
         
     | 
| 
      
 86 
     | 
    
         
            +
              end
         
     | 
| 
      
 87 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/nesta/models.rb
    ADDED
    
    | 
         @@ -0,0 +1,313 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "time"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "rubygems"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "maruku"
         
     | 
| 
      
 5 
     | 
    
         
            +
            require "redcloth"
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module Nesta
         
     | 
| 
      
 8 
     | 
    
         
            +
              class FileModel
         
     | 
| 
      
 9 
     | 
    
         
            +
                FORMATS = [:mdown, :haml, :textile]
         
     | 
| 
      
 10 
     | 
    
         
            +
                @@cache = {}
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                attr_reader :filename, :mtime
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                def self.model_path(basename = nil)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  Nesta::Config.content_path(basename)
         
     | 
| 
      
 16 
     | 
    
         
            +
                end
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                def self.find_all
         
     | 
| 
      
 19 
     | 
    
         
            +
                  file_pattern = File.join(model_path, "**", "*.{#{FORMATS.join(',')}}")
         
     | 
| 
      
 20 
     | 
    
         
            +
                  Dir.glob(file_pattern).map do |path|
         
     | 
| 
      
 21 
     | 
    
         
            +
                    relative = path.sub("#{model_path}/", "")
         
     | 
| 
      
 22 
     | 
    
         
            +
                    load(relative.sub(/\.(#{FORMATS.join('|')})/, ""))
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                def self.needs_loading?(path, filename)
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @@cache[path].nil? || File.mtime(filename) > @@cache[path].mtime
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                def self.load(path)
         
     | 
| 
      
 31 
     | 
    
         
            +
                  FORMATS.each do |format|
         
     | 
| 
      
 32 
     | 
    
         
            +
                    filename = model_path("#{path}.#{format}")
         
     | 
| 
      
 33 
     | 
    
         
            +
                    if File.exist?(filename) && needs_loading?(path, filename)
         
     | 
| 
      
 34 
     | 
    
         
            +
                      @@cache[path] = self.new(filename)
         
     | 
| 
      
 35 
     | 
    
         
            +
                      break
         
     | 
| 
      
 36 
     | 
    
         
            +
                    end
         
     | 
| 
      
 37 
     | 
    
         
            +
                  end
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @@cache[path]
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def self.purge_cache
         
     | 
| 
      
 42 
     | 
    
         
            +
                  @@cache = {}
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def self.deprecated(name, message)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  if Nesta::App.environment != :test
         
     | 
| 
      
 47 
     | 
    
         
            +
                    $stderr.puts "DEPRECATION WARNING: #{name} is deprecated; #{message}"
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def self.menu_items
         
     | 
| 
      
 52 
     | 
    
         
            +
                  deprecated("Page.menu_items", "see Menu.top_level and Menu.for_path")
         
     | 
| 
      
 53 
     | 
    
         
            +
                  Menu.top_level
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                def initialize(filename)
         
     | 
| 
      
 57 
     | 
    
         
            +
                  @filename = filename
         
     | 
| 
      
 58 
     | 
    
         
            +
                  @format = filename.split(".").last.to_sym
         
     | 
| 
      
 59 
     | 
    
         
            +
                  parse_file
         
     | 
| 
      
 60 
     | 
    
         
            +
                  @mtime = File.mtime(filename)
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
                def permalink
         
     | 
| 
      
 64 
     | 
    
         
            +
                  File.basename(@filename, ".*")
         
     | 
| 
      
 65 
     | 
    
         
            +
                end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
                def path
         
     | 
| 
      
 68 
     | 
    
         
            +
                  abspath.sub(/^\//, "")
         
     | 
| 
      
 69 
     | 
    
         
            +
                end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
                def abspath
         
     | 
| 
      
 72 
     | 
    
         
            +
                  prefix = File.dirname(@filename).sub(Nesta::Config.page_path, "")
         
     | 
| 
      
 73 
     | 
    
         
            +
                  File.join(prefix, permalink)
         
     | 
| 
      
 74 
     | 
    
         
            +
                end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
                def layout
         
     | 
| 
      
 77 
     | 
    
         
            +
                  (metadata("layout") || "layout").to_sym
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def template
         
     | 
| 
      
 81 
     | 
    
         
            +
                  (metadata("template") || "page").to_sym
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                def to_html(scope = nil)
         
     | 
| 
      
 85 
     | 
    
         
            +
                  case @format
         
     | 
| 
      
 86 
     | 
    
         
            +
                  when :mdown
         
     | 
| 
      
 87 
     | 
    
         
            +
                    Maruku.new(markup).to_html
         
     | 
| 
      
 88 
     | 
    
         
            +
                  when :haml
         
     | 
| 
      
 89 
     | 
    
         
            +
                    Haml::Engine.new(markup).to_html(scope || Object.new)
         
     | 
| 
      
 90 
     | 
    
         
            +
                  when :textile
         
     | 
| 
      
 91 
     | 
    
         
            +
                    RedCloth.new(markup).to_html
         
     | 
| 
      
 92 
     | 
    
         
            +
                  end
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                def last_modified
         
     | 
| 
      
 96 
     | 
    
         
            +
                  @last_modified ||= File.stat(@filename).mtime
         
     | 
| 
      
 97 
     | 
    
         
            +
                end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
      
 99 
     | 
    
         
            +
                def description
         
     | 
| 
      
 100 
     | 
    
         
            +
                  metadata("description")
         
     | 
| 
      
 101 
     | 
    
         
            +
                end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
                def keywords
         
     | 
| 
      
 104 
     | 
    
         
            +
                  metadata("keywords")
         
     | 
| 
      
 105 
     | 
    
         
            +
                end
         
     | 
| 
      
 106 
     | 
    
         
            +
             
     | 
| 
      
 107 
     | 
    
         
            +
                private
         
     | 
| 
      
 108 
     | 
    
         
            +
                def markup
         
     | 
| 
      
 109 
     | 
    
         
            +
                  @markup
         
     | 
| 
      
 110 
     | 
    
         
            +
                end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                def metadata(key)
         
     | 
| 
      
 113 
     | 
    
         
            +
                  @metadata[key]
         
     | 
| 
      
 114 
     | 
    
         
            +
                end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
                def paragraph_is_metadata(text)
         
     | 
| 
      
 117 
     | 
    
         
            +
                  text.split("\n").first =~ /^[\w ]+:/
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                def parse_file
         
     | 
| 
      
 121 
     | 
    
         
            +
                  first_para, remaining = File.open(@filename).read.split(/\r?\n\r?\n/, 2)
         
     | 
| 
      
 122 
     | 
    
         
            +
                  @metadata = {}
         
     | 
| 
      
 123 
     | 
    
         
            +
                  if paragraph_is_metadata(first_para)
         
     | 
| 
      
 124 
     | 
    
         
            +
                    @markup = remaining
         
     | 
| 
      
 125 
     | 
    
         
            +
                    for line in first_para.split("\n") do
         
     | 
| 
      
 126 
     | 
    
         
            +
                      key, value = line.split(/\s*:\s*/, 2)
         
     | 
| 
      
 127 
     | 
    
         
            +
                      @metadata[key.downcase] = value.chomp
         
     | 
| 
      
 128 
     | 
    
         
            +
                    end
         
     | 
| 
      
 129 
     | 
    
         
            +
                  else
         
     | 
| 
      
 130 
     | 
    
         
            +
                    @markup = [first_para, remaining].join("\n\n")
         
     | 
| 
      
 131 
     | 
    
         
            +
                  end
         
     | 
| 
      
 132 
     | 
    
         
            +
                rescue Errno::ENOENT  # file not found
         
     | 
| 
      
 133 
     | 
    
         
            +
                  raise Sinatra::NotFound
         
     | 
| 
      
 134 
     | 
    
         
            +
                end
         
     | 
| 
      
 135 
     | 
    
         
            +
              end
         
     | 
| 
      
 136 
     | 
    
         
            +
             
     | 
| 
      
 137 
     | 
    
         
            +
              class Page < FileModel
         
     | 
| 
      
 138 
     | 
    
         
            +
                def self.model_path(basename = nil)
         
     | 
| 
      
 139 
     | 
    
         
            +
                  Nesta::Config.page_path(basename)
         
     | 
| 
      
 140 
     | 
    
         
            +
                end
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                def self.find_by_path(path)
         
     | 
| 
      
 143 
     | 
    
         
            +
                  load(path)
         
     | 
| 
      
 144 
     | 
    
         
            +
                end
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                def self.find_articles
         
     | 
| 
      
 147 
     | 
    
         
            +
                  find_all.select do |page|
         
     | 
| 
      
 148 
     | 
    
         
            +
                    page.date && page.date < DateTime.now
         
     | 
| 
      
 149 
     | 
    
         
            +
                  end.sort { |x, y| y.date <=> x.date }
         
     | 
| 
      
 150 
     | 
    
         
            +
                end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                def ==(other)
         
     | 
| 
      
 153 
     | 
    
         
            +
                  other.respond_to?(:path) && (self.path == other.path)
         
     | 
| 
      
 154 
     | 
    
         
            +
                end
         
     | 
| 
      
 155 
     | 
    
         
            +
             
     | 
| 
      
 156 
     | 
    
         
            +
                def heading
         
     | 
| 
      
 157 
     | 
    
         
            +
                  regex = case @format
         
     | 
| 
      
 158 
     | 
    
         
            +
                          when :mdown
         
     | 
| 
      
 159 
     | 
    
         
            +
                            /^#\s*(.*)/
         
     | 
| 
      
 160 
     | 
    
         
            +
                          when :haml
         
     | 
| 
      
 161 
     | 
    
         
            +
                            /^\s*%h1\s+(.*)/
         
     | 
| 
      
 162 
     | 
    
         
            +
                          when :textile
         
     | 
| 
      
 163 
     | 
    
         
            +
                            /^\s*h1\.\s+(.*)/
         
     | 
| 
      
 164 
     | 
    
         
            +
                          end
         
     | 
| 
      
 165 
     | 
    
         
            +
                  markup =~ regex
         
     | 
| 
      
 166 
     | 
    
         
            +
                  Regexp.last_match(1)
         
     | 
| 
      
 167 
     | 
    
         
            +
                end
         
     | 
| 
      
 168 
     | 
    
         
            +
             
     | 
| 
      
 169 
     | 
    
         
            +
                def date(format = nil)
         
     | 
| 
      
 170 
     | 
    
         
            +
                  @date ||= if metadata("date")
         
     | 
| 
      
 171 
     | 
    
         
            +
                              if format == :xmlschema
         
     | 
| 
      
 172 
     | 
    
         
            +
                                Time.parse(metadata("date")).xmlschema
         
     | 
| 
      
 173 
     | 
    
         
            +
                              else
         
     | 
| 
      
 174 
     | 
    
         
            +
                                DateTime.parse(metadata("date"))
         
     | 
| 
      
 175 
     | 
    
         
            +
                              end
         
     | 
| 
      
 176 
     | 
    
         
            +
                            end
         
     | 
| 
      
 177 
     | 
    
         
            +
                end
         
     | 
| 
      
 178 
     | 
    
         
            +
             
     | 
| 
      
 179 
     | 
    
         
            +
                def atom_id
         
     | 
| 
      
 180 
     | 
    
         
            +
                  metadata("atom id")
         
     | 
| 
      
 181 
     | 
    
         
            +
                end
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                def read_more
         
     | 
| 
      
 184 
     | 
    
         
            +
                  metadata("read more") || "Continue reading"
         
     | 
| 
      
 185 
     | 
    
         
            +
                end
         
     | 
| 
      
 186 
     | 
    
         
            +
             
     | 
| 
      
 187 
     | 
    
         
            +
                def summary
         
     | 
| 
      
 188 
     | 
    
         
            +
                  if summary_text = metadata("summary")
         
     | 
| 
      
 189 
     | 
    
         
            +
                    summary_text.gsub!('\n', "\n")
         
     | 
| 
      
 190 
     | 
    
         
            +
                    case @format
         
     | 
| 
      
 191 
     | 
    
         
            +
                    when :textile
         
     | 
| 
      
 192 
     | 
    
         
            +
                      RedCloth.new(summary_text).to_html
         
     | 
| 
      
 193 
     | 
    
         
            +
                    else
         
     | 
| 
      
 194 
     | 
    
         
            +
                      Maruku.new(summary_text).to_html
         
     | 
| 
      
 195 
     | 
    
         
            +
                    end
         
     | 
| 
      
 196 
     | 
    
         
            +
                  end
         
     | 
| 
      
 197 
     | 
    
         
            +
                end
         
     | 
| 
      
 198 
     | 
    
         
            +
             
     | 
| 
      
 199 
     | 
    
         
            +
                def body
         
     | 
| 
      
 200 
     | 
    
         
            +
                  case @format
         
     | 
| 
      
 201 
     | 
    
         
            +
                  when :mdown
         
     | 
| 
      
 202 
     | 
    
         
            +
                    body_text = markup.sub(/^#[^#].*$\r?\n(\r?\n)?/, "")
         
     | 
| 
      
 203 
     | 
    
         
            +
                    Maruku.new(body_text).to_html
         
     | 
| 
      
 204 
     | 
    
         
            +
                  when :haml
         
     | 
| 
      
 205 
     | 
    
         
            +
                    body_text = markup.sub(/^\s*%h1\s+.*$\r?\n(\r?\n)?/, "")
         
     | 
| 
      
 206 
     | 
    
         
            +
                    Haml::Engine.new(body_text).render
         
     | 
| 
      
 207 
     | 
    
         
            +
                  when :textile
         
     | 
| 
      
 208 
     | 
    
         
            +
                    body_text = markup.sub(/^\s*h1\.\s+.*$\r?\n(\r?\n)?/, "")
         
     | 
| 
      
 209 
     | 
    
         
            +
                    RedCloth.new(body_text).to_html
         
     | 
| 
      
 210 
     | 
    
         
            +
                  end
         
     | 
| 
      
 211 
     | 
    
         
            +
                end
         
     | 
| 
      
 212 
     | 
    
         
            +
             
     | 
| 
      
 213 
     | 
    
         
            +
                def categories
         
     | 
| 
      
 214 
     | 
    
         
            +
                  categories = metadata("categories")
         
     | 
| 
      
 215 
     | 
    
         
            +
                  paths = categories.nil? ? [] : categories.split(",").map { |p| p.strip }
         
     | 
| 
      
 216 
     | 
    
         
            +
                  valid_paths(paths).map { |p| Page.find_by_path(p) }.sort do |x, y|
         
     | 
| 
      
 217 
     | 
    
         
            +
                    x.heading.downcase <=> y.heading.downcase
         
     | 
| 
      
 218 
     | 
    
         
            +
                  end
         
     | 
| 
      
 219 
     | 
    
         
            +
                end
         
     | 
| 
      
 220 
     | 
    
         
            +
             
     | 
| 
      
 221 
     | 
    
         
            +
                def parent
         
     | 
| 
      
 222 
     | 
    
         
            +
                  Page.load(File.dirname(path))
         
     | 
| 
      
 223 
     | 
    
         
            +
                end
         
     | 
| 
      
 224 
     | 
    
         
            +
             
     | 
| 
      
 225 
     | 
    
         
            +
                def pages
         
     | 
| 
      
 226 
     | 
    
         
            +
                  Page.find_all.select do |page|
         
     | 
| 
      
 227 
     | 
    
         
            +
                    page.date.nil? && page.categories.include?(self)
         
     | 
| 
      
 228 
     | 
    
         
            +
                  end.sort do |x, y|
         
     | 
| 
      
 229 
     | 
    
         
            +
                    x.heading.downcase <=> y.heading.downcase
         
     | 
| 
      
 230 
     | 
    
         
            +
                  end
         
     | 
| 
      
 231 
     | 
    
         
            +
                end
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
                def articles
         
     | 
| 
      
 234 
     | 
    
         
            +
                  Page.find_articles.select { |article| article.categories.include?(self) }
         
     | 
| 
      
 235 
     | 
    
         
            +
                end
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                private
         
     | 
| 
      
 238 
     | 
    
         
            +
                  def valid_paths(paths)
         
     | 
| 
      
 239 
     | 
    
         
            +
                    paths.select do |path|
         
     | 
| 
      
 240 
     | 
    
         
            +
                      FORMATS.detect do |format|
         
     | 
| 
      
 241 
     | 
    
         
            +
                        File.exist?(File.join(Nesta::Config.page_path, "#{path}.#{format}"))
         
     | 
| 
      
 242 
     | 
    
         
            +
                      end
         
     | 
| 
      
 243 
     | 
    
         
            +
                    end
         
     | 
| 
      
 244 
     | 
    
         
            +
                  end
         
     | 
| 
      
 245 
     | 
    
         
            +
              end
         
     | 
| 
      
 246 
     | 
    
         
            +
             
     | 
| 
      
 247 
     | 
    
         
            +
              class Menu
         
     | 
| 
      
 248 
     | 
    
         
            +
                INDENT = " " * 2
         
     | 
| 
      
 249 
     | 
    
         
            +
             
     | 
| 
      
 250 
     | 
    
         
            +
                def self.full_menu
         
     | 
| 
      
 251 
     | 
    
         
            +
                  menu = []
         
     | 
| 
      
 252 
     | 
    
         
            +
                  menu_file = Nesta::Config.content_path('menu.txt')
         
     | 
| 
      
 253 
     | 
    
         
            +
                  if File.exist?(menu_file)
         
     | 
| 
      
 254 
     | 
    
         
            +
                    File.open(menu_file) { |file| append_menu_item(menu, file, 0) }
         
     | 
| 
      
 255 
     | 
    
         
            +
                  end
         
     | 
| 
      
 256 
     | 
    
         
            +
                  menu
         
     | 
| 
      
 257 
     | 
    
         
            +
                end
         
     | 
| 
      
 258 
     | 
    
         
            +
             
     | 
| 
      
 259 
     | 
    
         
            +
                def self.top_level
         
     | 
| 
      
 260 
     | 
    
         
            +
                  full_menu.reject { |item| item.is_a?(Array) }
         
     | 
| 
      
 261 
     | 
    
         
            +
                end
         
     | 
| 
      
 262 
     | 
    
         
            +
             
     | 
| 
      
 263 
     | 
    
         
            +
                def self.for_path(path)
         
     | 
| 
      
 264 
     | 
    
         
            +
                  path.sub!(Regexp.new('^/'), '')
         
     | 
| 
      
 265 
     | 
    
         
            +
                  if path.empty?
         
     | 
| 
      
 266 
     | 
    
         
            +
                    full_menu
         
     | 
| 
      
 267 
     | 
    
         
            +
                  else
         
     | 
| 
      
 268 
     | 
    
         
            +
                    find_menu_item_by_path(full_menu, path)
         
     | 
| 
      
 269 
     | 
    
         
            +
                  end
         
     | 
| 
      
 270 
     | 
    
         
            +
                end
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
                private
         
     | 
| 
      
 273 
     | 
    
         
            +
                  def self.append_menu_item(menu, file, depth)
         
     | 
| 
      
 274 
     | 
    
         
            +
                    path = file.readline
         
     | 
| 
      
 275 
     | 
    
         
            +
                  rescue EOFError
         
     | 
| 
      
 276 
     | 
    
         
            +
                  else
         
     | 
| 
      
 277 
     | 
    
         
            +
                    page = Page.load(path.strip)
         
     | 
| 
      
 278 
     | 
    
         
            +
                    if page
         
     | 
| 
      
 279 
     | 
    
         
            +
                      current_depth = path.scan(INDENT).size
         
     | 
| 
      
 280 
     | 
    
         
            +
                      if current_depth > depth
         
     | 
| 
      
 281 
     | 
    
         
            +
                        sub_menu_for_depth(menu, depth) << [page]
         
     | 
| 
      
 282 
     | 
    
         
            +
                      else
         
     | 
| 
      
 283 
     | 
    
         
            +
                        sub_menu_for_depth(menu, current_depth) << page
         
     | 
| 
      
 284 
     | 
    
         
            +
                      end
         
     | 
| 
      
 285 
     | 
    
         
            +
                      append_menu_item(menu, file, current_depth)
         
     | 
| 
      
 286 
     | 
    
         
            +
                    end
         
     | 
| 
      
 287 
     | 
    
         
            +
                  end
         
     | 
| 
      
 288 
     | 
    
         
            +
             
     | 
| 
      
 289 
     | 
    
         
            +
                  def self.sub_menu_for_depth(menu, depth)
         
     | 
| 
      
 290 
     | 
    
         
            +
                    sub_menu = menu
         
     | 
| 
      
 291 
     | 
    
         
            +
                    depth.times { sub_menu = sub_menu[-1] }
         
     | 
| 
      
 292 
     | 
    
         
            +
                    sub_menu
         
     | 
| 
      
 293 
     | 
    
         
            +
                  end
         
     | 
| 
      
 294 
     | 
    
         
            +
             
     | 
| 
      
 295 
     | 
    
         
            +
                  def self.find_menu_item_by_path(menu, path)
         
     | 
| 
      
 296 
     | 
    
         
            +
                    item = menu.detect do |item|
         
     | 
| 
      
 297 
     | 
    
         
            +
                      item.respond_to?(:path) && (item.path == path)
         
     | 
| 
      
 298 
     | 
    
         
            +
                    end
         
     | 
| 
      
 299 
     | 
    
         
            +
                    if item
         
     | 
| 
      
 300 
     | 
    
         
            +
                      subsequent = menu[menu.index(item) + 1]
         
     | 
| 
      
 301 
     | 
    
         
            +
                      item = [item]
         
     | 
| 
      
 302 
     | 
    
         
            +
                      item << subsequent if subsequent.respond_to?(:each)
         
     | 
| 
      
 303 
     | 
    
         
            +
                    else
         
     | 
| 
      
 304 
     | 
    
         
            +
                      sub_menus = menu.select { |menu_item| menu_item.respond_to?(:each) }
         
     | 
| 
      
 305 
     | 
    
         
            +
                      sub_menus.each do |sub_menu|
         
     | 
| 
      
 306 
     | 
    
         
            +
                        item = find_menu_item_by_path(sub_menu, path)
         
     | 
| 
      
 307 
     | 
    
         
            +
                        break if item
         
     | 
| 
      
 308 
     | 
    
         
            +
                      end
         
     | 
| 
      
 309 
     | 
    
         
            +
                    end
         
     | 
| 
      
 310 
     | 
    
         
            +
                    item
         
     | 
| 
      
 311 
     | 
    
         
            +
                  end
         
     | 
| 
      
 312 
     | 
    
         
            +
              end
         
     | 
| 
      
 313 
     | 
    
         
            +
            end
         
     |