card/lib/card/content.rb
# -*- encoding : utf-8 -*-
class Card
# Content objects support the parsing of content strings into arrays that
# contain semantically meaningful "chunks" like nests, links, urls, etc.
#
# Each chunk has an object whose class inherits from {Card::Content::Chunk::Abstract}
#
class Content < SimpleDelegator
extend Clean
Chunk # trigger autoload
attr_reader :revision, :format, :chunks, :opts
# initialization parses String, detects chunks
# @param content [String]
# @param format_or_card [Card::Format or Card]
# @param opts [Hash]
# @option opts [Symbol] :chunk_list - name of registered list of chunk
# classes to be used in parsing
def initialize content, format_or_card, opts={}
@format = resolve_format format_or_card
opts ||= {}
chunk_list = opts[:chunk_list] || @format.chunk_list
@chunks = Parser.new(chunk_list, self).parse(content)
super(@chunks.any? ? @chunks : content)
end
# Content must be associated with a Format object, which in turn must be
# associated with a Card
# @return [Card]
def card
@format.card
end
# Find all chunks of a given type
# @param chunk_type [Chunk Class or Symbol]
# @return [Array of Chunk instances]
def find_chunks chunk_type=nil
chunk_type = interpret_chunk_type chunk_type
each_chunk.select { |chunk| chunk.is_a?(chunk_type) }
end
def has_chunk? chunk_type
each_chunk.any { |chunk| chunk.is_a?(chunk_type) }
end
# sends &block to #process_chunk on each Chunk object
def process_chunks &block
return custom_process_chunks(&block) if block_given?
each_chunk(&:process_chunk)
end
def custom_process_chunks
each_chunk do |chunk|
chunk.burn_after_reading yield(chunk)
end
end
def pieces
Array.wrap(__getobj__)
end
def each_chunk
return enum_for(:each_chunk) unless block_given?
iterator =
case __getobj__
when Hash then :each_value
when Array then :each
when String then return # no chunks
else
Rails.logger.warn "unrecognized type for #each_chunk: " \
" #{self.class} #{__getobj__.class}"
return
end
send(iterator) { |item| yield item if item.is_a?(Chunk::Abstract) }
end
# convert content to String.
# the common cases here are that either
#
# - (a) content is already a String, or
# - (b) it's an Array that needs to be iterated over and converted into a
# a string by running to_s on each item.
def to_s
case __getobj__
when Array then map(&:to_s) * ""
when String then __getobj__
when NilClass then "" # raise "Nil Card::Content"
else __getobj__.to_s
end
end
def inspect
"<#{__getobj__.class}:#{card}:#{self}>"
end
def without_nests &block
without_chunks Chunk::Nest, &block
end
def without_references &block
without_chunks Chunk::Nest, Chunk::Link, Chunk::EscapedLiteral, &block
end
def without_chunks *chunk_classes
chunk_classes = ::Set.new Array.wrap(chunk_classes)
stash = stash_chunks chunk_classes
processed = yield to_s
unstash_chunks processed, stash
end
def strip_nests
strip_chunks Chunk::Nest
end
def strip_chunks chunk_type
chunks.reject! { |c| c.is_a? chunk_type }
end
private
def interpret_chunk_type chunk_type
case chunk_type
when nil
Chunk
when Symbol, String
Chunk.const_get chunk_type
when Class
chunk_type
else
raise ArgumentError, "unknown chunk type: #{chunk_type}"
end
end
def stash_chunks chunk_classes
chunks = []
each_chunk do |chunk|
next unless chunk_classes.include? chunk.class
chunk.burn_after_reading "{{#{chunks.size}}}"
chunks << chunk.text
end
chunks
end
def unstash_chunks content, stashed_chunks
Chunk::Nest.gsub content do |nest_content|
nest_content.number? ? stashed_chunks[nest_content.to_i] : "{{#{nest_content}}}"
end
end
def resolve_format format_or_card
if format_or_card.is_a?(Card)
Format.new format_or_card, format: nil
else
format_or_card
end
end
end
end