serum 0.1.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/Gemfile +2 -0
- data/LICENSE +23 -0
- data/README.md +55 -0
- data/Rakefile +10 -0
- data/lib/serum.rb +71 -0
- data/lib/serum/core_ext.rb +68 -0
- data/lib/serum/errors.rb +4 -0
- data/lib/serum/mime.types +84 -0
- data/lib/serum/post.rb +177 -0
- data/lib/serum/site.rb +116 -0
- data/lib/serum/static_file.rb +53 -0
- data/serum.gemspec +85 -0
- data/test/helper.rb +26 -0
- data/test/source/2008-02-02-not-published.textile +8 -0
- data/test/source/2008-02-02-published.textile +8 -0
- data/test/source/2008-10-18-foo-bar.textile +8 -0
- data/test/source/2008-11-21-complex.textile +8 -0
- data/test/source/2008-12-03-permalinked-post.textile +9 -0
- data/test/source/2008-12-13-include.markdown +8 -0
- data/test/source/2009-01-27-array-categories.textile +10 -0
- data/test/source/2009-01-27-categories.textile +7 -0
- data/test/source/2009-01-27-category.textile +7 -0
- data/test/source/2009-01-27-empty-categories.textile +7 -0
- data/test/source/2009-01-27-empty-category.textile +7 -0
- data/test/source/2009-03-12-hash-#1.markdown +6 -0
- data/test/source/2009-05-18-empty-tag.textile +6 -0
- data/test/source/2009-05-18-empty-tags.textile +6 -0
- data/test/source/2009-05-18-tag.textile +6 -0
- data/test/source/2009-05-18-tags.textile +9 -0
- data/test/source/2009-05-24-yaml-linebreak.markdown +7 -0
- data/test/source/2009-06-22-empty-yaml.textile +3 -0
- data/test/source/2009-06-22-no-yaml.textile +1 -0
- data/test/source/2010-01-08-triple-dash.markdown +6 -0
- data/test/source/2010-01-09-date-override.textile +7 -0
- data/test/source/2010-01-09-time-override.textile +7 -0
- data/test/source/2010-01-09-timezone-override.textile +7 -0
- data/test/source/2010-01-16-override-data.textile +4 -0
- data/test/source/2011-04-12-md-extension.md +7 -0
- data/test/source/2011-04-12-text-extension.text +0 -0
- data/test/source/2013-01-02-post-excerpt.markdown +14 -0
- data/test/source/2013-01-12-nil-layout.textile +6 -0
- data/test/source/2013-01-12-no-layout.textile +5 -0
- data/test/suite.rb +11 -0
- data/test/test_core_ext.rb +88 -0
- data/test/test_post.rb +138 -0
- data/test/test_site.rb +68 -0
- metadata +194 -0
    
        data/Gemfile
    ADDED
    
    
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,23 @@ | |
| 1 | 
            +
            (The MIT License)
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Copyright (c) 2013 Brad Fults
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Jekyll Copyright (c) 2008 Tom Preston-Werner
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining a copy
         | 
| 8 | 
            +
            of this software and associated documentation files (the 'Software'), to deal
         | 
| 9 | 
            +
            in the Software without restriction, including without limitation the rights
         | 
| 10 | 
            +
            to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
         | 
| 11 | 
            +
            copies of the Software, and to permit persons to whom the Software is
         | 
| 12 | 
            +
            furnished to do so, subject to the following conditions:
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            The above copyright notice and this permission notice shall be included in all
         | 
| 15 | 
            +
            copies or substantial portions of the Software.
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
         | 
| 18 | 
            +
            IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
         | 
| 19 | 
            +
            FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
         | 
| 20 | 
            +
            AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
         | 
| 21 | 
            +
            LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
         | 
| 22 | 
            +
            OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
         | 
| 23 | 
            +
            SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,55 @@ | |
| 1 | 
            +
            # Serum
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Serum is a simple object model on static posts with YAML front matter.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            ## Usage
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            Instantiate Serum passing in a directory of files that may or may not have
         | 
| 8 | 
            +
            YAML front matter:
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ```irb
         | 
| 11 | 
            +
            >> site = Serum.for_dir("posts/")
         | 
| 12 | 
            +
            => <Site: /Users/bob/posts>
         | 
| 13 | 
            +
            ```
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            Then ask questions about the posts:
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            ```irb
         | 
| 18 | 
            +
            >> site.posts.size
         | 
| 19 | 
            +
            => 28
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            >> site.posts.first
         | 
| 22 | 
            +
            => <Post: /published>
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            >> site.posts.first.next
         | 
| 25 | 
            +
            => <Post: /foo-bar>
         | 
| 26 | 
            +
            ```
         | 
| 27 | 
            +
             | 
| 28 | 
            +
            You can also pass in a `'baseurl'` option to `for_dir` in order to get URL
         | 
| 29 | 
            +
            generation on each of the posts:
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            ```irb
         | 
| 32 | 
            +
            >> site = Serum.for_dir('posts/', {'baseurl' => '/story'})
         | 
| 33 | 
            +
            => <Site: /Users/bob/posts>
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            >> site.posts.first.url
         | 
| 36 | 
            +
            => "/story/published"
         | 
| 37 | 
            +
            ```
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            It's really that simple. Sometimes you just want a Ruby object model on top of
         | 
| 40 | 
            +
            a simple directory of posts.
         | 
| 41 | 
            +
             | 
| 42 | 
            +
            ## Author
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            Maybe more aptly named "deleter" considering this project's origin.
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            [Brad Fults](http://github.com/h3h) ([bfults@gmail.com][])
         | 
| 47 | 
            +
             | 
| 48 | 
            +
            ## Acknowledgements
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            Serum is based entirely on [Jekyll](http://jekyllrb.com/) from Tomm Preston-Werner.
         | 
| 51 | 
            +
            Thank you, Tom.
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            ## License
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            MIT License; see `LICENSE` file.
         | 
    
        data/Rakefile
    ADDED
    
    
    
        data/lib/serum.rb
    ADDED
    
    | @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            $:.unshift File.dirname(__FILE__) # For use/testing when no gem is installed
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # Require all of the Ruby files in the given directory.
         | 
| 4 | 
            +
            #
         | 
| 5 | 
            +
            # path - The String relative path from here to the directory.
         | 
| 6 | 
            +
            #
         | 
| 7 | 
            +
            # Returns nothing.
         | 
| 8 | 
            +
            def require_all(path)
         | 
| 9 | 
            +
              glob = File.join(File.dirname(__FILE__), path, '*.rb')
         | 
| 10 | 
            +
              Dir[glob].each do |f|
         | 
| 11 | 
            +
                require f
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            # rubygems
         | 
| 16 | 
            +
            require 'rubygems'
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            # stdlib
         | 
| 19 | 
            +
            require 'fileutils'
         | 
| 20 | 
            +
            require 'time'
         | 
| 21 | 
            +
            require 'safe_yaml'
         | 
| 22 | 
            +
            require 'English'
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            # internal requires
         | 
| 25 | 
            +
            require 'serum/core_ext'
         | 
| 26 | 
            +
            require 'serum/site'
         | 
| 27 | 
            +
            require 'serum/post'
         | 
| 28 | 
            +
            require 'serum/static_file'
         | 
| 29 | 
            +
            require 'serum/errors'
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            SafeYAML::OPTIONS[:suppress_warnings] = true
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            module Serum
         | 
| 34 | 
            +
              VERSION = '0.1.0'
         | 
| 35 | 
            +
             | 
| 36 | 
            +
              # Default options.
         | 
| 37 | 
            +
              # Strings rather than symbols are used for compatability with YAML.
         | 
| 38 | 
            +
              DEFAULTS = {
         | 
| 39 | 
            +
                'source'        => Dir.pwd,
         | 
| 40 | 
            +
                'permalink'     => ':title',
         | 
| 41 | 
            +
                'baseurl'       => '',
         | 
| 42 | 
            +
                'include'       => ['.htaccess'],
         | 
| 43 | 
            +
              }
         | 
| 44 | 
            +
             | 
| 45 | 
            +
              # Public: Generate a Serum configuration Hash by merging the default
         | 
| 46 | 
            +
              # options with anything in _config.yml, and adding the given options on top.
         | 
| 47 | 
            +
              #
         | 
| 48 | 
            +
              # override - A Hash of config directives that override any options in both
         | 
| 49 | 
            +
              #            the defaults and the config file. See Serum::DEFAULTS for a
         | 
| 50 | 
            +
              #            list of option names and their defaults.
         | 
| 51 | 
            +
              #
         | 
| 52 | 
            +
              # Returns the final configuration Hash.
         | 
| 53 | 
            +
              def self.configuration(override)
         | 
| 54 | 
            +
                # Convert any symbol keys to strings and remove the old key/values
         | 
| 55 | 
            +
                override = override.reduce({}) { |hsh,(k,v)| hsh.merge(k.to_s => v) }
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                # Merge DEFAULTS < override
         | 
| 58 | 
            +
                Serum::DEFAULTS.deep_merge(override)
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
              # Public: Generate a new Serum::Site for the given directory.
         | 
| 62 | 
            +
              #
         | 
| 63 | 
            +
              # source - A String path to the directory.
         | 
| 64 | 
            +
              # opts   - A Hash of additional configuration options.
         | 
| 65 | 
            +
              #
         | 
| 66 | 
            +
              # Returns the Serum::Site.
         | 
| 67 | 
            +
              def self.for_dir(source, opts={})
         | 
| 68 | 
            +
                Serum::Site.new(configuration({source: source}.merge(opts)))
         | 
| 69 | 
            +
              end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
            end
         | 
| @@ -0,0 +1,68 @@ | |
| 1 | 
            +
            class Hash
         | 
| 2 | 
            +
              # Merges self with another hash, recursively.
         | 
| 3 | 
            +
              #
         | 
| 4 | 
            +
              # This code was lovingly stolen from some random gem:
         | 
| 5 | 
            +
              # http://gemjack.com/gems/tartan-0.1.1/classes/Hash.html
         | 
| 6 | 
            +
              #
         | 
| 7 | 
            +
              # Thanks to whoever made it.
         | 
| 8 | 
            +
              def deep_merge(hash)
         | 
| 9 | 
            +
                target = dup
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                hash.keys.each do |key|
         | 
| 12 | 
            +
                  if hash[key].is_a? Hash and self[key].is_a? Hash
         | 
| 13 | 
            +
                    target[key] = target[key].deep_merge(hash[key])
         | 
| 14 | 
            +
                    next
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                  target[key] = hash[key]
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                target
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              # Read array from the supplied hash favouring the singular key
         | 
| 24 | 
            +
              # and then the plural key, and handling any nil entries.
         | 
| 25 | 
            +
              #   +hash+ the hash to read from
         | 
| 26 | 
            +
              #   +singular_key+ the singular key
         | 
| 27 | 
            +
              #   +plural_key+ the plural key
         | 
| 28 | 
            +
              #
         | 
| 29 | 
            +
              # Returns an array
         | 
| 30 | 
            +
              def pluralized_array(singular_key, plural_key)
         | 
| 31 | 
            +
                hash = self
         | 
| 32 | 
            +
                if hash.has_key?(singular_key)
         | 
| 33 | 
            +
                  array = [hash[singular_key]] if hash[singular_key]
         | 
| 34 | 
            +
                elsif hash.has_key?(plural_key)
         | 
| 35 | 
            +
                  case hash[plural_key]
         | 
| 36 | 
            +
                  when String
         | 
| 37 | 
            +
                    array = hash[plural_key].split
         | 
| 38 | 
            +
                  when Array
         | 
| 39 | 
            +
                    array = hash[plural_key].compact
         | 
| 40 | 
            +
                  end
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
                array || []
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
            # Thanks, ActiveSupport!
         | 
| 47 | 
            +
            class Date
         | 
| 48 | 
            +
              # Converts datetime to an appropriate format for use in XML
         | 
| 49 | 
            +
              def xmlschema
         | 
| 50 | 
            +
                strftime("%Y-%m-%dT%H:%M:%S%Z")
         | 
| 51 | 
            +
              end if RUBY_VERSION < '1.9'
         | 
| 52 | 
            +
            end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            module Enumerable
         | 
| 55 | 
            +
              # Returns true if path matches against any glob pattern.
         | 
| 56 | 
            +
              # Look for more detail about glob pattern in method File::fnmatch.
         | 
| 57 | 
            +
              def glob_include?(e)
         | 
| 58 | 
            +
                any? { |exp| File.fnmatch?(exp, e) }
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            if RUBY_VERSION < "1.9"
         | 
| 63 | 
            +
              class String
         | 
| 64 | 
            +
                def force_encoding(enc)
         | 
| 65 | 
            +
                  self
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
              end
         | 
| 68 | 
            +
            end
         | 
    
        data/lib/serum/errors.rb
    ADDED
    
    
| @@ -0,0 +1,84 @@ | |
| 1 | 
            +
            # These are the same MIME types that GitHub Pages uses as of 17 Mar 2013.
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            text/html                             html htm shtml
         | 
| 4 | 
            +
            text/css                              css
         | 
| 5 | 
            +
            text/xml                              xml rss xsl
         | 
| 6 | 
            +
            image/gif                             gif
         | 
| 7 | 
            +
            image/jpeg                            jpeg jpg
         | 
| 8 | 
            +
            application/x-javascript              js
         | 
| 9 | 
            +
            application/atom+xml                  atom
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            text/mathml                           mml
         | 
| 12 | 
            +
            text/plain                            txt
         | 
| 13 | 
            +
            text/vnd.sun.j2me.app-descriptor      jad
         | 
| 14 | 
            +
            text/vnd.wap.wml                      wml
         | 
| 15 | 
            +
            text/x-component                      htc
         | 
| 16 | 
            +
            text/cache-manifest                   manifest appcache
         | 
| 17 | 
            +
            text/coffeescript                     coffee
         | 
| 18 | 
            +
            text/plain                            pde
         | 
| 19 | 
            +
            text/plain                            md markdown
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            image/png                             png
         | 
| 22 | 
            +
            image/svg+xml                         svg
         | 
| 23 | 
            +
            image/tiff                            tif tiff
         | 
| 24 | 
            +
            image/vnd.wap.wbmp                    wbmp
         | 
| 25 | 
            +
            image/x-icon                          ico
         | 
| 26 | 
            +
            image/x-jng                           jng
         | 
| 27 | 
            +
            image/x-ms-bmp                        bmp
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            application/json                      json
         | 
| 30 | 
            +
            application/java-archive              jar ear
         | 
| 31 | 
            +
            application/mac-binhex40              hqx
         | 
| 32 | 
            +
            application/msword                    doc
         | 
| 33 | 
            +
            application/pdf                       pdf
         | 
| 34 | 
            +
            application/postscript                ps eps ai
         | 
| 35 | 
            +
            application/rdf+xml                   rdf
         | 
| 36 | 
            +
            application/rtf                       rtf
         | 
| 37 | 
            +
            text/vcard                            vcf vcard
         | 
| 38 | 
            +
            application/vnd.ms-excel              xls
         | 
| 39 | 
            +
            application/vnd.ms-powerpoint         ppt
         | 
| 40 | 
            +
            application/vnd.wap.wmlc              wmlc
         | 
| 41 | 
            +
            application/xhtml+xml                 xhtml
         | 
| 42 | 
            +
            application/x-chrome-extension        crx
         | 
| 43 | 
            +
            application/x-cocoa                   cco
         | 
| 44 | 
            +
            application/x-font-ttf                ttf
         | 
| 45 | 
            +
            application/x-java-archive-diff       jardiff
         | 
| 46 | 
            +
            application/x-java-jnlp-file          jnlp
         | 
| 47 | 
            +
            application/x-makeself                run
         | 
| 48 | 
            +
            application/x-ns-proxy-autoconfig     pac
         | 
| 49 | 
            +
            application/x-perl                    pl pm
         | 
| 50 | 
            +
            application/x-pilot                   prc pdb
         | 
| 51 | 
            +
            application/x-rar-compressed          rar
         | 
| 52 | 
            +
            application/x-redhat-package-manager  rpm
         | 
| 53 | 
            +
            application/x-sea                     sea
         | 
| 54 | 
            +
            application/x-shockwave-flash         swf
         | 
| 55 | 
            +
            application/x-stuffit                 sit
         | 
| 56 | 
            +
            application/x-tcl                     tcl tk
         | 
| 57 | 
            +
            application/x-web-app-manifest+json   webapp
         | 
| 58 | 
            +
            application/x-x509-ca-cert            der pem crt
         | 
| 59 | 
            +
            application/x-xpinstall               xpi
         | 
| 60 | 
            +
            application/x-zip                     war
         | 
| 61 | 
            +
            application/zip                       zip
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            application/octet-stream              bin exe dll
         | 
| 64 | 
            +
            application/octet-stream              deb
         | 
| 65 | 
            +
            application/octet-stream              dmg
         | 
| 66 | 
            +
            application/octet-stream              eot
         | 
| 67 | 
            +
            application/octet-stream              iso img
         | 
| 68 | 
            +
            application/octet-stream              msi msp msm
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            audio/midi                            mid midi kar
         | 
| 71 | 
            +
            audio/mpeg                            mp3
         | 
| 72 | 
            +
            audio/x-realaudio                     ra
         | 
| 73 | 
            +
            audio/ogg                             ogg
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            video/3gpp                            3gpp 3gp
         | 
| 76 | 
            +
            video/mpeg                            mpeg mpg
         | 
| 77 | 
            +
            video/quicktime                       mov
         | 
| 78 | 
            +
            video/x-flv                           flv
         | 
| 79 | 
            +
            video/x-mng                           mng
         | 
| 80 | 
            +
            video/x-ms-asf                        asx asf
         | 
| 81 | 
            +
            video/x-ms-wmv                        wmv
         | 
| 82 | 
            +
            video/x-msvideo                       avi
         | 
| 83 | 
            +
            video/ogg                             ogv
         | 
| 84 | 
            +
            video/webm                            webm
         | 
    
        data/lib/serum/post.rb
    ADDED
    
    | @@ -0,0 +1,177 @@ | |
| 1 | 
            +
            require 'cgi'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Serum
         | 
| 4 | 
            +
              class Post
         | 
| 5 | 
            +
                include Comparable
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                # Valid post name regex.
         | 
| 8 | 
            +
                MATCHER = %r{
         | 
| 9 | 
            +
                  ^(.+\/)*        # zero or more path segments including their trailing slash
         | 
| 10 | 
            +
                  (\d+-\d+-\d+)   # three numbers (YYYY-mm-dd) separated by hyphens for the date
         | 
| 11 | 
            +
                  -(.*)           # a hyphen followed by the slug
         | 
| 12 | 
            +
                  (\.[^.]+)$      # the file extension after a .
         | 
| 13 | 
            +
                }x
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                # Post name validator. Post filenames must be like:
         | 
| 16 | 
            +
                # 2008-11-05-my-awesome-post.textile
         | 
| 17 | 
            +
                #
         | 
| 18 | 
            +
                # Returns true if valid, false if not.
         | 
| 19 | 
            +
                def self.valid?(name)
         | 
| 20 | 
            +
                  name =~ MATCHER
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                attr_accessor :site
         | 
| 24 | 
            +
                attr_accessor :data, :content, :output, :ext
         | 
| 25 | 
            +
                attr_accessor :date, :slug, :published, :dir
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                attr_reader :name
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                # Initialize this Post instance.
         | 
| 30 | 
            +
                #
         | 
| 31 | 
            +
                # site       - The Site.
         | 
| 32 | 
            +
                # base       - The String path to the dir containing the post file.
         | 
| 33 | 
            +
                # name       - The String filename of the post file.
         | 
| 34 | 
            +
                #
         | 
| 35 | 
            +
                # Returns the new Post.
         | 
| 36 | 
            +
                def initialize(site, source, dir, name)
         | 
| 37 | 
            +
                  @site = site
         | 
| 38 | 
            +
                  @base = source
         | 
| 39 | 
            +
                  @dir = dir
         | 
| 40 | 
            +
                  @name = name
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  self.process(name)
         | 
| 43 | 
            +
                  begin
         | 
| 44 | 
            +
                    self.read_yaml(@base, name)
         | 
| 45 | 
            +
                  rescue Exception => msg
         | 
| 46 | 
            +
                    raise FatalException.new("#{msg} in #{@base}/#{name}")
         | 
| 47 | 
            +
                  end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  # If we've added a date and time to the YAML, use that instead of the
         | 
| 50 | 
            +
                  # filename date. Means we'll sort correctly.
         | 
| 51 | 
            +
                  if self.data.has_key?('date')
         | 
| 52 | 
            +
                    # ensure Time via to_s and reparse
         | 
| 53 | 
            +
                    self.date = Time.parse(self.data["date"].to_s)
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  if self.data.has_key?('published') && self.data['published'] == false
         | 
| 57 | 
            +
                    self.published = false
         | 
| 58 | 
            +
                  else
         | 
| 59 | 
            +
                    self.published = true
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                # Read the YAML frontmatter.
         | 
| 64 | 
            +
                #
         | 
| 65 | 
            +
                # base - The String path to the dir containing the file.
         | 
| 66 | 
            +
                # name - The String filename of the file.
         | 
| 67 | 
            +
                #
         | 
| 68 | 
            +
                # Returns nothing.
         | 
| 69 | 
            +
                def read_yaml(base, name)
         | 
| 70 | 
            +
                  begin
         | 
| 71 | 
            +
                    content = File.read(File.join(base, name))
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    if content =~ /\A(---\s*\n.*?\n?)^(---\s*$\n?)/m
         | 
| 74 | 
            +
                      self.data = YAML.safe_load($1)
         | 
| 75 | 
            +
                    end
         | 
| 76 | 
            +
                  rescue => e
         | 
| 77 | 
            +
                    puts "Error reading file #{File.join(base, name)}: #{e.message}"
         | 
| 78 | 
            +
                  rescue SyntaxError => e
         | 
| 79 | 
            +
                    puts "YAML Exception reading #{File.join(base, name)}: #{e.message}"
         | 
| 80 | 
            +
                  end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                  self.data ||= {}
         | 
| 83 | 
            +
                end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                # Compares Post objects. First compares the Post date. If the dates are
         | 
| 86 | 
            +
                # equal, it compares the Post slugs.
         | 
| 87 | 
            +
                #
         | 
| 88 | 
            +
                # other - The other Post we are comparing to.
         | 
| 89 | 
            +
                #
         | 
| 90 | 
            +
                # Returns -1, 0, 1
         | 
| 91 | 
            +
                def <=>(other)
         | 
| 92 | 
            +
                  cmp = self.date <=> other.date
         | 
| 93 | 
            +
                  if 0 == cmp
         | 
| 94 | 
            +
                   cmp = self.slug <=> other.slug
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
                  return cmp
         | 
| 97 | 
            +
                end
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                # Extract information from the post filename.
         | 
| 100 | 
            +
                #
         | 
| 101 | 
            +
                # name - The String filename of the post file.
         | 
| 102 | 
            +
                #
         | 
| 103 | 
            +
                # Returns nothing.
         | 
| 104 | 
            +
                def process(name)
         | 
| 105 | 
            +
                  m, cats, date, slug, ext = *name.match(MATCHER)
         | 
| 106 | 
            +
                  self.date = Time.parse(date)
         | 
| 107 | 
            +
                  self.slug = slug
         | 
| 108 | 
            +
                  self.ext = ext
         | 
| 109 | 
            +
                rescue ArgumentError
         | 
| 110 | 
            +
                  raise FatalException.new("Post #{name} does not have a valid date.")
         | 
| 111 | 
            +
                end
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                # The full path and filename of the post. Defined in the YAML of the post
         | 
| 114 | 
            +
                # body (optional).
         | 
| 115 | 
            +
                #
         | 
| 116 | 
            +
                # Returns the String permalink.
         | 
| 117 | 
            +
                def permalink
         | 
| 118 | 
            +
                  self.data && self.data['permalink']
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
                # The generated relative url of this post.
         | 
| 122 | 
            +
                # e.g. /2008/11/05/my-awesome-post.html
         | 
| 123 | 
            +
                #
         | 
| 124 | 
            +
                # Returns the String URL.
         | 
| 125 | 
            +
                def url
         | 
| 126 | 
            +
                  return @url if @url
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                  url = "#{self.site.baseurl}#{self.id}"
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  # sanitize url
         | 
| 131 | 
            +
                  @url = url.split('/').reject{ |part| part =~ /^\.+$/ }.join('/')
         | 
| 132 | 
            +
                  @url += "/" if url =~ /\/$/
         | 
| 133 | 
            +
                  @url
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                # The UID for this post (useful in feeds).
         | 
| 137 | 
            +
                # e.g. /2008/11/05/my-awesome-post
         | 
| 138 | 
            +
                #
         | 
| 139 | 
            +
                # Returns the String UID.
         | 
| 140 | 
            +
                def id
         | 
| 141 | 
            +
                  File.join(self.dir, self.slug)
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                # Returns the shorthand String identifier of this Post.
         | 
| 145 | 
            +
                def inspect
         | 
| 146 | 
            +
                  "<Post: #{self.id}>"
         | 
| 147 | 
            +
                end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                def next
         | 
| 150 | 
            +
                  pos = self.site.posts.index(self)
         | 
| 151 | 
            +
             | 
| 152 | 
            +
                  if pos && pos < self.site.posts.length-1
         | 
| 153 | 
            +
                    self.site.posts[pos+1]
         | 
| 154 | 
            +
                  else
         | 
| 155 | 
            +
                    nil
         | 
| 156 | 
            +
                  end
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                def previous
         | 
| 160 | 
            +
                  pos = self.site.posts.index(self)
         | 
| 161 | 
            +
                  if pos && pos > 0
         | 
| 162 | 
            +
                    self.site.posts[pos-1]
         | 
| 163 | 
            +
                  else
         | 
| 164 | 
            +
                    nil
         | 
| 165 | 
            +
                  end
         | 
| 166 | 
            +
                end
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                def method_missing(meth, *args)
         | 
| 169 | 
            +
                  if self.data.has_key?(meth.to_s) && args.empty?
         | 
| 170 | 
            +
                    self.data[meth.to_s]
         | 
| 171 | 
            +
                  else
         | 
| 172 | 
            +
                    super
         | 
| 173 | 
            +
                  end
         | 
| 174 | 
            +
                end
         | 
| 175 | 
            +
             | 
| 176 | 
            +
              end
         | 
| 177 | 
            +
            end
         |