1## 2# Outputs RDoc markup as paragraphs with inline markup only. 3 4class RDoc::Markup::ToHtmlSnippet < RDoc::Markup::ToHtml 5 6 ## 7 # After this many characters the input will be cut off. 8 9 attr_reader :character_limit 10 11 ## 12 # The number of characters seen so far. 13 14 attr_reader :characters # :nodoc: 15 16 ## 17 # The attribute bitmask 18 19 attr_reader :mask 20 21 ## 22 # After this many paragraphs the input will be cut off. 23 24 attr_reader :paragraph_limit 25 26 ## 27 # Count of paragraphs found 28 29 attr_reader :paragraphs 30 31 ## 32 # Creates a new ToHtmlSnippet formatter that will cut off the input on the 33 # next word boundary after the given number of +characters+ or +paragraphs+ 34 # of text have been encountered. 35 36 def initialize options, characters = 100, paragraphs = 3, markup = nil 37 super options, markup 38 39 @character_limit = characters 40 @paragraph_limit = paragraphs 41 42 @characters = 0 43 @mask = 0 44 @paragraphs = 0 45 46 @markup.add_special RDoc::CrossReference::CROSSREF_REGEXP, :CROSSREF 47 end 48 49 ## 50 # Adds +heading+ to the output as a paragraph 51 52 def accept_heading heading 53 @res << "<p>#{to_html heading.text}\n" 54 55 add_paragraph 56 end 57 58 ## 59 # Raw sections are untrusted and ignored 60 61 alias accept_raw ignore 62 63 ## 64 # Rules are ignored 65 66 alias accept_rule ignore 67 68 def accept_paragraph paragraph 69 para = @in_list_entry.last || "<p>" 70 71 text = paragraph.text @hard_break 72 73 @res << "#{para}#{wrap to_html text}\n" 74 75 add_paragraph 76 end 77 78 ## 79 # Finishes consumption of +list_item+ 80 81 def accept_list_item_end list_item 82 end 83 84 ## 85 # Prepares the visitor for consuming +list_item+ 86 87 def accept_list_item_start list_item 88 @res << list_item_start(list_item, @list.last) 89 end 90 91 ## 92 # Prepares the visitor for consuming +list+ 93 94 def accept_list_start list 95 @list << list.type 96 @res << html_list_name(list.type, true) 97 @in_list_entry.push '' 98 end 99 100 ## 101 # Adds +verbatim+ to the output 102 103 def accept_verbatim verbatim 104 throw :done if @characters >= @character_limit 105 input = verbatim.text.rstrip 106 107 text = truncate input 108 text << ' ...' unless text == input 109 110 super RDoc::Markup::Verbatim.new text 111 112 add_paragraph 113 end 114 115 ## 116 # Prepares the visitor for HTML snippet generation 117 118 def start_accepting 119 super 120 121 @characters = 0 122 end 123 124 ## 125 # Removes escaping from the cross-references in +special+ 126 127 def handle_special_CROSSREF special 128 special.text.sub(/\A\\/, '') 129 end 130 131 ## 132 # +special+ is a <code><br></code> 133 134 def handle_special_HARD_BREAK special 135 @characters -= 4 136 '<br>' 137 end 138 139 ## 140 # Lists are paragraphs, but notes and labels have a separator 141 142 def list_item_start list_item, list_type 143 throw :done if @characters >= @character_limit 144 145 case list_type 146 when :BULLET, :LALPHA, :NUMBER, :UALPHA then 147 "<p>" 148 when :LABEL, :NOTE then 149 labels = Array(list_item.label).map do |label| 150 to_html label 151 end.join ', ' 152 153 labels << " — " unless labels.empty? 154 155 start = "<p>#{labels}" 156 @characters += 1 # try to include the label 157 start 158 else 159 raise RDoc::Error, "Invalid list type: #{list_type.inspect}" 160 end 161 end 162 163 ## 164 # Returns just the text of +link+, +url+ is only used to determine the link 165 # type. 166 167 def gen_url url, text 168 if url =~ /^rdoc-label:([^:]*)(?::(.*))?/ then 169 type = "link" 170 elsif url =~ /([A-Za-z]+):(.*)/ then 171 type = $1 172 else 173 type = "http" 174 end 175 176 if (type == "http" or type == "https" or type == "link") and 177 url =~ /\.(gif|png|jpg|jpeg|bmp)$/ then 178 '' 179 else 180 text.sub(%r%^#{type}:/*%, '') 181 end 182 end 183 184 ## 185 # In snippets, there are no lists 186 187 def html_list_name list_type, open_tag 188 '' 189 end 190 191 ## 192 # Throws +:done+ when paragraph_limit paragraphs have been encountered 193 194 def add_paragraph 195 @paragraphs += 1 196 197 throw :done if @paragraphs >= @paragraph_limit 198 end 199 200 ## 201 # Marks up +content+ 202 203 def convert content 204 catch :done do 205 return super 206 end 207 208 end_accepting 209 end 210 211 ## 212 # Converts flow items +flow+ 213 214 def convert_flow flow 215 throw :done if @characters >= @character_limit 216 217 res = [] 218 @mask = 0 219 220 flow.each do |item| 221 case item 222 when RDoc::Markup::AttrChanger then 223 off_tags res, item 224 on_tags res, item 225 when String then 226 text = convert_string item 227 res << truncate(text) 228 when RDoc::Markup::Special then 229 text = convert_special item 230 res << truncate(text) 231 else 232 raise "Unknown flow element: #{item.inspect}" 233 end 234 235 if @characters >= @character_limit then 236 off_tags res, RDoc::Markup::AttrChanger.new(0, @mask) 237 break 238 end 239 end 240 241 res << ' ...' if @characters >= @character_limit 242 243 res.join 244 end 245 246 ## 247 # Maintains a bitmask to allow HTML elements to be closed properly. See 248 # RDoc::Markup::Formatter. 249 250 def on_tags res, item 251 @mask ^= item.turn_on 252 253 super 254 end 255 256 ## 257 # Maintains a bitmask to allow HTML elements to be closed properly. See 258 # RDoc::Markup::Formatter. 259 260 def off_tags res, item 261 @mask ^= item.turn_off 262 263 super 264 end 265 266 ## 267 # Truncates +text+ at the end of the first word after the character_limit. 268 269 def truncate text 270 length = text.length 271 characters = @characters 272 @characters += length 273 274 return text if @characters < @character_limit 275 276 remaining = @character_limit - characters 277 278 text =~ /\A(.{#{remaining},}?)(\s|$)/m # TODO word-break instead of \s? 279 280 $1 281 end 282 283end 284 285