pseudohikiparser 0.0.0.11.develop → 0.0.0.12.develop
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/bin/pseudohiki2html.rb +1 -410
- data/lib/pseudohiki/converter.rb +411 -0
- data/lib/pseudohiki/htmlformat.rb +4 -0
- data/lib/pseudohiki/htmlplugin.rb +10 -0
- data/lib/pseudohiki/inlineparser.rb +2 -5
- data/lib/pseudohiki/markdownformat.rb +0 -6
- data/lib/pseudohiki/plaintextformat.rb +25 -15
- data/lib/pseudohiki/sinatra_helpers.rb +23 -0
- data/lib/pseudohiki/version.rb +1 -1
- data/lib/pseudohikiparser.rb +95 -0
- data/test/test_htmlformat.rb +4 -0
- data/test/test_plaintextformat.rb +19 -0
- data/test/test_pseudohiki2html.rb +159 -0
- data/test/test_pseudohikiparser.rb +142 -0
- metadata +8 -2
@@ -0,0 +1,411 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- coding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'erb'
|
6
|
+
require 'pseudohiki/blockparser'
|
7
|
+
require 'pseudohiki/htmlformat'
|
8
|
+
require 'pseudohiki/plaintextformat'
|
9
|
+
require 'pseudohiki/markdownformat'
|
10
|
+
require 'htmlelement/htmltemplate'
|
11
|
+
require 'htmlelement'
|
12
|
+
|
13
|
+
module PseudoHiki
|
14
|
+
class PageComposer
|
15
|
+
HEADING_WITH_ID_PAT = /^(!{2,3})\[([A-Za-z][0-9A-Za-z_\-.:]*)\]\s*/o
|
16
|
+
|
17
|
+
PlainFormat = PlainTextFormat.create
|
18
|
+
|
19
|
+
def initialize(options)
|
20
|
+
@options = options
|
21
|
+
end
|
22
|
+
|
23
|
+
def formatter
|
24
|
+
@formatter ||= @options.html_template.new
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_plain(line)
|
28
|
+
PlainFormat.format(BlockParser.parse(line.lines.to_a)).to_s.chomp
|
29
|
+
end
|
30
|
+
|
31
|
+
def create_plain_table_of_contents(lines)
|
32
|
+
toc_lines = lines.grep(HEADING_WITH_ID_PAT).map do |line|
|
33
|
+
m = HEADING_WITH_ID_PAT.match(line)
|
34
|
+
heading_depth = m[1].length
|
35
|
+
line.sub(/^!+/o, '*'*heading_depth)
|
36
|
+
end
|
37
|
+
|
38
|
+
@options.formatter.format(BlockParser.parse(toc_lines))
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_html_table_of_contents(lines)
|
42
|
+
toc_lines = lines.grep(HEADING_WITH_ID_PAT).map do |line|
|
43
|
+
m = HEADING_WITH_ID_PAT.match(line)
|
44
|
+
heading_depth, id = m[1].length, m[2].upcase
|
45
|
+
"%s[[%s|#%s]]"%['*'*heading_depth, to_plain(line.sub(HEADING_WITH_ID_PAT,'')), id]
|
46
|
+
end
|
47
|
+
@options.formatter.format(BlockParser.parse(toc_lines)).tap do |toc|
|
48
|
+
toc.traverse do |element|
|
49
|
+
if element.kind_of? HtmlElement and element.tagname == "a"
|
50
|
+
element["title"] = "toc_item: " + element.children.join.chomp
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_table_of_contents(lines)
|
57
|
+
return "" unless @options[:toc]
|
58
|
+
return create_plain_table_of_contents(lines) unless @options.html_template
|
59
|
+
create_html_table_of_contents(lines)
|
60
|
+
end
|
61
|
+
|
62
|
+
def split_main_heading(input_lines)
|
63
|
+
return "" unless @options[:split_main_heading]
|
64
|
+
h1_pos = input_lines.find_index {|line| /^![^!]/o =~ line }
|
65
|
+
return "" unless h1_pos
|
66
|
+
tree = BlockParser.parse([input_lines.delete_at(h1_pos)])
|
67
|
+
@options.formatter.format(tree)
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_plain_main(toc, body, h1)
|
71
|
+
contents = [body]
|
72
|
+
contents.unshift toc unless toc.empty?
|
73
|
+
contents.unshift @options.formatter.format(BlockParser.parse("!!" + @options[:toc])) if @options[:toc]
|
74
|
+
contents.unshift h1 unless h1.empty?
|
75
|
+
contents.join($/)
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_html_main(toc, body, h1)
|
79
|
+
return nil unless @options[:toc]
|
80
|
+
toc_container = formatter.create_element("section").tap do |element|
|
81
|
+
element["id"] = "toc"
|
82
|
+
element.push formatter.create_element("h2", @options[:toc]) unless @options[:toc].empty?
|
83
|
+
element.push toc
|
84
|
+
end
|
85
|
+
contents_container = formatter.create_element("section").tap do |element|
|
86
|
+
element["id"] = "contents"
|
87
|
+
element.push body
|
88
|
+
end
|
89
|
+
main = formatter.create_element("section").tap do |element|
|
90
|
+
element["id"] = "main"
|
91
|
+
element.push h1 unless h1.empty?
|
92
|
+
element.push toc_container
|
93
|
+
element.push contents_container
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_main(toc, body, h1)
|
98
|
+
return create_plain_main(toc, body, h1) unless @options.html_template
|
99
|
+
create_html_main(toc, body, h1)
|
100
|
+
end
|
101
|
+
|
102
|
+
def create_style(path_to_css_file)
|
103
|
+
style = formatter.create_element("style").tap do |element|
|
104
|
+
element["type"] = "text/css"
|
105
|
+
open(File.expand_path(path_to_css_file)) do |css_file|
|
106
|
+
element.push css_file.read
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def compose_body(input_lines)
|
112
|
+
tree = BlockParser.parse(input_lines)
|
113
|
+
@options.formatter.format(tree)
|
114
|
+
end
|
115
|
+
|
116
|
+
def compose_html(input_lines)
|
117
|
+
h1 = split_main_heading(input_lines)
|
118
|
+
css = @options[:css]
|
119
|
+
toc = create_table_of_contents(input_lines)
|
120
|
+
body = compose_body(input_lines)
|
121
|
+
title = @options.title
|
122
|
+
main = create_main(toc,body, h1)
|
123
|
+
|
124
|
+
if @options[:template]
|
125
|
+
erb = ERB.new(@options.read_template_file)
|
126
|
+
html = erb.result(binding)
|
127
|
+
else
|
128
|
+
html = @options.create_html_template_with_current_options
|
129
|
+
html.head.push create_style(@options[:embed_css]) if @options[:embed_css]
|
130
|
+
html.push main||body
|
131
|
+
end
|
132
|
+
|
133
|
+
html
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
class OptionManager
|
138
|
+
include HtmlElement::CHARSET
|
139
|
+
|
140
|
+
PlainVerboseFormat = PlainTextFormat.create(:verbose_mode => true)
|
141
|
+
MDFormat = MarkDownFormat.create
|
142
|
+
GFMFormat = MarkDownFormat.create(:gfm_style => true)
|
143
|
+
|
144
|
+
class Formatter < Struct.new(:version, :formatter, :template, :ext, :opt_pat)
|
145
|
+
end
|
146
|
+
|
147
|
+
VERSIONS = [
|
148
|
+
["html4", HtmlFormat, HtmlTemplate, ".html", /^h/io],
|
149
|
+
["xhtml1", XhtmlFormat, XhtmlTemplate, ".html", /^x/io],
|
150
|
+
["html5", Xhtml5Format, Xhtml5Template, ".html", /^h5/io],
|
151
|
+
["plain", PageComposer::PlainFormat, nil, ".plain", /^p/io],
|
152
|
+
["plain_verbose", PlainVerboseFormat, nil, ".plain", /^pv/io],
|
153
|
+
["markdown", MDFormat, nil, ".md", /^m/io],
|
154
|
+
["gfm", GFMFormat, nil, ".md", /^g/io]
|
155
|
+
].map {|args| Formatter.new(*args) }
|
156
|
+
|
157
|
+
ENCODING_REGEXP = {
|
158
|
+
/^u/io => 'utf8',
|
159
|
+
/^e/io => 'euc-jp',
|
160
|
+
/^s/io => 'sjis',
|
161
|
+
/^l[a-zA-Z]*1/io => 'latin1'
|
162
|
+
}
|
163
|
+
|
164
|
+
BOM = "\xef\xbb\xbf"
|
165
|
+
BOM.force_encoding("ASCII-8BIT") if BOM.respond_to? :encoding
|
166
|
+
FILE_HEADER_PAT = /^\/\//
|
167
|
+
|
168
|
+
ENCODING_TO_CHARSET = {
|
169
|
+
'utf8' => UTF8,
|
170
|
+
'euc-jp' => EUC_JP,
|
171
|
+
'sjis' => SJIS,
|
172
|
+
'latin1' => LATIN1
|
173
|
+
}
|
174
|
+
|
175
|
+
attr_accessor :need_output_file, :default_title
|
176
|
+
attr_reader :input_file_basename
|
177
|
+
|
178
|
+
def self.remove_bom(input=ARGF)
|
179
|
+
bom = input.read(3)
|
180
|
+
input.rewind unless BOM == bom
|
181
|
+
end
|
182
|
+
|
183
|
+
def initialize(options=nil)
|
184
|
+
@options = options||{
|
185
|
+
:html_version => VERSIONS[0],
|
186
|
+
:lang => 'en',
|
187
|
+
:encoding => 'utf8',
|
188
|
+
:title => nil,
|
189
|
+
:css => "default.css",
|
190
|
+
:embed_css => nil,
|
191
|
+
:base => nil,
|
192
|
+
:template => nil,
|
193
|
+
:output => nil,
|
194
|
+
:force => false,
|
195
|
+
:toc => nil,
|
196
|
+
:split_main_heading => false
|
197
|
+
}
|
198
|
+
@written_option_pat = {}
|
199
|
+
@options.keys.each {|opt| @written_option_pat[opt] = /^\/\/#{opt}:\s*(.*)$/ }
|
200
|
+
end
|
201
|
+
|
202
|
+
def [](key)
|
203
|
+
@options[key]
|
204
|
+
end
|
205
|
+
|
206
|
+
def[]=(key, value)
|
207
|
+
@options[key] = value
|
208
|
+
end
|
209
|
+
|
210
|
+
def win32?
|
211
|
+
true if RUBY_PLATFORM =~ /win/i
|
212
|
+
end
|
213
|
+
|
214
|
+
def value_given?(value)
|
215
|
+
value and not value.empty?
|
216
|
+
end
|
217
|
+
|
218
|
+
def html_template
|
219
|
+
self[:html_version].template
|
220
|
+
end
|
221
|
+
|
222
|
+
def formatter
|
223
|
+
self[:html_version].formatter
|
224
|
+
end
|
225
|
+
|
226
|
+
def charset
|
227
|
+
ENCODING_TO_CHARSET[self[:encoding]]
|
228
|
+
end
|
229
|
+
|
230
|
+
def base
|
231
|
+
base_dir = self[:base]
|
232
|
+
if base_dir and base_dir !~ /[\/\\]\.*$/o
|
233
|
+
base_dir = File.join(base_dir,".")
|
234
|
+
base_dir = "file:///"+base_dir if base_dir !~ /^\./o and win32?
|
235
|
+
end
|
236
|
+
base_dir
|
237
|
+
end
|
238
|
+
|
239
|
+
def title
|
240
|
+
self[:title]||@default_title||"-"
|
241
|
+
end
|
242
|
+
|
243
|
+
def read_template_file
|
244
|
+
File.read(File.expand_path(self[:template]), :encoding => self.charset)
|
245
|
+
end
|
246
|
+
|
247
|
+
def set_html_version(version)
|
248
|
+
VERSIONS.each do |v|
|
249
|
+
if v.version == version
|
250
|
+
return self[:html_version] = v
|
251
|
+
else
|
252
|
+
self[:html_version] = v if v.opt_pat =~ version
|
253
|
+
end
|
254
|
+
end
|
255
|
+
STDERR.puts "\"#{version}\" is an invalid option for --format-version. \"#{self[:html_version].version}\" is chosen instead."
|
256
|
+
end
|
257
|
+
|
258
|
+
def set_html_encoding(given_opt)
|
259
|
+
if ENCODING_REGEXP.values.include? given_opt
|
260
|
+
self[:encoding] = given_opt
|
261
|
+
else
|
262
|
+
ENCODING_REGEXP.each do |pat, encoding|
|
263
|
+
self[:encoding] = encoding if pat =~ given_opt
|
264
|
+
end
|
265
|
+
STDERR.puts "\"#{self[:encoding]}\" is chosen as an encoding system, instead of \"#{given_opt}\"."
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
def set_encoding(given_opt)
|
270
|
+
return nil unless String.new.respond_to? :encoding
|
271
|
+
external, internal = given_opt.split(/:/o)
|
272
|
+
Encoding.default_external = external if external
|
273
|
+
Encoding.default_internal = internal if internal
|
274
|
+
end
|
275
|
+
|
276
|
+
def parse_command_line_options
|
277
|
+
OptionParser.new("** Convert texts written in a Hiki-like notation into HTML **
|
278
|
+
USAGE: #{File.basename(__FILE__)} [options]") do |opt|
|
279
|
+
opt.on("-f [html_version]", "--format-version [=format_version]",
|
280
|
+
"HTML version to be used. Choose html4, xhtml1, html5, plain, plain_verbose, markdown or gfm (default: #{self[:html_version].version})") do |version|
|
281
|
+
self.set_html_version(version)
|
282
|
+
end
|
283
|
+
|
284
|
+
opt.on("-l [lang]", "--lang [=lang]",
|
285
|
+
"Set the value of charset attributes (default: #{self[:lang]})") do |lang|
|
286
|
+
self[:lang] = lang if value_given?(lang)
|
287
|
+
end
|
288
|
+
|
289
|
+
opt.on("-e [encoding]", "--format-encoding [=encoding]",
|
290
|
+
"Available options: utf8, euc-jp, sjis, latin1 (default: #{self[:encoding]})") do |given_opt|
|
291
|
+
self.set_html_encoding(given_opt)
|
292
|
+
end
|
293
|
+
|
294
|
+
opt.on("-E [ex[:in]]", "--encoding [=ex[:in]]",
|
295
|
+
"Specify the default external and internal character encodings (same as the option of MRI") do |given_opt|
|
296
|
+
self.set_encoding(given_opt)
|
297
|
+
end
|
298
|
+
|
299
|
+
#use '-w' to avoid the conflict with the short option for '[-t]emplate'
|
300
|
+
opt.on("-w [(window) title]", "--title [=title]",
|
301
|
+
"Set the value of the <title> element (default: the basename of the input file)") do |title|
|
302
|
+
self[:title] = title if value_given?(title)
|
303
|
+
end
|
304
|
+
|
305
|
+
opt.on("-c [css]", "--css [=css]",
|
306
|
+
"Set the path to a css file to be used (default: #{self[:css]})") do |css|
|
307
|
+
self[:css] = css
|
308
|
+
end
|
309
|
+
|
310
|
+
opt.on("-C [path_to_css_file]", "--embed-css [=path_to_css_file]",
|
311
|
+
"Set the path to a css file to embed (default: not to embed)") do |path_to_css_file|
|
312
|
+
self[:embed_css] = path_to_css_file
|
313
|
+
end
|
314
|
+
|
315
|
+
opt.on("-b [base]", "--base [=base]",
|
316
|
+
"Specify the value of href attribute of the <base> element (default: not specified)") do |base_dir|
|
317
|
+
self[:base] = base_dir if value_given?(base_dir)
|
318
|
+
end
|
319
|
+
|
320
|
+
opt.on("-t [template]", "--template [=template]",
|
321
|
+
"Specify a template file written in eruby format with \"<%= body %>\" inside (default: not specified)") do |template|
|
322
|
+
self[:template] = template if value_given?(template)
|
323
|
+
end
|
324
|
+
|
325
|
+
opt.on("-o [output]", "--output [=output]",
|
326
|
+
"Output to the specified file. If no file is given, \"[input_file_basename].html\" will be used.(default: STDOUT)") do |output|
|
327
|
+
self[:output] = File.expand_path(output) if value_given?(output)
|
328
|
+
self.need_output_file = true
|
329
|
+
end
|
330
|
+
|
331
|
+
opt.on("-F", "--force",
|
332
|
+
"Force to apply command line options.(default: false)") do |force|
|
333
|
+
self[:force] = force
|
334
|
+
end
|
335
|
+
|
336
|
+
opt.on("-m [contents-title]", "--table-of-contents [=contents-title]",
|
337
|
+
"Include the list of h2 and/or h3 headings with ids.(default: nil)") do |toc_title|
|
338
|
+
self[:toc] = toc_title
|
339
|
+
end
|
340
|
+
|
341
|
+
opt.on("-s", "--split-main-heading",
|
342
|
+
"Split the first h1 element") do |should_be_split|
|
343
|
+
self[:split_main_heading] = should_be_split
|
344
|
+
end
|
345
|
+
|
346
|
+
opt.parse!
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def check_argv
|
351
|
+
case ARGV.length
|
352
|
+
when 0
|
353
|
+
if self.need_output_file and not self[:output]
|
354
|
+
raise "You must specify a file name for output"
|
355
|
+
end
|
356
|
+
when 1
|
357
|
+
self.read_input_filename(ARGV[0])
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
def set_options_from_command_line
|
362
|
+
parse_command_line_options
|
363
|
+
check_argv
|
364
|
+
@default_title = @input_file_basename
|
365
|
+
end
|
366
|
+
|
367
|
+
def set_options_from_input_file(input_lines)
|
368
|
+
input_lines.each do |line|
|
369
|
+
break if FILE_HEADER_PAT !~ line
|
370
|
+
line = line.chomp
|
371
|
+
@options.keys.each do |opt|
|
372
|
+
next if self[opt] and self[:force]
|
373
|
+
self[opt] = $1 if @written_option_pat[opt] =~ line
|
374
|
+
end
|
375
|
+
end
|
376
|
+
end
|
377
|
+
|
378
|
+
def create_html_template_with_current_options
|
379
|
+
return [] unless self.html_template
|
380
|
+
html = self.html_template.new
|
381
|
+
html.charset = self.charset
|
382
|
+
html.language = self[:lang]
|
383
|
+
html.default_css = self[:css] if self[:css]
|
384
|
+
html.base = self.base if self[:base]
|
385
|
+
html.title = self.title
|
386
|
+
html
|
387
|
+
end
|
388
|
+
|
389
|
+
def read_input_filename(filename)
|
390
|
+
@input_file_dir, @input_file_name = File.split(File.expand_path(filename))
|
391
|
+
@input_file_basename = File.basename(@input_file_name,".*")
|
392
|
+
end
|
393
|
+
|
394
|
+
def output_filename
|
395
|
+
return nil unless self.need_output_file
|
396
|
+
if self[:output]
|
397
|
+
File.expand_path(self[:output])
|
398
|
+
else
|
399
|
+
File.join(@input_file_dir, @input_file_basename + self[:html_version].ext)
|
400
|
+
end
|
401
|
+
end
|
402
|
+
|
403
|
+
def open_output
|
404
|
+
if self.output_filename
|
405
|
+
open(self.output_filename, "w") {|f| yield f }
|
406
|
+
else
|
407
|
+
yield STDOUT
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
@@ -14,6 +14,16 @@ module PseudoHiki
|
|
14
14
|
end
|
15
15
|
end
|
16
16
|
|
17
|
+
class XhtmlFormat < HtmlFormat
|
18
|
+
Formatter = HtmlFormat::Formatter.dup
|
19
|
+
setup_new_formatter(Formatter, XhtmlElement)
|
20
|
+
end
|
21
|
+
|
22
|
+
class Xhtml5Format < XhtmlFormat
|
23
|
+
Formatter = HtmlFormat::Formatter.dup
|
24
|
+
setup_new_formatter(Formatter, Xhtml5Element)
|
25
|
+
end
|
26
|
+
|
17
27
|
class HtmlPlugin
|
18
28
|
|
19
29
|
PLUGIN_PAT = /^(\w+)([\s\(]+)/
|
@@ -126,13 +126,10 @@ module PseudoHiki
|
|
126
126
|
def parse_cellspan(token_str)
|
127
127
|
return token_str if m = MODIFIED_CELL_PAT.match(token_str) and m[0].empty? #if token.kind_of? String
|
128
128
|
cell_modifiers = m[0]
|
129
|
-
if cell_modifiers[0].chr == TH_PAT
|
130
|
-
cell_modifiers[0] = ""
|
131
|
-
@cell_type = TH
|
132
|
-
end
|
129
|
+
@cell_type = TH if cell_modifiers[0].chr == TH_PAT
|
133
130
|
@rowspan = cell_modifiers.count(ROW_EXPANDER) + 1
|
134
131
|
@colspan = cell_modifiers.count(COL_EXPANDER) + 1
|
135
|
-
|
132
|
+
m.post_match
|
136
133
|
end
|
137
134
|
|
138
135
|
def parse_first_token(orig_tokens)
|
@@ -11,10 +11,22 @@ module PseudoHiki
|
|
11
11
|
|
12
12
|
DescSep = [InlineParser::DescSep]
|
13
13
|
|
14
|
+
Formatters = {}
|
15
|
+
|
14
16
|
class Node < Array
|
15
17
|
alias to_s join
|
16
18
|
end
|
17
19
|
|
20
|
+
def self.format(tree, options={ :verbose_mode => false })
|
21
|
+
if Formatters.empty?
|
22
|
+
default_options = { :verbose_mode => false }
|
23
|
+
Formatters[default_options] = create(default_options)
|
24
|
+
end
|
25
|
+
|
26
|
+
Formatters[options] ||= create(options)
|
27
|
+
Formatters[options].format(tree)
|
28
|
+
end
|
29
|
+
|
18
30
|
def initialize(formatter={}, options = { :verbose_mode=> false })
|
19
31
|
@formatter = formatter
|
20
32
|
options_given_via_block = nil
|
@@ -152,19 +164,17 @@ ERROR_TEXT
|
|
152
164
|
max_col = tree.map{|row| row.reduce(0) {|sum, cell| sum + cell.colspan }}.max - 1
|
153
165
|
max_row = rows.length - 1
|
154
166
|
cur_row = nil
|
155
|
-
|
167
|
+
each_cell_index(max_row, max_col) do |r, c|
|
156
168
|
cur_row = rows.shift if c == 0
|
157
169
|
next if table[r][c]
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
next
|
167
|
-
end
|
170
|
+
begin
|
171
|
+
raise MalFormedTableError.new(ERROR_MESSAGE%[table[r].inspect]) if cur_row.empty?
|
172
|
+
table[r][c] = cur_row.shift
|
173
|
+
fill_expand(table, r, c, table[r][c])
|
174
|
+
rescue
|
175
|
+
raise if @options.strict_mode
|
176
|
+
STDERR.puts ERROR_MESSAGE%[table[r].inspect]
|
177
|
+
next
|
168
178
|
end
|
169
179
|
end
|
170
180
|
format_table(table, tree)
|
@@ -176,10 +186,10 @@ ERROR_TEXT
|
|
176
186
|
end
|
177
187
|
end
|
178
188
|
|
179
|
-
def
|
189
|
+
def each_cell_index(max_row, max_col, initial_row=0, initial_col=0)
|
180
190
|
initial_row.upto(max_row) do |r|
|
181
191
|
initial_col.upto(max_col) do |c|
|
182
|
-
yield
|
192
|
+
yield r, c
|
183
193
|
end
|
184
194
|
end
|
185
195
|
end
|
@@ -188,8 +198,8 @@ ERROR_TEXT
|
|
188
198
|
row_expand, col_expand = choose_expander_of_col_and_row
|
189
199
|
max_row = initial_row + cur_cell.rowspan - 1
|
190
200
|
max_col = initial_col + cur_cell.colspan - 1
|
191
|
-
|
192
|
-
|
201
|
+
each_cell_index(max_row, max_col,
|
202
|
+
initial_row, initial_col) do |r, c|
|
193
203
|
if initial_row == r and initial_col == c
|
194
204
|
table[r][c] = visited_result(cur_cell).join.lstrip.chomp
|
195
205
|
next
|
@@ -0,0 +1,23 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
begin
|
4
|
+
module Sinatra
|
5
|
+
module PseudoHikiParserHelpers
|
6
|
+
XHTML5_CONTENT_TYPE = 'application/xhtml+xml'
|
7
|
+
def phiki(hiki_data, &block)
|
8
|
+
case content_type
|
9
|
+
when XHTML5_CONTENT_TYPE
|
10
|
+
PseudoHiki::Format.to_html5(hiki_data, &block)
|
11
|
+
else
|
12
|
+
PseudoHiki::Format.to_xhtml(hiki_data, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class Base
|
18
|
+
helpers PseudoHikiParserHelpers
|
19
|
+
end
|
20
|
+
end
|
21
|
+
rescue
|
22
|
+
#Sinatra is not available
|
23
|
+
end
|
data/lib/pseudohiki/version.rb
CHANGED
data/lib/pseudohikiparser.rb
CHANGED
@@ -2,5 +2,100 @@
|
|
2
2
|
|
3
3
|
require "pseudohiki/htmlformat"
|
4
4
|
require "pseudohiki/plaintextformat"
|
5
|
+
require "pseudohiki/markdownformat"
|
5
6
|
require "pseudohiki/version"
|
6
7
|
|
8
|
+
# = PseudoHikiParser -- A converter of texts written in a Hiki-like notation into HTML or other formats.
|
9
|
+
#
|
10
|
+
# You may find more detailed information at {PseudoHikiParser Wiki}[https://github.com/nico-hn/PseudoHikiParser/wiki]
|
11
|
+
#
|
12
|
+
module PseudoHiki
|
13
|
+
# This class provides class methods for converting texts written in a Hiki-like notation into HTML or other formats.
|
14
|
+
#
|
15
|
+
class Format
|
16
|
+
Formatter = {} # :nodoc:
|
17
|
+
PRESET_OPTIONS = {} # :nodoc:
|
18
|
+
TYPE_TO_FORMATTER = {} # :nodoc:
|
19
|
+
|
20
|
+
[
|
21
|
+
[:html, HtmlFormat, nil],
|
22
|
+
[:xhtml, XhtmlFormat, nil],
|
23
|
+
[:html5, Xhtml5Format, nil],
|
24
|
+
[:plain, PlainTextFormat, {:verbose_mode => false }],
|
25
|
+
[:plain_verbose, PlainTextFormat, {:verbose_mode => true }],
|
26
|
+
[:markdown, MarkDownFormat, { :strict_mode=> false, :gfm_style => false }],
|
27
|
+
[:gfm, MarkDownFormat, { :strict_mode=> false, :gfm_style => true }]
|
28
|
+
].each do |type, formatter, options|
|
29
|
+
preset_options = [type, nil]
|
30
|
+
Formatter[preset_options] = formatter.create(options)
|
31
|
+
PRESET_OPTIONS[type] = preset_options
|
32
|
+
TYPE_TO_FORMATTER[type] = formatter
|
33
|
+
end
|
34
|
+
|
35
|
+
class << self
|
36
|
+
# Converts <hiki_data> into a format specified by <format_type>
|
37
|
+
#
|
38
|
+
# <hiki_data> should be a string or an array of strings
|
39
|
+
#
|
40
|
+
# Options for <format_type> are:
|
41
|
+
# [:html] HTML4.1
|
42
|
+
# [:xhtml] XHTML1.0
|
43
|
+
# [:html5] HTML5
|
44
|
+
# [:plain] remove all of tags
|
45
|
+
# [:plain_verbose] similar to :plain, but certain information such as urls in link tags will be kept
|
46
|
+
# [:markdown] Markdown
|
47
|
+
# [:gfm] GitHub Flavored Markdown
|
48
|
+
#
|
49
|
+
def format(hiki_data, format_type, options=nil, &block)
|
50
|
+
tree = BlockParser.parse(hiki_data)
|
51
|
+
|
52
|
+
if options
|
53
|
+
Formatter[[format_type, options]] ||= TYPE_TO_FORMATTER[format_type].create(options)
|
54
|
+
else
|
55
|
+
Formatter[PRESET_OPTIONS[format_type]]
|
56
|
+
end.format(tree).tap do |formatted|
|
57
|
+
block.call(formatted) if block
|
58
|
+
end.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# Converts <hiki_data> into HTML4.1
|
62
|
+
#
|
63
|
+
#
|
64
|
+
def to_html(hiki_data, &block)
|
65
|
+
format(hiki_data, :html, options=nil, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
# Converts <hiki_data> into XHTML1.0
|
69
|
+
#
|
70
|
+
def to_xhtml(hiki_data, &block)
|
71
|
+
format(hiki_data, :xhtml, options=nil, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Converts <hiki_data> into HTML5
|
75
|
+
#
|
76
|
+
def to_html5(hiki_data, &block)
|
77
|
+
format(hiki_data, :html5, options=nil, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Converts <hiki_data> into plain texts without tags
|
81
|
+
#
|
82
|
+
def to_plain(hiki_data, &block)
|
83
|
+
format(hiki_data, :plain, options=nil, &block)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Converts <hiki_data> into Markdown
|
87
|
+
#
|
88
|
+
def to_markdown(hiki_data, &block)
|
89
|
+
format(hiki_data, :markdown, options=nil, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
# Converts <hiki_data> into GitHub Flavored Markdown
|
93
|
+
#
|
94
|
+
def to_gfm(hiki_data, &block)
|
95
|
+
format(hiki_data, :gfm, options=nil, &block)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
require 'pseudohiki/sinatra_helpers'if defined? Sinatra
|