require 'yaml' =begin class Array def to_inline_yaml( opts = {:UseHeader => false} ) YAML::quick_emit( object_id, opts ) do |out| out.seq( taguri, :inline ) do |seq| each do |x| seq.add( x ) end end end end end =end class Pyrosoma class InvalidDocument < RuntimeError; end class Frame < ::Hash class ParseError < InvalidDocument; end ORDER = [:title, :id, :master] attr_accessor :db def initialize(hash) initialize_copy hash end def self.load(string) fields = {} field = nil string.each_with_index { |line, lineno| line.chomp! break if line.empty? if line =~ /^(\w\S*?):\s*(.*)/ field = $1.downcase.intern if !fields.include? field fields[field] = $2 elsif fields[field].kind_of? Array fields[field] << $2 else fields[field] = [fields[field], $2] end elsif field && line =~ /^ / content = fields[field] content = content.last if content.kind_of? Array content << "\n" << $' else raise ParseError, "parse error in line #{lineno+1}: `#{line}'" end } if string =~ /^\n/ fields[:content] = $' unless $'.empty? end new fields end def to_s r = "" header_keys.each { |key| vs = fetch(key) vs = [vs] unless vs.kind_of? Array vs.each { |v| r << key.to_s.capitalize << ": " << v.gsub(/(?!\A)^/, ' ') << "\n" } } if include?(:content) && !self[:content].kind_of?(Array) r << "\n" r << self[:content].to_s end r end def keys super.sort_by { |k| ORDER.index(k) || 99999+k.hash } end def header_keys if self[:content].kind_of? Array keys else keys - [:content] end end def [](key) if !include?(key) && include?(:master) self[:master][key] else resolve super end end def resolve(r) if r =~ /\A@/ raise RuntimeError, "can't resolve #{r}: no database injected" if db.nil? r = db[$'] elsif r.kind_of? Array r.map! { |v| resolve v } end r end def add_inverse inverse.each { |k, vs| self[:"inverse_#{k}"] = vs } self end def inverse(field="*") ref = to_ref inv = {} db.search_each("#{field}:#{ref}") { |i, _| f = db[i] f.find_all { |k, v| case v when String v == ref when Array v.include? ref else false end }.each { |k, v| if inv.include? k if inv[k].kind_of? Array inv[k] << f.to_ref else inv[k] = [inv[k], f.to_ref] end else inv[k] = f.to_ref end } } if field != "*" resolve [inv[field.to_sym] || []].flatten else inv end end def to_ref "@#{self[:id]}" end def next_by(field, n=nil, type=:auto) r = [] rq = Ferret::Search::RangeQuery.new(field, :> => self[field]) db.search_each(rq, :limit => n || 1, :sort => Ferret::Search::Sort.new( [Ferret::Search::SortField.new(field, :type => type)])) { |doc, _| r << db[doc] } n.nil? ? r.first : r end def prev_by(field, n=nil, type=:auto) r = [] rq = Ferret::Search::RangeQuery.new(field, :< => self[field]) db.search_each(rq, :limit => n || 1, :sort => Ferret::Search::Sort.new( [Ferret::Search::SortField.new(field, :type => type, :reverse => true)])) { |doc, _| r << db[doc] } n.nil? ? r.first : r end end end