pseudohikiparser 0.0.2 → 0.0.3.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/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.'
|