pseudohikiparser 0.0.2 → 0.0.3.develop
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.ja.md +1 -1
- data/lib/pseudohiki/blockparser.rb +51 -18
- data/lib/pseudohiki/converter.rb +16 -3
- data/lib/pseudohiki/htmlformat.rb +27 -2
- data/lib/pseudohiki/inlineparser.rb +13 -14
- data/lib/pseudohiki/markdownformat.rb +52 -7
- data/lib/pseudohiki/plaintextformat.rb +12 -6
- data/lib/pseudohiki/sinatra_helpers.rb +23 -0
- data/lib/pseudohiki/utils.rb +34 -0
- data/lib/pseudohiki/version.rb +1 -1
- data/lib/pseudohikiparser.rb +2 -0
- data/test/test_blockparser.rb +29 -0
- data/test/test_htmlformat.rb +63 -0
- data/test/test_inlineparser.rb +7 -4
- data/test/test_markdownformat.rb +136 -0
- data/test/test_pseudohiki2html.rb +13 -13
- data/test/test_pseudohikiparser.rb +1 -1
- metadata +7 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4194b79f711194dc48b8b09a13302ec50dd9f20d
|
4
|
+
data.tar.gz: 1b91b097c191eb63711aae1f62bf92788943310e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7bc7d683db890fc147c1149480481af8df86ac9738e39958f251c9aee2eb5a6792ac2fe3e6465b7af798d31f1eccedda273a47ecba99608eda344772eb04548b
|
7
|
+
data.tar.gz: a0b647bc3512f1ca38a3fadec0a2a77560c7d7b457a564a49cc40b901f00754d6eaa82c37c7f0a01e8234d341252d5e4ee9502adfc53f0a024feabad60bbcbf2
|
data/README.ja.md
CHANGED
@@ -37,18 +37,17 @@ module PseudoHiki
|
|
37
37
|
end
|
38
38
|
|
39
39
|
class BlockStack < TreeStack
|
40
|
-
def
|
41
|
-
self.current_node.parse_leafs
|
42
|
-
|
40
|
+
def pop_with_breaker(breaker=nil)
|
41
|
+
self.current_node.parse_leafs(breaker)
|
42
|
+
pop
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
class BlockLeaf < BlockStack::Leaf
|
47
|
-
|
48
|
-
attr_accessor :nominal_level, :node_id
|
47
|
+
attr_accessor :nominal_level, :node_id, :decorator
|
49
48
|
|
50
49
|
def self.head_re=(head_regex)
|
51
|
-
@self_head_re =
|
50
|
+
@self_head_re = head_regex
|
52
51
|
end
|
53
52
|
|
54
53
|
def self.head_re
|
@@ -65,7 +64,7 @@ module PseudoHiki
|
|
65
64
|
end
|
66
65
|
|
67
66
|
def head_re
|
68
|
-
@head_re ||=
|
67
|
+
@head_re ||= self.class.head_re
|
69
68
|
end
|
70
69
|
|
71
70
|
def block
|
@@ -85,7 +84,7 @@ module PseudoHiki
|
|
85
84
|
super(stack)
|
86
85
|
end
|
87
86
|
|
88
|
-
def parse_leafs
|
87
|
+
def parse_leafs(breaker)
|
89
88
|
parsed = InlineParser.parse(self.join)
|
90
89
|
self.clear
|
91
90
|
self.concat(parsed)
|
@@ -135,6 +134,10 @@ module PseudoHiki
|
|
135
134
|
first.nominal_level if first # @cached_nominal_level ||= (first.nominal_level if first)
|
136
135
|
end
|
137
136
|
|
137
|
+
def decorator
|
138
|
+
first.decorator if first
|
139
|
+
end
|
140
|
+
|
138
141
|
def push_self(stack)
|
139
142
|
@stack = stack
|
140
143
|
super(stack)
|
@@ -144,7 +147,7 @@ module PseudoHiki
|
|
144
147
|
not (kind_of?(breaker.block) and nominal_level == breaker.nominal_level)
|
145
148
|
end
|
146
149
|
|
147
|
-
def parse_leafs; end
|
150
|
+
def parse_leafs(breaker); end
|
148
151
|
|
149
152
|
def in_link_tag?(preceding_str)
|
150
153
|
preceding_str[-2, 2] == "[[".freeze or preceding_str[-1, 1] == "|".freeze
|
@@ -156,7 +159,7 @@ module PseudoHiki
|
|
156
159
|
|
157
160
|
def add_leaf(line, blockparser)
|
158
161
|
leaf = create_leaf(line, blockparser)
|
159
|
-
blockparser.stack.
|
162
|
+
blockparser.stack.pop_with_breaker(leaf) while blockparser.breakable?(leaf)
|
160
163
|
blockparser.stack.push leaf
|
161
164
|
end
|
162
165
|
|
@@ -168,8 +171,8 @@ module PseudoHiki
|
|
168
171
|
end
|
169
172
|
|
170
173
|
class NonNestedBlockNode < BlockNode
|
171
|
-
def parse_leafs
|
172
|
-
self.each {|leaf| leaf.parse_leafs }
|
174
|
+
def parse_leafs(breaker)
|
175
|
+
self.each {|leaf| leaf.parse_leafs(breaker) }
|
173
176
|
end
|
174
177
|
end
|
175
178
|
|
@@ -189,11 +192,11 @@ module PseudoHiki
|
|
189
192
|
|
190
193
|
module BlockElement
|
191
194
|
{
|
192
|
-
BlockLeaf => %w(DescLeaf VerbatimLeaf TableLeaf CommentOutLeaf BlockNodeEnd HrLeaf),
|
195
|
+
BlockLeaf => %w(DescLeaf VerbatimLeaf TableLeaf CommentOutLeaf BlockNodeEnd HrLeaf DecoratorLeaf),
|
193
196
|
NonNestedBlockLeaf => %w(QuoteLeaf ParagraphLeaf),
|
194
197
|
NestedBlockLeaf => %w(HeadingLeaf),
|
195
198
|
ListTypeLeaf => %w(ListLeaf EnumLeaf),
|
196
|
-
BlockNode => %w(DescNode VerbatimNode TableNode CommentOutNode HrNode),
|
199
|
+
BlockNode => %w(DescNode VerbatimNode TableNode CommentOutNode HrNode DecoratorNode),
|
197
200
|
NonNestedBlockNode => %w(QuoteNode ParagraphNode),
|
198
201
|
NestedBlockNode => %w(HeadingNode),
|
199
202
|
ListTypeBlockNode => %w(ListNode EnumNode),
|
@@ -218,15 +221,43 @@ module PseudoHiki
|
|
218
221
|
attr_accessor :in_block_tag
|
219
222
|
|
220
223
|
def add_leaf(line, blockparser)
|
221
|
-
return @stack.
|
224
|
+
return @stack.pop_with_breaker if LINE_PAT::VERBATIM_END =~ line
|
222
225
|
return super(line, blockparser) unless @in_block_tag
|
223
226
|
line = " ".concat(line) if BlockElement::BlockNodeEnd.head_re =~ line and not @in_block_tag
|
224
227
|
@stack.push BlockElement::VerbatimLeaf.create(line, @in_block_tag)
|
225
228
|
end
|
226
229
|
end
|
227
230
|
|
231
|
+
class BlockElement::DecoratorNode
|
232
|
+
DECORATOR_PAT = /\A(?:([^\[\]:]+))?(?:\[([^\[\]]+)\])?(?::\s*(\S.*))?/o
|
233
|
+
|
234
|
+
class DecoratorItem < Struct.new(:string, :type, :id, :value)
|
235
|
+
def initialize(*args)
|
236
|
+
super
|
237
|
+
self.value = InlineParser.parse(self.value) if self.value
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
def parse_leafs(breaker)
|
242
|
+
decorator = {}
|
243
|
+
breaker.decorator = decorator
|
244
|
+
@stack.remove_current_node.each do |leaf|
|
245
|
+
m = DECORATOR_PAT.match(leaf.join)
|
246
|
+
return nil unless m
|
247
|
+
item = DecoratorItem.new(*(m.to_a))
|
248
|
+
decorator[item.type||:id] = item
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def breakable?(breaker)
|
253
|
+
return super if breaker.kind_of?(BlockElement::DecoratorLeaf)
|
254
|
+
parse_leafs(breaker)
|
255
|
+
@stack.current_node.breakable?(breaker)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
228
259
|
class BlockElement::QuoteNode
|
229
|
-
def parse_leafs
|
260
|
+
def parse_leafs(breaker)
|
230
261
|
self[0] = BlockParser.parse(self[0])
|
231
262
|
end
|
232
263
|
end
|
@@ -284,7 +315,8 @@ module PseudoHiki
|
|
284
315
|
[ParagraphLeaf, ParagraphNode],
|
285
316
|
[HrLeaf, HrNode],
|
286
317
|
[ListLeaf, ListNode],
|
287
|
-
[EnumLeaf, EnumNode]
|
318
|
+
[EnumLeaf, EnumNode],
|
319
|
+
[DecoratorLeaf, DecoratorNode]
|
288
320
|
].each do |leaf, node|
|
289
321
|
ParentNode[leaf] = node
|
290
322
|
end
|
@@ -302,6 +334,7 @@ module PseudoHiki
|
|
302
334
|
['!', HeadingLeaf],
|
303
335
|
['""', QuoteLeaf],
|
304
336
|
['||', TableLeaf],
|
337
|
+
['//@', DecoratorLeaf],
|
305
338
|
['//', CommentOutLeaf],
|
306
339
|
['----\s*$', HrLeaf]
|
307
340
|
].each do |head, leaf|
|
@@ -340,7 +373,7 @@ module PseudoHiki
|
|
340
373
|
def read_lines(lines)
|
341
374
|
each_line = lines.respond_to?(:each_line) ? :each_line : :each
|
342
375
|
lines.send(each_line) {|line| @stack.current_node.add_leaf(line, self) }
|
343
|
-
@stack.
|
376
|
+
@stack.pop_with_breaker
|
344
377
|
end
|
345
378
|
end
|
346
379
|
end
|
data/lib/pseudohiki/converter.rb
CHANGED
@@ -60,8 +60,21 @@ module PseudoHiki
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
+
def gfm_id(heading_node)
|
64
|
+
MarkDownFormat.convert_to_gfm_id_format(to_plain(heading_node).strip)
|
65
|
+
end
|
66
|
+
|
67
|
+
def create_gfm_table_of_contents(tree)
|
68
|
+
toc_lines = collect_nodes_for_table_of_contents(tree).map do |toc_node|
|
69
|
+
"%s[[%s|#%s]]#{$/}"%['*'*toc_node.nominal_level, to_plain(toc_node).strip, gfm_id(toc_node)]
|
70
|
+
end
|
71
|
+
|
72
|
+
@options.formatter.format(BlockParser.parse(toc_lines))
|
73
|
+
end
|
74
|
+
|
63
75
|
def create_table_of_contents(tree)
|
64
76
|
return "" unless @options[:toc]
|
77
|
+
return create_gfm_table_of_contents(tree) if @options[:html_version].version == "gfm"
|
65
78
|
return create_plain_table_of_contents(tree) unless @options.html_template
|
66
79
|
create_html_table_of_contents(tree)
|
67
80
|
end
|
@@ -275,9 +288,9 @@ module PseudoHiki
|
|
275
288
|
|
276
289
|
def set_encoding(given_opt)
|
277
290
|
return nil unless String.new.respond_to? :encoding
|
278
|
-
external, internal = given_opt.split(/:/o)
|
279
|
-
Encoding.default_external = external if external
|
280
|
-
Encoding.default_internal = internal if internal
|
291
|
+
external, internal = given_opt.split(/:/o, 2)
|
292
|
+
Encoding.default_external = external if external and not external.empty?
|
293
|
+
Encoding.default_internal = internal if internal and not internal.empty?
|
281
294
|
end
|
282
295
|
|
283
296
|
def parse_command_line_options
|
@@ -61,6 +61,7 @@ module PseudoHiki
|
|
61
61
|
|
62
62
|
def visit(tree)
|
63
63
|
htmlelement = create_self_element(tree)
|
64
|
+
decorate(htmlelement, tree)
|
64
65
|
push_visited_results(htmlelement, tree)
|
65
66
|
htmlelement
|
66
67
|
end
|
@@ -78,6 +79,21 @@ module PseudoHiki
|
|
78
79
|
chunks.push tree
|
79
80
|
end
|
80
81
|
|
82
|
+
def decorate(htmlelement, tree)
|
83
|
+
each_decorator(htmlelement, tree) do |element, decorator|
|
84
|
+
element[CLASS] = HtmlElement.escape(decorator[CLASS].id) if decorator[CLASS]
|
85
|
+
if id_item = decorator[ID]||decorator[:id]
|
86
|
+
element[ID] = HtmlElement.escape(id_item.id).upcase
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def each_decorator(element, tree)
|
92
|
+
return unless element.kind_of? HtmlElement
|
93
|
+
return unless tree.kind_of? BlockParser::BlockNode and tree.decorator
|
94
|
+
tree.decorator.tap {|decorator| yield element, decorator }
|
95
|
+
end
|
96
|
+
|
81
97
|
class ListLeafNodeFormatter < self
|
82
98
|
def create_self_element(tree)
|
83
99
|
super(tree).tap do |element|
|
@@ -148,8 +164,9 @@ module PseudoHiki
|
|
148
164
|
htmlelement[ALT] = caption.join if caption
|
149
165
|
else
|
150
166
|
htmlelement = create_self_element
|
151
|
-
|
152
|
-
htmlelement.
|
167
|
+
url = tree.join
|
168
|
+
htmlelement[HREF] = url.start_with?("#".freeze) ? url.upcase : url
|
169
|
+
htmlelement.push caption||url
|
153
170
|
end
|
154
171
|
htmlelement
|
155
172
|
end
|
@@ -175,6 +192,14 @@ module PseudoHiki
|
|
175
192
|
|
176
193
|
#for BlockParser
|
177
194
|
|
195
|
+
class << Formatter[TableNode]
|
196
|
+
def decorate(htmlelement, tree)
|
197
|
+
each_decorator(htmlelement, tree) do |element, decorator|
|
198
|
+
htmlelement["summary"] = HtmlElement.escape(decorator["summary"].value.join) if decorator["summary"]
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
178
203
|
class << Formatter[VerbatimNode]
|
179
204
|
def visit(tree)
|
180
205
|
create_self_element.tap do |element|
|
@@ -20,6 +20,17 @@ module PseudoHiki
|
|
20
20
|
Regexp.new(tokens.join("|"))
|
21
21
|
end
|
22
22
|
|
23
|
+
def self.split_into_tokens(str, token_pat)
|
24
|
+
tokens = []
|
25
|
+
while m = token_pat.match(str)
|
26
|
+
tokens.push m.pre_match unless m.pre_match.empty?
|
27
|
+
tokens.push m[0]
|
28
|
+
str = m.post_match
|
29
|
+
end
|
30
|
+
tokens.push str unless str.empty?
|
31
|
+
tokens
|
32
|
+
end
|
33
|
+
|
23
34
|
class InlineParser < TreeStack
|
24
35
|
module InlineElement
|
25
36
|
class InlineNode < InlineParser::Node; end
|
@@ -43,7 +54,7 @@ module PseudoHiki
|
|
43
54
|
[StrongNode, "'''", "'''"],
|
44
55
|
[DelNode, "==", "=="],
|
45
56
|
[LiteralNode, "``", "``"],
|
46
|
-
[PluginNode, "{{","}}"]].each do |type, head, tail|
|
57
|
+
[PluginNode, "{{", "}}"]].each do |type, head, tail|
|
47
58
|
HEAD[head] = type
|
48
59
|
TAIL[tail] = type
|
49
60
|
NodeTypeToHead[type] = head
|
@@ -52,8 +63,7 @@ module PseudoHiki
|
|
52
63
|
TokenPat[self] = PseudoHiki.compile_token_pat(HEAD.keys,TAIL.keys,[LinkSep, TableSep, DescSep])
|
53
64
|
|
54
65
|
def initialize(str)
|
55
|
-
@
|
56
|
-
@tokens = split_into_tokens(str)
|
66
|
+
@tokens = PseudoHiki.split_into_tokens(str, TokenPat[self.class])
|
57
67
|
super()
|
58
68
|
end
|
59
69
|
|
@@ -75,17 +85,6 @@ module PseudoHiki
|
|
75
85
|
self.pop
|
76
86
|
end
|
77
87
|
|
78
|
-
def split_into_tokens(str)
|
79
|
-
tokens = []
|
80
|
-
while m = @token_pat.match(str)
|
81
|
-
tokens.push m.pre_match unless m.pre_match.empty?
|
82
|
-
tokens.push m[0]
|
83
|
-
str = m.post_match
|
84
|
-
end
|
85
|
-
tokens.push str unless str.empty?
|
86
|
-
tokens
|
87
|
-
end
|
88
|
-
|
89
88
|
def parse
|
90
89
|
while token = @tokens.shift
|
91
90
|
next if TAIL[token] and treated_as_node_end(token)
|
@@ -4,6 +4,7 @@ require 'pseudohiki/inlineparser'
|
|
4
4
|
require 'pseudohiki/blockparser'
|
5
5
|
require 'pseudohiki/htmlformat'
|
6
6
|
require 'pseudohiki/plaintextformat'
|
7
|
+
require 'pseudohiki/utils'
|
7
8
|
require 'htmlelement'
|
8
9
|
require 'ostruct'
|
9
10
|
|
@@ -14,6 +15,8 @@ module PseudoHiki
|
|
14
15
|
include BlockParser::BlockElement
|
15
16
|
|
16
17
|
Formatters = {}
|
18
|
+
GFM_STRIPPED_CHARS = " -&+$,/:;=?@\"{}#|^~[]`\\*()%.!'"
|
19
|
+
GFM_STRIPPED_CHARS_PAT = Regexp.union(/\s+/o, /[#{Regexp.escape(GFM_STRIPPED_CHARS)}]/o)
|
17
20
|
|
18
21
|
def self.format(tree, options={ :strict_mode=> false, :gfm_style => false })
|
19
22
|
if Formatters.empty?
|
@@ -25,6 +28,12 @@ module PseudoHiki
|
|
25
28
|
Formatters[options].format(tree)
|
26
29
|
end
|
27
30
|
|
31
|
+
def self.convert_to_gfm_id_format(heading)
|
32
|
+
heading.gsub(GFM_STRIPPED_CHARS_PAT) do |char|
|
33
|
+
/\A\s+\Z/o =~ char ? '-'.freeze : ''.freeze
|
34
|
+
end.downcase
|
35
|
+
end
|
36
|
+
|
28
37
|
def initialize(formatter={}, options={ :strict_mode=> false, :gfm_style => false })
|
29
38
|
@formatter = formatter
|
30
39
|
options_given_via_block = nil
|
@@ -60,6 +69,7 @@ module PseudoHiki
|
|
60
69
|
|
61
70
|
def format(tree)
|
62
71
|
formatter = get_plain
|
72
|
+
@formatter[LinkNode].id_conv_table = prepare_id_conv_table(tree) if @options.gfm_style
|
63
73
|
tree.accept(formatter).join
|
64
74
|
end
|
65
75
|
|
@@ -78,6 +88,23 @@ module PseudoHiki
|
|
78
88
|
element.to_s.gsub(/([^>])\r?\n/, "\\1") << $/
|
79
89
|
end
|
80
90
|
|
91
|
+
def collect_headings(tree)
|
92
|
+
PseudoHiki::Utils::NodeCollector.select(tree) do |node|
|
93
|
+
node.kind_of? PseudoHiki::BlockParser::HeadingLeaf
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def prepare_id_conv_table(tree)
|
98
|
+
{}.tap do |table|
|
99
|
+
collect_headings(tree).each do |heading|
|
100
|
+
if node_id = heading.node_id
|
101
|
+
heading_text = PlainTextFormat.format(heading).strip
|
102
|
+
table[node_id] = MarkDownFormat.convert_to_gfm_id_format(heading_text)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
81
108
|
def self.create(options={ :strict_mode => false })
|
82
109
|
formatter = {}
|
83
110
|
main_formatter = self.new(formatter, options)
|
@@ -139,6 +166,8 @@ module PseudoHiki
|
|
139
166
|
end
|
140
167
|
|
141
168
|
class LinkNodeFormatter < self
|
169
|
+
attr_writer :id_conv_table
|
170
|
+
|
142
171
|
def visit(tree)
|
143
172
|
not_from_thumbnail = tree.first.class != LinkNode
|
144
173
|
tree = tree.dup
|
@@ -151,7 +180,8 @@ module PseudoHiki
|
|
151
180
|
STDERR.puts "No uri is specified for #{caption}"
|
152
181
|
end
|
153
182
|
element.push "!" if ImageSuffix =~ ref and not_from_thumbnail
|
154
|
-
|
183
|
+
link = format_link(tree)
|
184
|
+
element.push "[#{(caption||tree).join}](#{link})"
|
155
185
|
element
|
156
186
|
end
|
157
187
|
|
@@ -162,6 +192,16 @@ module PseudoHiki
|
|
162
192
|
tree.shift
|
163
193
|
caption_part.map {|element| visited_result(element) }
|
164
194
|
end
|
195
|
+
|
196
|
+
def format_link(tree)
|
197
|
+
link = tree.join
|
198
|
+
return link unless @id_conv_table
|
199
|
+
if /\A#/o =~ link and gfm_link = @id_conv_table[link[1..-1]]
|
200
|
+
"#".concat gfm_link
|
201
|
+
else
|
202
|
+
link
|
203
|
+
end
|
204
|
+
end
|
165
205
|
end
|
166
206
|
|
167
207
|
class EmNodeFormatter < self
|
@@ -244,13 +284,14 @@ module PseudoHiki
|
|
244
284
|
class VerbatimNodeFormatter < self
|
245
285
|
def visit(tree)
|
246
286
|
element = super(tree)
|
287
|
+
@language_name = language_name(tree)
|
247
288
|
return gfm_verbatim(element) if @options.gfm_style
|
248
289
|
md_verbatim(element)
|
249
290
|
end
|
250
291
|
|
251
292
|
def gfm_verbatim(element)
|
252
293
|
element.tap do |lines|
|
253
|
-
lines.unshift "```#{$/}"
|
294
|
+
lines.unshift "```#{@language_name + $/}"
|
254
295
|
lines.push "```#{$/ * 2}"
|
255
296
|
end
|
256
297
|
end
|
@@ -258,6 +299,12 @@ module PseudoHiki
|
|
258
299
|
def md_verbatim(element)
|
259
300
|
element.join.gsub(/^/o, " ").sub(/ \Z/o, "").concat $/
|
260
301
|
end
|
302
|
+
|
303
|
+
def language_name(tree)
|
304
|
+
tree.decorator.tap do |decorator|
|
305
|
+
return decorator ? decorator["code"].id : ""
|
306
|
+
end
|
307
|
+
end
|
261
308
|
end
|
262
309
|
|
263
310
|
class QuoteNodeFormatter < self
|
@@ -329,11 +376,9 @@ module PseudoHiki
|
|
329
376
|
rows.each_with_index do |row, i|
|
330
377
|
row.each do |cell|
|
331
378
|
return false if cell.rowspan > 1 or cell.colspan > 1
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
return false if cell.cell_type == "th"
|
336
|
-
end
|
379
|
+
#A table head row should be at the beginning and composed of <th> elements,
|
380
|
+
#and other rows should not include <th> elements
|
381
|
+
return false unless (i == 0) == (cell.cell_type == "th")
|
337
382
|
end
|
338
383
|
end
|
339
384
|
true
|
@@ -159,25 +159,31 @@ ERROR_TEXT
|
|
159
159
|
|
160
160
|
def visit(tree)
|
161
161
|
table = create_self_element(tree)
|
162
|
-
|
163
|
-
rows.length.times { table.push create_self_element(tree) }
|
162
|
+
tree.length.times { table.push create_self_element(tree) }
|
164
163
|
max_col = tree.map{|row| row.reduce(0) {|sum, cell| sum + cell.colspan }}.max - 1
|
165
|
-
max_row =
|
164
|
+
max_row = tree.length - 1
|
165
|
+
each_empty_cell_index(max_row, max_col, tree, table) do |r, c, cur_row|
|
166
|
+
table[r][c] = cur_row.shift
|
167
|
+
fill_expand(table, r, c, table[r][c])
|
168
|
+
end
|
169
|
+
format_table(table, tree)
|
170
|
+
end
|
171
|
+
|
172
|
+
def each_empty_cell_index(max_row, max_col, tree, table)
|
173
|
+
rows = deep_copy_tree(tree)
|
166
174
|
cur_row = nil
|
167
175
|
each_cell_index(max_row, max_col) do |r, c|
|
168
176
|
cur_row = rows.shift if c == 0
|
169
177
|
next if table[r][c]
|
170
178
|
begin
|
171
179
|
raise MalFormedTableError.new(ERROR_MESSAGE%[table[r].inspect]) if cur_row.empty?
|
172
|
-
|
173
|
-
fill_expand(table, r, c, table[r][c])
|
180
|
+
yield r, c, cur_row
|
174
181
|
rescue
|
175
182
|
raise if @options.strict_mode
|
176
183
|
STDERR.puts ERROR_MESSAGE%[table[r].inspect]
|
177
184
|
next
|
178
185
|
end
|
179
186
|
end
|
180
|
-
format_table(table, tree)
|
181
187
|
end
|
182
188
|
|
183
189
|
def deep_copy_tree(tree)
|
@@ -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
|
@@ -0,0 +1,34 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'pseudohiki/inlineparser'
|
4
|
+
require 'pseudohiki/blockparser'
|
5
|
+
|
6
|
+
module PseudoHiki
|
7
|
+
module Utils
|
8
|
+
|
9
|
+
class NodeCollector
|
10
|
+
attr_reader :nodes
|
11
|
+
|
12
|
+
def self.select(tree, &condition)
|
13
|
+
collector = new(&condition)
|
14
|
+
collector.visit(tree)
|
15
|
+
collector.nodes
|
16
|
+
end
|
17
|
+
|
18
|
+
def initialize (&condition)
|
19
|
+
@nodes = []
|
20
|
+
@condition = condition
|
21
|
+
end
|
22
|
+
|
23
|
+
def visit(tree)
|
24
|
+
if @condition.call(tree)
|
25
|
+
@nodes.push tree
|
26
|
+
else
|
27
|
+
tree.each do |node|
|
28
|
+
node.accept(self) if node.respond_to? :accept
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/pseudohiki/version.rb
CHANGED
data/lib/pseudohikiparser.rb
CHANGED
data/test/test_blockparser.rb
CHANGED
@@ -311,6 +311,35 @@ TEXT
|
|
311
311
|
assert_equal([[[["heading"]]]],parsed)
|
312
312
|
end
|
313
313
|
|
314
|
+
|
315
|
+
def test_decorator
|
316
|
+
text = <<TEXT
|
317
|
+
//@class[section_name]
|
318
|
+
!!title of section
|
319
|
+
|
320
|
+
//@summary: Summary of the table
|
321
|
+
||!header 1||! header 2
|
322
|
+
||cell 1||cell 2
|
323
|
+
|
324
|
+
a paragraph.
|
325
|
+
|
326
|
+
//@class[class_name]
|
327
|
+
//@[id_name]
|
328
|
+
another paragraph.
|
329
|
+
TEXT
|
330
|
+
|
331
|
+
tree = PseudoHiki::BlockParser.parse(text.lines.to_a.map {|line| line.chomp })
|
332
|
+
assert_equal(PseudoHiki::BlockParser::BlockNode, tree.class)
|
333
|
+
assert_equal("section_name", tree[0].decorator["class"].id)
|
334
|
+
assert_equal(PseudoHiki::BlockParser::BlockElement::HeadingNode, tree[0].class)
|
335
|
+
assert_equal([[["title of section"]], [[[["header 1"]], [[" header 2"]]], [[["cell 1"]], [["cell 2"]]]], [[["a paragraph."]]], [[["another paragraph."]]]], tree[0])
|
336
|
+
assert_equal([["Summary of the table"]], tree[0][1].decorator["summary"].value)
|
337
|
+
assert_equal(PseudoHiki::BlockParser::BlockElement::TableNode, tree[0][1].class)
|
338
|
+
assert_equal(nil, tree[0][2].decorator)
|
339
|
+
assert_equal('id_name', tree[0][3].decorator[:id].id)
|
340
|
+
assert_equal('class_name', tree[0][3].decorator["class"].id)
|
341
|
+
end
|
342
|
+
|
314
343
|
def test_comment_out_followed_by_a_verbatim_block
|
315
344
|
text = <<TEXT
|
316
345
|
the first paragraph
|
data/test/test_htmlformat.rb
CHANGED
@@ -696,6 +696,69 @@ HTML
|
|
696
696
|
assert_equal(xhtml, XhtmlFormat.format(tree).to_s)
|
697
697
|
end
|
698
698
|
|
699
|
+
def test_decorator
|
700
|
+
text = <<TEXT
|
701
|
+
//@class[section_type]
|
702
|
+
!!title of section
|
703
|
+
|
704
|
+
a paragraph.
|
705
|
+
|
706
|
+
//@class[class_name]
|
707
|
+
//@id[id_name]
|
708
|
+
another paragraph.
|
709
|
+
TEXT
|
710
|
+
|
711
|
+
xhtml = <<HTML
|
712
|
+
<div class="section_type">
|
713
|
+
<h2>title of section</h2>
|
714
|
+
<p>
|
715
|
+
a paragraph.</p>
|
716
|
+
<p class="class_name" id="ID_NAME">
|
717
|
+
another paragraph.</p>
|
718
|
+
<!-- end of section_type -->
|
719
|
+
</div>
|
720
|
+
HTML
|
721
|
+
tree = BlockParser.parse(text.lines.to_a.map {|line| line.chomp })
|
722
|
+
assert_equal(xhtml, XhtmlFormat.format(tree).to_s)
|
723
|
+
end
|
724
|
+
|
725
|
+
def test_decorator_for_table
|
726
|
+
text = <<TEXT
|
727
|
+
//@summary: Summary of the table
|
728
|
+
||!header 1||! header 2
|
729
|
+
||cell 1||cell 2
|
730
|
+
TEXT
|
731
|
+
|
732
|
+
xhtml = <<HTML
|
733
|
+
<table summary="Summary of the table">
|
734
|
+
<tr><th>header 1</th><th> header 2</th></tr>
|
735
|
+
<tr><td>cell 1</td><td>cell 2</td></tr>
|
736
|
+
</table>
|
737
|
+
HTML
|
738
|
+
tree = BlockParser.parse(text.lines.to_a.map {|line| line.chomp })
|
739
|
+
assert_equal(xhtml, XhtmlFormat.format(tree).to_s)
|
740
|
+
end
|
741
|
+
|
742
|
+
def test_decorator_for_verbatim
|
743
|
+
text = <<TEXT
|
744
|
+
//@code[ruby]
|
745
|
+
def bonjour!
|
746
|
+
puts "Bonjour!"
|
747
|
+
end
|
748
|
+
TEXT
|
749
|
+
|
750
|
+
xhtml = <<HTML
|
751
|
+
<pre>
|
752
|
+
def bonjour!
|
753
|
+
puts "Bonjour!"
|
754
|
+
end
|
755
|
+
</pre>
|
756
|
+
HTML
|
757
|
+
|
758
|
+
tree = BlockParser.parse(text.lines.to_a)
|
759
|
+
assert_equal(xhtml, XhtmlFormat.format(tree).to_s)
|
760
|
+
end
|
761
|
+
|
699
762
|
def test_comment_out_followed_by_a_verbatim_block
|
700
763
|
text = <<TEXT
|
701
764
|
the first paragraph
|
data/test/test_inlineparser.rb
CHANGED
@@ -10,16 +10,19 @@ class TC_InlineParser < MiniTest::Unit::TestCase
|
|
10
10
|
|
11
11
|
def test_inlineparser_compile_token_pat
|
12
12
|
parser = InlineParser.new("")
|
13
|
-
assert_equal(/'''|\}\}|\|\||\{\{|``|\]\]|\[\[|==|''|\||:/, parser.
|
13
|
+
assert_equal(/'''|\}\}|\|\||\{\{|``|\]\]|\[\[|==|''|\||:/, InlineParser::TokenPat[parser.class])
|
14
14
|
end
|
15
15
|
|
16
16
|
def test_inlineparser_split_into_tokens
|
17
17
|
parser = InlineParser.new("")
|
18
|
-
tokens =
|
18
|
+
tokens = PseudoHiki.split_into_tokens("As a test case, '''this part''' must be in <strong>.",
|
19
|
+
InlineParser::TokenPat[parser.class])
|
19
20
|
assert_equal(["As a test case, ","'''","this part","'''"," must be in <strong>."],tokens)
|
20
|
-
tokens =
|
21
|
+
tokens = PseudoHiki.split_into_tokens("As another {{test case}}, '''this part''' must be in <strong>.",
|
22
|
+
InlineParser::TokenPat[parser.class])
|
21
23
|
assert_equal(["As another ","{{","test case","}}", ", ","'''","this part","'''"," must be in <strong>."],tokens)
|
22
|
-
tokens =
|
24
|
+
tokens = PseudoHiki.split_into_tokens("As another ''test case'', '''this part''' must be in <strong>.",
|
25
|
+
InlineParser::TokenPat[parser.class])
|
23
26
|
assert_equal(["As another ","''","test case","''", ", ","'''","this part","'''"," must be in <strong>."],tokens)
|
24
27
|
end
|
25
28
|
|
data/test/test_markdownformat.rb
CHANGED
@@ -44,6 +44,15 @@ TEXT
|
|
44
44
|
assert_equal(gfm_text, MarkDownFormat.format(tree, :gfm_style => true))
|
45
45
|
end
|
46
46
|
|
47
|
+
def test_self_convert_to_gfm_id_format
|
48
|
+
heading_with_non_ascii_chars = "class PseudoHiki::BlockParser"
|
49
|
+
heading_with_multiple_spaces = "Development status of features from the original Hiki notation"
|
50
|
+
gfm_id_with_non_ascii_chars = MarkDownFormat.convert_to_gfm_id_format(heading_with_non_ascii_chars)
|
51
|
+
gfm_id_with_multiple_spaces = MarkDownFormat.convert_to_gfm_id_format(heading_with_multiple_spaces)
|
52
|
+
assert_equal("class-pseudohikiblockparser", gfm_id_with_non_ascii_chars)
|
53
|
+
assert_equal("development-status-of-features-from-the-original-hiki-notation", gfm_id_with_multiple_spaces)
|
54
|
+
end
|
55
|
+
|
47
56
|
def test_plain
|
48
57
|
text = <<TEXT
|
49
58
|
test string
|
@@ -508,5 +517,132 @@ TEXT
|
|
508
517
|
tree = BlockParser.parse(text.lines.to_a)
|
509
518
|
assert_equal(md_text, @formatter.format(tree).to_s)
|
510
519
|
end
|
520
|
+
|
521
|
+
def test_decorator_for_verbatim
|
522
|
+
text = <<TEXT
|
523
|
+
//@code[ruby]
|
524
|
+
def bonjour!
|
525
|
+
puts "Bonjour!"
|
526
|
+
end
|
527
|
+
TEXT
|
528
|
+
|
529
|
+
gfm_text =<<TEXT
|
530
|
+
```ruby
|
531
|
+
def bonjour!
|
532
|
+
puts "Bonjour!"
|
533
|
+
end
|
534
|
+
```
|
535
|
+
|
536
|
+
TEXT
|
537
|
+
|
538
|
+
md_text = <<TEXT
|
539
|
+
def bonjour!
|
540
|
+
puts "Bonjour!"
|
541
|
+
end
|
542
|
+
|
543
|
+
TEXT
|
544
|
+
|
545
|
+
tree = BlockParser.parse(text.lines.to_a)
|
546
|
+
assert_equal(gfm_text, @gfm_formatter.format(tree).to_s)
|
547
|
+
assert_equal(md_text, @formatter.format(tree).to_s)
|
548
|
+
end
|
549
|
+
|
550
|
+
def test_decorator_for_verbatim_block
|
551
|
+
text = <<TEXT
|
552
|
+
//@code[ruby]
|
553
|
+
<<<
|
554
|
+
def bonjour!
|
555
|
+
puts "Bonjour!"
|
556
|
+
end
|
557
|
+
>>>
|
558
|
+
TEXT
|
559
|
+
|
560
|
+
gfm_text =<<TEXT
|
561
|
+
```ruby
|
562
|
+
def bonjour!
|
563
|
+
puts "Bonjour!"
|
564
|
+
end
|
565
|
+
```
|
566
|
+
|
567
|
+
TEXT
|
568
|
+
|
569
|
+
md_text = <<TEXT
|
570
|
+
def bonjour!
|
571
|
+
puts "Bonjour!"
|
572
|
+
end
|
573
|
+
|
574
|
+
TEXT
|
575
|
+
|
576
|
+
tree = BlockParser.parse(text.lines.to_a)
|
577
|
+
assert_equal(gfm_text, @gfm_formatter.format(tree).to_s)
|
578
|
+
assert_equal(md_text, @formatter.format(tree).to_s)
|
579
|
+
end
|
580
|
+
|
581
|
+
def test_collect_headings
|
582
|
+
text = <<TEXT
|
583
|
+
!![main-heading] heading
|
584
|
+
|
585
|
+
paragraph
|
586
|
+
|
587
|
+
!!![sub-heading] subheading
|
588
|
+
|
589
|
+
another paragraph
|
590
|
+
TEXT
|
591
|
+
|
592
|
+
tree = BlockParser.parse(text)
|
593
|
+
headings = @gfm_formatter.collect_headings(tree)
|
594
|
+
|
595
|
+
assert_equal([[[" heading\n"]], [[" subheading\n"]]], headings)
|
596
|
+
end
|
597
|
+
|
598
|
+
def test_prepare_id_conv_table
|
599
|
+
text = <<TEXT
|
600
|
+
!![main-heading] heading
|
601
|
+
|
602
|
+
paragraph
|
603
|
+
|
604
|
+
!!![sub-heading] subheading
|
605
|
+
|
606
|
+
another paragraph
|
607
|
+
TEXT
|
608
|
+
|
609
|
+
expected_table = {
|
610
|
+
"main-heading" => "heading",
|
611
|
+
"sub-heading" => "subheading"
|
612
|
+
}
|
613
|
+
|
614
|
+
tree = BlockParser.parse(text)
|
615
|
+
id_conv_table = @gfm_formatter.prepare_id_conv_table(tree)
|
616
|
+
|
617
|
+
assert_equal(expected_table, id_conv_table)
|
618
|
+
end
|
619
|
+
|
620
|
+
def test_gfm_style_in_page_anchor
|
621
|
+
text = <<TEXT
|
622
|
+
!![main_heading] Main Heading
|
623
|
+
|
624
|
+
a paragraph
|
625
|
+
|
626
|
+
!!![sub-heading] SubHeading
|
627
|
+
|
628
|
+
a link to [[main heading|#main_heading]]
|
629
|
+
TEXT
|
630
|
+
|
631
|
+
expected_text = <<TEXT
|
632
|
+
## Main Heading
|
633
|
+
|
634
|
+
a paragraph
|
635
|
+
|
636
|
+
### SubHeading
|
637
|
+
|
638
|
+
a link to [main heading](#main-heading)
|
639
|
+
|
640
|
+
TEXT
|
641
|
+
|
642
|
+
tree = BlockParser.parse(text)
|
643
|
+
gfm_text = @gfm_formatter.format(tree)
|
644
|
+
|
645
|
+
assert_equal(expected_text, gfm_text)
|
646
|
+
end
|
511
647
|
end
|
512
648
|
|
@@ -194,17 +194,17 @@ TEXT
|
|
194
194
|
toc = PageComposer.new(options).create_table_of_contents(@parsed_tree)
|
195
195
|
assert_equal("", toc)
|
196
196
|
|
197
|
-
|
198
|
-
* Heading1
|
199
|
-
* Heading2
|
200
|
-
* Heading2-1
|
197
|
+
toc_in_gfm_text = <<TEXT
|
198
|
+
* [Heading1](#heading1)
|
199
|
+
* [Heading2](#heading2)
|
200
|
+
* [Heading2-1](#heading21)
|
201
201
|
TEXT
|
202
202
|
|
203
203
|
set_argv("-fg -m 'table of contents' -c css/with_toc.css wikipage.txt")
|
204
204
|
options = OptionManager.new
|
205
205
|
options.set_options_from_command_line
|
206
206
|
toc = PageComposer.new(options).create_table_of_contents(@parsed_tree)
|
207
|
-
assert_equal(
|
207
|
+
assert_equal(toc_in_gfm_text, toc)
|
208
208
|
|
209
209
|
toc_in_html = <<TEXT
|
210
210
|
<ul>
|
@@ -293,12 +293,12 @@ paragraph
|
|
293
293
|
</html>
|
294
294
|
HTML
|
295
295
|
|
296
|
-
|
296
|
+
expected_gfm_text = <<TEXT
|
297
297
|
## table of contents
|
298
298
|
|
299
|
-
* Heading1
|
300
|
-
* Heading2
|
301
|
-
* Heading2-1
|
299
|
+
* [Heading1](#heading1)
|
300
|
+
* [Heading2](#heading2)
|
301
|
+
* [Heading2-1](#heading21)
|
302
302
|
|
303
303
|
# Title
|
304
304
|
|
@@ -329,8 +329,8 @@ TEXT
|
|
329
329
|
options = OptionManager.new
|
330
330
|
options.set_options_from_command_line
|
331
331
|
|
332
|
-
|
333
|
-
assert_equal(
|
332
|
+
composed_gfm_text = PageComposer.new(options).compose_html(@input_lines).join
|
333
|
+
assert_equal(expected_gfm_text, composed_gfm_text)
|
334
334
|
end
|
335
335
|
|
336
336
|
def test_output_in_gfm_with_toc
|
@@ -355,8 +355,8 @@ output = <<GFM
|
|
355
355
|
|
356
356
|
## Table of Contents
|
357
357
|
|
358
|
-
* The first heading
|
359
|
-
* The second heading
|
358
|
+
* [The first heading](#the-first-heading)
|
359
|
+
* [The second heading](#the-second-heading)
|
360
360
|
|
361
361
|
## The first heading
|
362
362
|
|
@@ -33,7 +33,7 @@ TEXT
|
|
33
33
|
the first paragraph with <em>inline</em> <code>tags</code>
|
34
34
|
</p>
|
35
35
|
<p>
|
36
|
-
the second paragraph with <del>block</del> <a href="#
|
36
|
+
the second paragraph with <del>block</del> <a href="#ID">inline</a> tags
|
37
37
|
</p>
|
38
38
|
<!-- end of section h2 -->
|
39
39
|
</div>
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pseudohikiparser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3.develop
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- HASHIMOTO, Naoki
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2015-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -74,7 +74,9 @@ files:
|
|
74
74
|
- lib/pseudohiki/inlineparser.rb
|
75
75
|
- lib/pseudohiki/markdownformat.rb
|
76
76
|
- lib/pseudohiki/plaintextformat.rb
|
77
|
+
- lib/pseudohiki/sinatra_helpers.rb
|
77
78
|
- lib/pseudohiki/treestack.rb
|
79
|
+
- lib/pseudohiki/utils.rb
|
78
80
|
- lib/pseudohiki/version.rb
|
79
81
|
- lib/pseudohikiparser.rb
|
80
82
|
- test/test_blockparser.rb
|
@@ -105,12 +107,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
105
107
|
version: 1.8.7
|
106
108
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
109
|
requirements:
|
108
|
-
- - "
|
110
|
+
- - ">"
|
109
111
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
112
|
+
version: 1.3.1
|
111
113
|
requirements: []
|
112
114
|
rubyforge_project:
|
113
|
-
rubygems_version: 2.2.
|
115
|
+
rubygems_version: 2.2.3
|
114
116
|
signing_key:
|
115
117
|
specification_version: 4
|
116
118
|
summary: 'PseudoHikiParser: a parser of texts in a Hiki like notation.'
|