#!ruby -w # Structured Dashes class SDStack < Array def pushnode( depth, key, val ) node = [ key, val ] self.last[1][1].push node self.push [ depth, node ] end end class PairList < Array def all( key ) self.select { |pair| pair[0] == key }.map{ |pair| pair[1] } end def get_one( key ) if $DEBUG finalval = nil self.each( key ) { |val| raise "More than one #{key}!" if finalval finalval = val } finalval else self.all(key)[0] end end def get_one_str( key ) val = get_one(key) if val.respond_to? :strip val.strip elsif val == nil or val == [] nil else raise "Value is neither string nor nil: #{val}" end end def []( key ) get_one_str(key) end end def sdparse( file ) file = File.open( file ) if file.respond_to? :to_str blank = %r{\A\s*\Z} doc = PairList.new stack = SDStack.new([ [-1, [nil, doc] ] ]) file.each { |line| rawmode = stack.length > 1 && stack[-2][1][0] == 'raw' if line =~ %r{\A (\s*)-\s*(\w+)\s+(.*)}x and not rawmode #and not stack.last[1][0] == 'raw' # this is a dash-lead key/value line depth = $1.length key, val = $2, $3 puts "% 2d- %s (%s)" % [depth, $2, $3] if $DEBUG if val =~ blank val = PairList.new else val += "\n" end stack.pop while stack.last[0] >= depth stack.pushnode( depth, key, val ) else # this is a plaintext line that needs a nice nil-node to go into if line =~ blank # don't let blank lines mess up our 'current depth' val = line else line =~ %r{\A (\s*) (.*)}x depth = $1.length val = $2 puts "% 2d: %s" % [depth, $2] if $DEBUG stack.pop while stack.last[0] > depth end # figure out if there's an old nil-node to add this line to, or if a # fresh one is needed. if stack.last[1][1].respond_to? :pop stack.pop if stack.last[1][1].length > 0 fresh = true elsif not rawmode and depth and stack.last[1][1] =~ %r{ \n\s*\n\s* \Z }x stack.pop fresh = true else fresh = false end # either create a new nil-node, or add this line to and existing node if fresh # no need to create a nil-node yet if it starts with a blank line if depth puts "First para line" if $DEBUG stack.pushnode( depth, key, val + "\n" ) end else puts "Subsequent para line" if $DEBUG val = ' ' * (depth - stack.last[0]) + val + "\n" if depth stack.last[1][1] += val end end } p doc if $DEBUG sddump doc if $DEBUG return doc end def sddump(list, depth=0) list.each { |pair| print ' ' * depth + '-' + (pair[0] or 'nil') if pair[1].respond_to? :pop puts sddump(pair[1], depth + 1) else puts ' ' + pair[1] end } end def esctext( str ) # process text that's not inside an xml-like tag str.gsub(%r{ (.*?) (\s]* ))* \s*/?> | \Z) }mx) { text, tag = $1, ($2 or '') text.gsub!(%r{ ( &[#]?\w+; ) | [<>&] }x) { |char| $1 ? $1 : ('&#%d;' % char[0]) } text + tag } end def richify( str ) str.gsub(%r{ \[ ([-/\\.=]) (.+?) \1\] | "([^"]+?)":([^ "]+) # throwback to redcloth silliness }mx) { if $1 mark, text = $1, $2 params = '' tag = case mark when '/': 'em' when '\\': 'cite' when '-': 'strong' when '.': 'code' when '=': text.gsub! %r{\A\s*(\S+)\s+}, '' href = $1 title = nil if href =~ %r{^isbn:(.*)}i title = %Q{pricescan.com (ISBN #{$1})} href = %Q(http://pricescan.com/books/BookDetail.asp?isbn=#{$1}) end if not title href =~ %r{^\w+://(www\.|ftp\.|)([^/]*(/~[^/]+)?)} title = $2 end params = %Q( href="#{href}" title="#{title}") 'a' end "<#{tag}#{params}>#{richify(text)}" elsif $3 text, href = $3, $4 %Q(#{richify(text)}) else raise "What?" end } end def to_html( pairlist ) pairlist.map { |key, val| key ||= 'p' if key == 'raw' next val.get_one(nil) elsif key == 'pre' val = esctext(val.get_one(nil)).strip elsif val.respond_to? :pop val = to_html(val) else val = esctext(val).strip val = richify(val) end "<#{key}>#{val}\n\n" }.join '' end def gettime( str ) re = %r{ (\d+)-(\d+)-(\d+) T (\d+):(\d+) :(\d+)\.(\d+) ([-+])(\d+):(\d+) }x md = re.match( str ) if md year, month, day, hour, min, sec, usec, tzdir, tzhour, tzmin = md[1..9] Time.local( year.to_i, month.to_i, day.to_i, hour.to_i, min.to_i, sec.to_i, usec.to_i ) else nil end end def lastposttime(doc, fmt) pubDate = doc.all('item').select { |item| item['pubDate'] }[0]['pubDate'] time = gettime(pubDate) return time ? time.strftime(fmt) : pubDate end