# # Backend for file-system based articles # class Serious::Article # Exception for invalid filenames class InvalidFilename < StandardError end class << self # # Returns all articles. Can be drilled down by (optional) :limit and :offset options # def all(options={}) options = {:limit => 10000, :offset => 0}.merge(options) (article_paths[options[:offset]...options[:limit]+options[:offset]] || []).map {|article_path| new(article_path) } end # # Retrieves the article(s) for the given arguments. The arguments must be part of the filename. # You can give any combination, but the arguments must appear in the same order as given in the # arguments, so find(2009, 11) works for all articles in november 2009, # find('foo-bar') works for all articles with permalink 'foo-bar', but find(2009, 'foo-bar') # will not return anything. # def find(*args) # Reformat arguments (one-digit months and days should be converted to two-digit format) args = args.map {|a| a.to_s =~ /^\d{1}$/ ? "%02d" % a : a } articles = article_paths.select {|path| File.basename(path) =~ /#{args.join('-')}/i }.map {|path| new(path) } end # # Find the article for given arguments (same as find), but returns only the first one # def first(*args) find(*args).first end private # Returns all article files in articles path def article_paths @article_paths ||= Dir[File.join(Serious.articles, '*')].sort.reverse end end attr_reader :path, :date, :permalink def initialize(path) @path = path extract_date_and_permalink! # TODO: Add some nice error handling! end # Lazy-loading title accessor def title @title ||= yaml["title"] end # Lazy-loading author accessor with fallback to Serious.author def author @author ||= yaml["author"] || Serious.author end # Cached lazy-loading of summary def summary return @summary if @summary @summary ||= content.split("~", 2).first.chomp end # Cached lazy-loading of body def body return @body if @body @body ||= content.split("~", 2).join("").chomp end # Compiles the url for this article def url @url ||= "/#{date.year}/#{"%02d" % date.month}/#{"%02d" % date.day}/#{permalink}" end # url combined with Serious.url def full_url @full_url ||= File.join(Serious.url, url) end # Equality comparison def ==(article) path == article.path rescue NoMethodError false end # Collection of validation errors def errors @errors || valid? end # # Loads the article and makes sure it can be loaded, formatted etc.. # Error messages can be read through errors array # def valid? @errors = [] errors << "No title given" unless title.kind_of?(String) and title.length > 0 errors << "No author given" unless author.kind_of?(String) and author.length > 0 begin summary.formatted rescue => err errors << "Failed to format summary" end begin body.formatted rescue => err errors << "Failed to format body" end ensure return errors.length == 0 end private # Will extract the date and permalink from the filename. def extract_date_and_permalink! match = File.basename(path).match(/(\d{4})-(\d{1,2})-(\d{1,2})-([^\.]+)/) @date = Date.new(match[1].to_i, match[2].to_i, match[3].to_i) @permalink = match[4] rescue NoMethodError => err raise InvalidFilename, "Failed to extract date or permalink from #{File.basename(path)}" end # # Will read the actual article file and store the yaml front processed in @yaml, # the content in @content # def load! return true if @yaml and @content yaml, @content = File.read(path).split(/\n\n/, 2) @yaml = YAML.load(yaml) end # Cached lazy-loading yaml config def yaml return @yaml if @yaml load! @yaml end # Cached lazy-loading content def content return @content if @content load! @content end end