module Shinmun # This class represents a post or page. # Each post has a header, encoded as YAML and a body. # # Example: # --- # category: Ruby # date: 2008-09-05 # title: BlueCloth, a Markdown library # --- # This is the summary, which is by definition the first paragraph of the # article. The summary shows up in list views and rss feeds. # class Post %w[title author date category tags].each do |name| define_method(name) { head[name] } define_method("#{name}=") {|v| head[name] = v } end attr_writer :name attr_accessor :src, :type, :head, :body, :file, :mtime # Initialize empty post and set specified attributes. def initialize(attributes={}) @head = {} @body = '' @type = 'md' attributes.each do |k, v| send "#{k}=", v end load if file end def name @name ||= title.to_s.downcase.gsub(/[ -]+/, '-').gsub(/[^-a-z0-9_]+/, '') end def method_missing(id, *args) key = id.to_s if @head.has_key?(key) @head[key] else raise NoMethodError, "undefined method `#{id}' for #{self}", caller(1) end end # Shortcut for year of date def year date.year end # Shortcut for month of date def month date.month end def tag_list @tag_list ||= tags.to_s.split(",").map { |s| s.strip } end def body_html @body_html ||= transform(@body) end def summary @summary ||= body_html.split("\n\n")[0] end def path folder = date ? "posts/#{year}/#{month}" : 'pages' "#{folder}/#{name}.#{type}" end def load self.type = File.extname(file)[1..-1] self.name = File.basename(file).chomp(".#{type}") self.mtime = File.mtime(file) parse(File.read(file)) end def changed? File.mtime(file) != mtime end def save FileUtils.mkpath(File.dirname(path)) File.open(path, 'w') do |io| io << dump end end # Split up the source into header and body. Load the header as # yaml document. def parse(src) if src =~ /\A---(.*?)---(.*)/m @head = YAML.load($1) @body = $2 else @body = src end @body_html = nil @tag_list = nil @summary = nil end # Convert to string representation def dump head.to_yaml + "---" + body end # Transform the body of this post. Defaults to Markdown. def transform(src, options={}) case type when 'html' RubyPants.new(src).to_html when 'tt' RubyPants.new(RedCloth.new(src).to_html).to_html else bluecloth = BlueCloth.new(src) bluecloth.code_css = options[:code_css] RubyPants.new(bluecloth.to_html).to_html end end def eql?(obj) self == obj end def ==(obj) Post === obj and file == obj.file end end end