require "forwardable" require "rabbit/element" require 'rabbit/parser/pause-support' require 'rabbit/parser/ext/escape' require 'rabbit/parser/rd/visitor' require "rabbit/parser/rd/ext/refer" require "rabbit/parser/rd/ext/inline-verbatim" require "rabbit/parser/rd/ext/block-verbatim" module Rabbit module Parser class RD class RD2RabbitVisitor < Visitor extend Forwardable include ::RD::MethodParse include Element include PauseSupport SYSTEM_NAME = "RD2RabbitLVisitor" SYSTEM_VERSION = "0.0.2" VERSION = ::RD::Version.new_from_version_string(SYSTEM_NAME, SYSTEM_VERSION) # must-have constants OUTPUT_SUFFIX = "" INCLUDE_SUFFIX = ["rabbit", "rb"] EXTENSIONS = { "refer" => Ext::Refer, "inline_verbatim" => Ext::InlineVerbatim, "block_verbatim" => Ext::BlockVerbatim, } def_delegators(:@canvas, :logger, :full_path, :tmp_dir_name) attr_reader :canvas def initialize(canvas) @canvas = canvas @slides = [] @slide = nil @slide_property_mode = false @index = {} init_extensions super() end def visit(tree) prepare_labels(tree, "label-") prepare_footnotes(tree) super(tree) end def apply_to_DocumentElement(element, contents) target = nil mode = :ignore contents.each do |content| case content when :no_element next when nil mode = :ignore when Slide target = content.body @canvas << content mode = :display when TitleSlide target = content @canvas << content mode = :display when SlidePropertySetter target = content mode = :property else case mode when :display target << content when :property target.apply(content) end end end burn_out_pause_targets burn_out_foot_texts end def apply_to_Headline(element, title) anchor = get_anchor(element) slide, @slide = @slide, nil case element.level when 1 if @slides.empty? @slide = TitleSlide.new(Title.new(title)) else @slide = Slide.new(HeadLine.new(title)) @slide << Body.new end @foot_texts << [] @slides << @slide @slide when 2 SlidePropertySetter.new(slide) else nil end end def apply_to_Include(element) paths = element.tree.include_paths fname = search_file(element.filename, paths, @include_suffix) if fname File.open(fname) do |f| instance_eval(f.read, fname, 0) end end end def apply_to_TextBlock(element, content) paragraph = Paragraph.new(content) register_pause(paragraph) if paragraph.have_wait_tag? paragraph end def apply_to_Verbatim(element) content = [] element.each_line do |line| content << line end content_str = content.join("") /\A#\s*([^\n]+)(?:\n)?(?m:(.*)?)\z/ =~ content_str apply_to_extension("block_verbatim", $1, $2.to_s, content_str) end def apply_to_ItemList(element, items) apply_to_List(element, items, ItemList) end def apply_to_EnumList(element, items) i = 1 apply_to_List(element, items, EnumList) do |list, item| item.order = i i += 1 end end def apply_to_DescList(element, items) apply_to_List(element, items, DescriptionList) end def apply_to_MethodList(element, items) apply_to_List(element, items, MethodList) end def apply_to_List(element, items, klass) list = klass.new() items.each do |item| list << item if block_given? yield(list, item) end end list end def apply_to_ItemListItem(element, content) apply_to_ListItem(element, content, ItemListItem.new) end def apply_to_EnumListItem(element, content) apply_to_ListItem(element, content, EnumListItem.new) end def apply_to_DescListItem(element, term, description) desc_term = DescriptionTerm.new(Paragraph.new(term)) desc_content = DescriptionContent.new apply_to_ListItem(element, description, desc_content) DescriptionListItem.new(desc_term, desc_content) end def apply_to_MethodListItem(element, term, description) method_term = parse_method(term) method_description = MethodDescription.new apply_to_ListItem(element, description, method_description) MethodListItem.new(method_term, method_description) end def apply_to_ListItem(element, contents, item) contents.each do |content| item << content end waited_paragraphs = item.elements.find_all do |element| element.is_a?(Paragraph) and element.have_wait_tag? end unless waited_paragraphs.empty? waited_paragraphs.each do |paragraph| paragraph.default_visible = true paragraph.clear_theme unregister_pause(paragraph) end item.default_visible = false item.clear_theme register_pause(item) end item end def apply_to_StringElement(element) content = element.content.gsub(/\n\s*/, '') Text.new(apply_to_String(content)) end def apply_to_Emphasis(element, content) Emphasis.new(content) end def apply_to_Code(element, content) Code.new(content) end def apply_to_Var(element, content) Variable.new(content) end def apply_to_Keyboard(element, content) Keyboard.new(content) end def apply_to_Index(element, content) Index.new(content) end def apply_to_Reference_with_RDLabel(element, content) source = content.collect{|elem| elem.text} apply_to_extension("refer", element.label, source, content) end def apply_to_Reference_with_URL(element, content) ref = ReferText.new(content) ref.to = element.label.url ref end def apply_to_Footnote(element, content) if @slide.nil? Text.new("") else num = get_footnote_num(element) unless num raise ArgumentError, "[BUG?] #{element} is not registered." end add_foot_text(num, content) Footnote.new(num) end end def apply_to_Foottext(element, content) num = get_footnote_num(element) raise ArgumentError, "[BUG] #{element} isn't registered." unless num FootText.new(num, content) end def apply_to_Verb(element) source = apply_to_String(element.content) content = element.content apply_to_extension("inline_verbatim", element.to_label, source, content) end def apply_to_String(str) Parser::Ext::Escape.escape_meta_character(str) end def create_have_text_element(klass, content) raise "Why???" if content.size > 1 klass.new(content.collect{|x| x.text}.join("")) end def current_body @slide.body end private def prepare_footnotes(tree) @footnotes = tree.find_all{|i| i.is_a? ::RD::Footnote} @foot_texts = [] end def get_footnote_num(fn) unless fn.is_a?(::RD::Footnote) raise ArgumentError, "#{fn} must be Footnote." end i = @footnotes.index(fn) if i i + 1 else nil end end def parse_method(method) klass, kind, method, args = ::RD::MethodParse.analize_method(method) term = MethodTerm.new if kind == :function klass = kind = nil else kind = ::RD::MethodParse.kind2str(kind) end case method when "self" klass, kind, method, args = ::RD::MethodParse.analize_method(args) term = make_pre_inited_method_term(klass, kind) term << MethodName.new(Text.new("self")) add_args_to_method_term(term, args) term when "[]" args.strip! args.sub!(/^\((.*)\)$/, '\\1') args.split(/,/) term = make_pre_inited_method_term(klass, kind) term << MethodName.new(Text.new("[")) args.each do |arg| term << Variable.new(Text.new(arg)) end term << MethodName.new(Text.new("] = ")) term when "[]=" args.tr!(' ', '') args.sub!(/^\((.*)\)$/, '\\1') ary = args.split(/,/) case ary.length when 1 args = [] val = 'val' when 2 args, val = *ary args = [Variable.new(Text.new(args))] when 3 args = ary[0, 2].inject([]) do |result, x| result + [Variable.new(Text.new(x)), Code.new(Text.new(", "))] end args.pop # removed too much ", " val = ary[2] end term = make_pre_inited_method_term(klass, kind) term << MethodName.new(Text.new("[")) args.each do |arg| term << arg end term << MethodName.new(Text.new("] = ")) term << Variable.new(Text.new(val)) term else term = make_pre_inited_method_term(klass, kind) term << MethodName.new(Text.new(method)) term.name = method add_args_to_method_term(term, args) term end end def make_pre_inited_method_term(klass, kind) term = MethodTerm.new term << ClassName.new(Text.new(klass)) if klass term << MethodKind.new(Text.new(kind)) if kind term end def add_args_to_method_term(term, args) first = true prev_index = 0 args.scan(/(?:&#?)?\w+;?/) do |m| code = $PREMATCH[prev_index..-1] term << Code.new(Text.new(code)) unless code.empty? prev_index = $PREMATCH.size + m.size if /(?:&#?)\w+;/ =~ m term << Code.new(Text.new(m)) else term << Variable.new(Text.new(m)) end end term << Code.new(Text.new($POSTMATCH)) if $POSTMATCH term end def add_foot_text(num, foot_text) unless @footnotes[num - 1] raise ArgumentError, "[BUG] footnote ##{num} isn't here." end @foot_texts.last << [foot_text, num - 1] end def burn_out_foot_texts @slides.each do |slide| ftb = FootTextBlock.new current_foot_texts = @foot_texts.shift while ft_info = current_foot_texts.shift ft, num = ft_info ftb << apply_to_Foottext(@footnotes[num], ft) end slide << ftb end end class SlidePropertySetter def initialize(slide) @slide = slide end def apply(element) return unless element.is_a?(Element::DescriptionList) element.each do |item| name = Parser.normalize_property_name(item.term.text) @slide[name] = item.content.text.strip end end end end end end end