hikidoc 0.0.1

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.
@@ -0,0 +1,314 @@
1
+ !�ѥ饰���
2
+
3
+ Ϣ³����ʣ���Ԥ�Ϣ�뤵��ư�ĤΥѥ饰��դˤʤ�ޤ���
4
+
5
+ ���� (���ԤΤߡ��ޤ��ϥ��ڡ��������֤����ι�) �ϥѥ饰��դζ��ڤ�ˤʤ�ޤ���
6
+
7
+ *������
8
+
9
+ �㤨�С�
10
+ �����������˵��Ҥ���ȡ������ιԤ�
11
+ ��ĤΥѥ饰��դȤ�����������ޤ���
12
+
13
+ *������
14
+
15
+ �㤨�С�
16
+ �����������˵��Ҥ���ȡ������ιԤ�
17
+ ��ĤΥѥ饰��դȤ�����������ޤ���
18
+
19
+ !���
20
+
21
+ !!WikiName
22
+
23
+ ��ʸ���αѻ��ǻϤޤꡢ��ʸ���αѻ��ޤ��Ͽ�����1ʸ���ʾ�³��
24
+
25
+ ���ξ�郎2��ʾ巫���֤����ñ���WikiName�ˤʤ꼫ưŪ�˥�󥯤��Ϥ��ޤ���
26
+
27
+ *������
28
+
29
+ WikiName - WikiName
30
+ HogeRule1 - WikiName
31
+ NOTWIKINAME - ������ʸ���ʤΤ�WikiName�ǤϤʤ�
32
+ WikiNAME - NAME��������ʸ���ʤΤ�WikiName�ǤϤʤ�
33
+ fooWikiName - ��Ƭ�����ƾ�ʸ���αѻ�foo�����뤿��WikiName�ǤϤʤ�
34
+
35
+ *������
36
+
37
+ **WikiName - WikiName
38
+ **HogeRule1 - WikiName
39
+ **NOTWIKINAME - ������ʸ���ʤΤ�WikiName�ǤϤʤ�
40
+ **WikiNAME - NAME��������ʸ���ʤΤ�WikiName�ǤϤʤ�
41
+ **fooWikiName - ��Ƭ�����ƾ�ʸ���αѻ�foo�����뤿��WikiName�ǤϤʤ�
42
+
43
+ WikiName������''^''��Ĥ����WikiName�ؤΥ�󥯤��������뤳�Ȥ��Ǥ��ޤ���
44
+
45
+ *������
46
+
47
+ WikiName - WikiName
48
+ ^WikiName - WikiName�ؤΥ�󥯤�����
49
+
50
+ *������
51
+
52
+ **WikiName - WikiName
53
+ **^WikiName - WikiName�ؤΥ�󥯤�����
54
+
55
+ !!�ڡ����ؤΥ��
56
+
57
+ �ڡ���̾����ĤΥ������å��ǰϤ�ȡ����Υڡ����ؤΥ�󥯤ˤʤ�ޤ���
58
+
59
+ *������
60
+
61
+ �㤨��[[�հ���Ruby]]�Ȥ���ȡ����Υڡ����ؤΥ�󥯤ˤʤ�ޤ���
62
+
63
+ *������
64
+
65
+ �㤨��[[�հ���Ruby]]�Ȥ���ȡ����Υڡ����ؤΥ�󥯤ˤʤ�ޤ���
66
+
67
+ !!Ǥ�դ�URL�ؤΥ��
68
+
69
+ ñ��|URL����ĤΥ������å��ǰϤ�Ȥ�Ǥ�դ�URL�ؤΥ�󥯤ˤʤ�ޤ���
70
+
71
+ *������
72
+
73
+ [[Yahoo!|http://www.yahoo.co.jp/]]�Ȥ���Ǥ��ޤ���
74
+
75
+ *������
76
+
77
+ [[Yahoo!|http://www.yahoo.co.jp/]]�Ȥ���Ǥ��ޤ���
78
+
79
+ �ѥ饰������URL�äݤ���Τ�����Ⱦ���˥�󥯤��Ϥ��ޤ���
80
+
81
+ *������
82
+
83
+ Hiki�Υڡ�����http://hikiwiki.org/ja/�Ǥ���
84
+
85
+ *������
86
+
87
+ Hiki�Υڡ�����http://hikiwiki.org/ja/�Ǥ���
88
+
89
+ ���ΤȤ�URL��������jpg,jpeg,png,gif����IMG������Ÿ������ޤ���
90
+
91
+ *������
92
+
93
+ http://jp.rubyist.net/theme/clover/clover_h1.png
94
+
95
+ *������
96
+
97
+ http://jp.rubyist.net/theme/clover/clover_h1.png
98
+
99
+ !�����Ѥߥƥ�����
100
+
101
+ �Ԥ���Ƭ�����ڡ����ޤ��ϥ��֤ǻϤޤäƤ���ȡ����ιԤ������ѤߤȤ��ư����ޤ���
102
+
103
+ *������
104
+
105
+ require 'cgi'
106
+
107
+ cgi = CGI::new
108
+ cgi.header
109
+
110
+ puts <<EOS
111
+ <html>
112
+ <head>
113
+ <title>Hello!</title>
114
+ </head>
115
+ <body>
116
+ <p>Hello!</p>
117
+ </body>
118
+ </html>
119
+ EOS
120
+
121
+ !ʸ���ν���
122
+
123
+ ��'��2�ĤǤϤ������ʬ�϶�Ĵ����ޤ���
124
+
125
+ ��'��3�ĤǤϤ������ʬ�Ϥ���˶�Ĵ����ޤ���
126
+
127
+ ��=��2�ĤǤϤ������ʬ�ϼ�����ˤʤ�ޤ���
128
+
129
+ *������
130
+
131
+ ���Τ褦�ˤ����''��Ĵ''�ˤʤ�ޤ���
132
+ �����ơ����Τ褦�ˤ����'''����˶�Ĵ'''����ޤ���
133
+ ==���뤤����==����ˡ����ä����⥵�ݡ��Ȥ��Ƥ��ޤ���
134
+
135
+ *������
136
+
137
+ ���Τ褦�ˤ����''��Ĵ''�ˤʤ�ޤ���
138
+ �����ơ����Τ褦�ˤ����'''����˶�Ĵ'''����ޤ���
139
+ ==���뤤����==����ˡ����ä����⥵�ݡ��Ȥ��Ƥ��ޤ���
140
+
141
+ !����
142
+
143
+ ��!�פ�Ԥ���Ƭ�˽񤯤ȸ��Ф��ˤʤ�ޤ���
144
+
145
+ ��!�פϰ�Ĥ���ϻ�Ĥޤǵ��Ҥ��뤳�Ȥ���ǽ�ǡ����줾��<H1>��<H6>���Ѵ�����ޤ���
146
+
147
+ *������
148
+
149
+ !����1
150
+ !!����2
151
+ !!!����3
152
+ !!!!����4
153
+ !!!!!����5
154
+
155
+ *������
156
+
157
+ !����1
158
+ !!����2
159
+ !!!����3
160
+ !!!!����4
161
+ !!!!!����5
162
+
163
+ !��ʿ��
164
+
165
+ �ޥ��ʥ������-�פ�Ԥ���Ƭ����4�Ľ񤯤ȿ�ʿ���ˤʤ�ޤ���
166
+
167
+ *������
168
+
169
+ ������������
170
+ ----
171
+ ������������
172
+
173
+ *������
174
+
175
+ ������������
176
+ ----
177
+ ������������
178
+
179
+ !�վ��
180
+
181
+ ��*�פ�Ԥ���Ƭ�˽񤯤Ȳվ�񤭤ˤʤ�ޤ���
182
+
183
+ ��*�פϰ�Ĥ��黰�Ĥޤǵ��Ҥ��뤳�Ȥ���ǽ������Ҥˤ��뤳�Ȥ�Ǥ��ޤ���
184
+
185
+ ��#�פ�Ԥ���Ƭ�˽񤯤��ֹ��դ��βվ�񤭤ˤʤ�ޤ���
186
+
187
+ *������
188
+
189
+ *�����ƥ�1
190
+ **�����ƥ�1.1
191
+ **�����ƥ�1.2
192
+ ***�����ƥ�1.2.1
193
+ ***�����ƥ�1.2.2
194
+ ***�����ƥ�1.2.3
195
+ **�����ƥ�1.3
196
+ **�����ƥ�1.4
197
+ *�����ƥ�2
198
+
199
+ #����1
200
+ #����2
201
+ ##����2.1
202
+ ##����2.2
203
+ ##����2.3
204
+ #����3
205
+ ##����3.1
206
+ ###����3.1.1
207
+ ###����3.1.2
208
+
209
+ *������
210
+
211
+ *�����ƥ�1
212
+ **�����ƥ�1.1
213
+ **�����ƥ�1.2
214
+ ***�����ƥ�1.2.1
215
+ ***�����ƥ�1.2.2
216
+ ***�����ƥ�1.2.3
217
+ **�����ƥ�1.3
218
+ **�����ƥ�1.4
219
+ *�����ƥ�2
220
+
221
+ #����1
222
+ #����2
223
+ ##����2.1
224
+ ##����2.2
225
+ ##����2.3
226
+ #����3
227
+ ##����3.1
228
+ ###����3.1.1
229
+ ###����3.1.2
230
+
231
+ !����
232
+
233
+ ��"�פ�Ԥ���Ƭ������Ľ񤯤Ȱ��Ѥˤʤ�ޤ���
234
+
235
+ *������
236
+
237
+ ""����ϰ��ѤǤ���
238
+ ""����˰��Ѥ��ޤ���
239
+ ""³���ư��Ѥ��ޤ������Ѥ�Ϣ³�����硢
240
+ ""���Τ褦�˰�Ĥΰ��ѤȤ���
241
+ ""Ÿ������ޤ���
242
+
243
+ *������
244
+
245
+ ""����ϰ��ѤǤ���
246
+ ""����˰��Ѥ��ޤ���
247
+ ""³���ư��Ѥ��ޤ������Ѥ�Ϣ³�����硢
248
+ ""���Τ褦�˰�Ĥΰ��ѤȤ���
249
+ ""Ÿ������ޤ���
250
+
251
+ !�Ѹ����
252
+
253
+ �������:�פ�Ԥ���Ƭ�˽񤭡�³�����Ѹ�:����ʸ�Ȥ�����Ѹ����ˤʤ�ޤ���
254
+
255
+ *������
256
+
257
+ :���:apple
258
+ :�����:gorilla
259
+ :�饯��:camel
260
+
261
+ *������
262
+
263
+ :���:apple
264
+ :�����:gorilla
265
+ :�饯��:camel
266
+
267
+
268
+
269
+ ɽ(�ơ��֥�)�ϡ�||�פǻϤ�ޤ���
270
+ ����ι��ܤ�Ƭ�ˡ�!�פ�Ĥ��뤳�Ȥˤ�긫�Ф�����ˤʤ�ޤ���
271
+ �Ԥ�Ϣ��ˤϡ�^�פ����Ϣ��ˤϡ�>�פ�Ϣ�뤷��������������ι���Ƭ�ˤĤ��Ƥ���������
272
+
273
+ *������
274
+
275
+ ||!�Ը��Ф����󸫽Ф�||!��-A||!��-B||!��-C||!>��-D-E�ʲ�Ϣ���
276
+ ||!��-1||A1||B1||^C1-C2�ʽ�Ϣ���||D1||E1
277
+ ||!��-2||A2||B2||^>D2-E2-D3-E3�ʽIJ�Ϣ���
278
+ ||!��-3||>>A3-C3�ʲ���Ϣ���
279
+
280
+ *������
281
+
282
+ ||!�Ը��Ф����󸫽Ф�||!��-A||!��-B||!��-C||!>��-D-E�ʲ�Ϣ���
283
+ ||!��-1||A1||B1||^C1-C2�ʽ�Ϣ���||D1||E1
284
+ ||!��-2||A2||B2||^>D2-E2-D3-E3�ʽIJ�Ϣ���
285
+ ||!��-3||>>A3-C3�ʲ���Ϣ���
286
+
287
+ !�����ȹ�
288
+
289
+ ��//�פ���Ƭ�ˤ���Ԥϥ����ȹԤˤʤꡢ���Ϥ���ʤ��ʤ�ޤ���
290
+
291
+ *������
292
+
293
+ // �����ϥ����ȤǤ���
294
+
295
+ *�������ɽ������ޤ����
296
+ // �����ϥ����ȤǤ���
297
+
298
+ !�ץ饰����
299
+
300
+ ��{����Ĥȡ�}����ĤǰϤ�ȥץ饰�����ƤӽФ����Ȥ��Ǥ��ޤ���
301
+ �ѥ�᡼����ʣ���Ԥ�ʬ���ƽ񤯤��Ȥ��ǽ�Ǥ���
302
+ �ץ饰����Τߤ�ñ�ȹԤ˽񤤤����ϥ֥��å��ץ饰����ˤʤꡢ�����<p>��</p>���դ��ʤ��ʤ�ޤ���
303
+
304
+ *������
305
+
306
+ {{recent(3)}}
307
+
308
+ *ʣ���Ե�����
309
+
310
+ {{pre(
311
+ �ѥ�᡼����
312
+ �ѥ�᡼����
313
+ �ѥ�᡼����
314
+ )}}
data/bin/hikidoc ADDED
@@ -0,0 +1,53 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'hikidoc'
4
+ require 'optparse'
5
+ require 'erb'
6
+
7
+ HTML_TEMPLATE = <<EOS
8
+ <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
9
+ <html>
10
+ <head>
11
+ <title><%=title%></title>
12
+ </head>
13
+ <body>
14
+ <%=body%>
15
+ </body>
16
+ </html>
17
+ EOS
18
+
19
+ opt = {}
20
+ ARGV.options do |o|
21
+ o.banner = "Usage: #$0 [OPTIONS] FILE"
22
+
23
+ # fragment mode
24
+ o.on('--fragment', '-f',
25
+ 'Output HTML fragments only') do
26
+ opt[:fragment] = true
27
+ end
28
+ o.on('--template=VAL', '-t',
29
+ 'Specify a HTML template file') do |v|
30
+ opt[:template] = File.read(v)
31
+ end
32
+
33
+ o.parse!
34
+ end
35
+
36
+ opt[:template] ||= HTML_TEMPLATE
37
+
38
+ case ARGV.size
39
+ when 0
40
+ title, txt = '-', $stdin.read
41
+ when 1
42
+ title, txt = ARGV[0], File.read(ARGV[0])
43
+ else
44
+ usage
45
+ end
46
+
47
+ body = HikiDoc.to_html(txt)
48
+
49
+ if opt[:fragment]
50
+ puts body
51
+ else
52
+ puts ERB.new(opt[:template]).result(binding)
53
+ end
data/lib/hikidoc.rb ADDED
@@ -0,0 +1,902 @@
1
+ # Copyright (c) 2005, Kazuhiko <kazuhiko@fdiary.net>
2
+ # Copyright (c) 2007 Minero Aoki
3
+ # All rights reserved.
4
+ #
5
+ # Redistribution and use in source and binary forms, with or without
6
+ # modification, are permitted provided that the following conditions are
7
+ # met:
8
+ #
9
+ # * Redistributions of source code must retain the above copyright
10
+ # notice, this list of conditions and the following disclaimer.
11
+ # * Redistributions in binary form must reproduce the above
12
+ # copyright notice, this list of conditions and the following
13
+ # disclaimer in the documentation and/or other materials provided
14
+ # with the distribution.
15
+ # * Neither the name of the HikiDoc nor the names of its
16
+ # contributors may be used to endorse or promote products derived
17
+ # from this software without specific prior written permission.
18
+ #
19
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20
+ # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21
+ # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22
+ # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23
+ # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24
+ # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26
+ # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27
+ # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28
+ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29
+ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
+
31
+ require "stringio"
32
+ require "strscan"
33
+ require "uri"
34
+ begin
35
+ require "syntax/convertors/html"
36
+ rescue LoadError
37
+ end
38
+
39
+ class HikiDoc
40
+ VERSION = "0.0.1" # FIXME
41
+
42
+ class Error < StandardError
43
+ end
44
+
45
+ class UnexpectedError < Error
46
+ end
47
+
48
+ def HikiDoc.to_html(src, options = {})
49
+ new(HTMLOutput.new(">"), options).compile(src)
50
+ end
51
+
52
+ def HikiDoc.to_xhtml(src, options = {})
53
+ new(HTMLOutput.new(" />"), options).compile(src)
54
+ end
55
+
56
+ def initialize(output, options = {})
57
+ @output = output
58
+ @options = default_options.merge(options)
59
+ @header_re = nil
60
+ @level = options[:level] || 1
61
+ @plugin_syntax = options[:plugin_syntax] || method(:valid_plugin_syntax?)
62
+ end
63
+
64
+ def compile(src)
65
+ @output.reset
66
+ escape_plugin_blocks(src) {|escaped|
67
+ compile_blocks escaped
68
+ @output.finish
69
+ }
70
+ end
71
+
72
+ # for backward compatibility
73
+ def to_html
74
+ $stderr.puts("warning: HikiDoc#to_html is deprecated. Please use HikiDoc.to_html or HikiDoc.to_xhtml instead.")
75
+ self.class.to_html(@output, @options)
76
+ end
77
+
78
+ private
79
+
80
+ def default_options
81
+ {
82
+ :allow_bracket_inline_image => true,
83
+ :use_wiki_name => true,
84
+ :use_not_wiki_name => true,
85
+ }
86
+ end
87
+
88
+ #
89
+ # Plugin
90
+ #
91
+
92
+ def valid_plugin_syntax?(code)
93
+ /['"]/ !~ code.gsub(/'(?:[^\\']+|\\.)*'|"(?:[^\\"]+|\\.)*"/m, "")
94
+ end
95
+
96
+ def escape_plugin_blocks(text)
97
+ s = StringScanner.new(text)
98
+ buf = ""
99
+ @plugin_blocks = []
100
+ while chunk = s.scan_until(/\{\{/)
101
+ tail = chunk[-2, 2]
102
+ chunk[-2, 2] = ""
103
+ buf << chunk
104
+ # plugin
105
+ if block = extract_plugin_block(s)
106
+ @plugin_blocks.push block
107
+ buf << "\0#{@plugin_blocks.size - 1}\0"
108
+ else
109
+ buf << "{{"
110
+ end
111
+ end
112
+ buf << s.rest
113
+ yield(buf)
114
+ end
115
+
116
+ def restore_plugin_block(str)
117
+ str.gsub(/\0(\d+)\0/) {
118
+ "{{" + plugin_block($1.to_i) + "}}"
119
+ }
120
+ end
121
+
122
+ def evaluate_plugin_block(str, buf = nil)
123
+ buf ||= @output.container
124
+ str.split(/(\0\d+\0)/).each do |s|
125
+ if s[0, 1] == "\0" and s[-1, 1] == "\0"
126
+ buf << @output.inline_plugin(plugin_block(s[1..-2].to_i))
127
+ else
128
+ buf << @output.text(s)
129
+ end
130
+ end
131
+ buf
132
+ end
133
+
134
+ def plugin_block(id)
135
+ @plugin_blocks[id] or raise UnexpectedError, "must not happen: #{id.inspect}"
136
+ end
137
+
138
+ def extract_plugin_block(s)
139
+ pos = s.pos
140
+ buf = ""
141
+ while chunk = s.scan_until(/\}\}/)
142
+ buf << chunk
143
+ buf.chomp!("}}")
144
+ if @plugin_syntax.call(buf)
145
+ return buf
146
+ end
147
+ buf << "}}"
148
+ end
149
+ s.pos = pos
150
+ nil
151
+ end
152
+
153
+ #
154
+ # Block Level
155
+ #
156
+
157
+ def compile_blocks(src)
158
+ f = LineInput.new(StringIO.new(src))
159
+ while line = f.peek
160
+ case line
161
+ when COMMENT_RE
162
+ f.gets
163
+ when HEADER_RE
164
+ compile_header f.gets
165
+ when HRULE_RE
166
+ f.gets
167
+ compile_hrule
168
+ when LIST_RE
169
+ compile_list f
170
+ when DLIST_RE
171
+ compile_dlist f
172
+ when TABLE_RE
173
+ compile_table f
174
+ when BLOCKQUOTE_RE
175
+ compile_blockquote f
176
+ when INDENTED_PRE_RE
177
+ compile_indented_pre f
178
+ when BLOCK_PRE_OPEN_RE
179
+ compile_block_pre f
180
+ else
181
+ if /^$/ =~ line
182
+ f.gets
183
+ next
184
+ end
185
+ compile_paragraph f
186
+ end
187
+ end
188
+ end
189
+
190
+ COMMENT_RE = %r<\A//>
191
+
192
+ def skip_comments(f)
193
+ f.while_match(COMMENT_RE) do |line|
194
+ end
195
+ end
196
+
197
+ HEADER_RE = /\A!+/
198
+
199
+ def compile_header(line)
200
+ @header_re ||= /\A!{1,#{7 - @level}}/
201
+ level = @level + (line.slice!(@header_re).size - 1)
202
+ title = strip(line)
203
+ @output.headline level, compile_inline(title)
204
+ end
205
+
206
+ HRULE_RE = /\A----$/
207
+
208
+ def compile_hrule
209
+ @output.hrule
210
+ end
211
+
212
+ ULIST = "*"
213
+ OLIST = "#"
214
+ LIST_RE = /\A#{Regexp.union(ULIST, OLIST)}+/
215
+
216
+ def compile_list(f)
217
+ typestack = []
218
+ level = 0
219
+ @output.list_begin
220
+ f.while_match(LIST_RE) do |line|
221
+ list_type = (line[0,1] == ULIST ? "ul" : "ol")
222
+ new_level = line.slice(LIST_RE).size
223
+ item = strip(line.sub(LIST_RE, ""))
224
+ if new_level > level
225
+ (new_level - level).times do
226
+ typestack.push list_type
227
+ @output.list_open list_type
228
+ @output.listitem_open
229
+ end
230
+ @output.listitem compile_inline(item)
231
+ elsif new_level < level
232
+ (level - new_level).times do
233
+ @output.listitem_close
234
+ @output.list_close typestack.pop
235
+ end
236
+ @output.listitem_close
237
+ @output.listitem_open
238
+ @output.listitem compile_inline(item)
239
+ elsif list_type == typestack.last
240
+ @output.listitem_close
241
+ @output.listitem_open
242
+ @output.listitem compile_inline(item)
243
+ else
244
+ @output.listitem_close
245
+ @output.list_close typestack.pop
246
+ @output.list_open list_type
247
+ @output.listitem_open
248
+ @output.listitem compile_inline(item)
249
+ typestack.push list_type
250
+ end
251
+ level = new_level
252
+ skip_comments f
253
+ end
254
+ level.times do
255
+ @output.listitem_close
256
+ @output.list_close typestack.pop
257
+ end
258
+ @output.list_end
259
+ end
260
+
261
+ DLIST_RE = /\A:/
262
+
263
+ def compile_dlist(f)
264
+ @output.dlist_open
265
+ f.while_match(DLIST_RE) do |line|
266
+ dt, dd = split_dlitem(line.sub(DLIST_RE, ""))
267
+ @output.dlist_item compile_inline(dt), compile_inline(dd)
268
+ skip_comments f
269
+ end
270
+ @output.dlist_close
271
+ end
272
+
273
+ def split_dlitem(line)
274
+ re = /\A((?:#{BRACKET_LINK_RE}|.)*?):/o
275
+ if m = re.match(line)
276
+ return m[1], m.post_match
277
+ else
278
+ return line, ""
279
+ end
280
+ end
281
+
282
+ TABLE_RE = /\A\|\|/
283
+
284
+ def compile_table(f)
285
+ lines = []
286
+ f.while_match(TABLE_RE) do |line|
287
+ lines.push line
288
+ skip_comments f
289
+ end
290
+ @output.table_open
291
+ lines.each do |line|
292
+ @output.table_record_open
293
+ split_columns(line.sub(TABLE_RE, "")).each do |col|
294
+ mid = col.sub!(/\A!/, "") ? "table_head" : "table_data"
295
+ span = col.slice!(/\A[\^>]*/)
296
+ rs = span_count(span, "^")
297
+ cs = span_count(span, ">")
298
+ @output.__send__(mid, compile_inline(col), rs, cs)
299
+ end
300
+ @output.table_record_close
301
+ end
302
+ @output.table_close
303
+ end
304
+
305
+ def split_columns(str)
306
+ cols = str.split(/\|\|/)
307
+ cols.pop if cols.last.chomp.empty?
308
+ cols
309
+ end
310
+
311
+ def span_count(str, ch)
312
+ c = str.count(ch)
313
+ c == 0 ? nil : c + 1
314
+ end
315
+
316
+ BLOCKQUOTE_RE = /\A""[ \t]?/
317
+
318
+ def compile_blockquote(f)
319
+ @output.blockquote_open
320
+ lines = []
321
+ f.while_match(BLOCKQUOTE_RE) do |line|
322
+ lines.push line.sub(BLOCKQUOTE_RE, "")
323
+ skip_comments f
324
+ end
325
+ compile_blocks lines.join("")
326
+ @output.blockquote_close
327
+ end
328
+
329
+ INDENTED_PRE_RE = /\A[ \t]/
330
+
331
+ def compile_indented_pre(f)
332
+ lines = f.span(INDENTED_PRE_RE)\
333
+ .map {|line| rstrip(line.sub(INDENTED_PRE_RE, "")) }\
334
+ .map {|line| @output.text(line) }
335
+ @output.preformatted restore_plugin_block(lines.join("\n"))
336
+ end
337
+
338
+ BLOCK_PRE_OPEN_RE = /\A<<<\s*(\w+)?/
339
+ BLOCK_PRE_CLOSE_RE = /\A>>>/
340
+
341
+ def compile_block_pre(f)
342
+ m = BLOCK_PRE_OPEN_RE.match(f.gets) or raise UnexpectedError, "must not happen"
343
+ str = restore_plugin_block(f.break(BLOCK_PRE_CLOSE_RE).join.chomp)
344
+ f.gets
345
+ @output.block_preformatted(str, m[1])
346
+ end
347
+
348
+ BLANK = /\A$/
349
+ PARAGRAPH_END_RE = Regexp.union(BLANK,
350
+ HEADER_RE, HRULE_RE, LIST_RE, DLIST_RE,
351
+ BLOCKQUOTE_RE, TABLE_RE,
352
+ INDENTED_PRE_RE, BLOCK_PRE_OPEN_RE)
353
+
354
+ def compile_paragraph(f)
355
+ lines = f.break(PARAGRAPH_END_RE)\
356
+ .reject {|line| COMMENT_RE =~ line }
357
+ if lines.size == 1 and /\A\0(\d+)\0\z/ =~ strip(lines[0])
358
+ @output.block_plugin plugin_block($1.to_i)
359
+ else
360
+ line_buffer = @output.container(:paragraph)
361
+ lines.each_with_index do |line, i|
362
+ buffer = @output.container
363
+ line_buffer << buffer
364
+ compile_inline(lstrip(line).chomp, buffer)
365
+ end
366
+ @output.paragraph(line_buffer)
367
+ end
368
+ end
369
+
370
+ #
371
+ # Inline Level
372
+ #
373
+
374
+ BRACKET_LINK_RE = /\[\[.+?\]\]/
375
+ URI_RE = /(?:https?|ftp|file|mailto):[A-Za-z0-9;\/?:@&=+$,\-_.!~*\'()#%]+/
376
+ WIKI_NAME_RE = /\b(?:[A-Z]+[a-z\d]+){2,}\b/
377
+
378
+ def inline_syntax_re
379
+ if @options[:use_wiki_name]
380
+ if @options[:use_not_wiki_name]
381
+ / (#{BRACKET_LINK_RE})
382
+ | (#{URI_RE})
383
+ | (#{MODIFIER_RE})
384
+ | (\^?#{WIKI_NAME_RE})
385
+ /xo
386
+ else
387
+ / (#{BRACKET_LINK_RE})
388
+ | (#{URI_RE})
389
+ | (#{MODIFIER_RE})
390
+ | (#{WIKI_NAME_RE})
391
+ /xo
392
+ end
393
+ else
394
+ / (#{BRACKET_LINK_RE})
395
+ | (#{URI_RE})
396
+ | (#{MODIFIER_RE})
397
+ /xo
398
+ end
399
+ end
400
+
401
+ def compile_inline(str, buf = nil)
402
+ buf ||= @output.container
403
+ re = inline_syntax_re
404
+ pending_str = nil
405
+ while m = re.match(str)
406
+ str = m.post_match
407
+
408
+ link, uri, mod, wiki_name = m[1, 4]
409
+ if wiki_name and wiki_name[0, 1] == "^"
410
+ pending_str = m.pre_match + wiki_name[1..-1]
411
+ next
412
+ end
413
+
414
+ pre_str = "#{pending_str}#{m.pre_match}"
415
+ pending_str = nil
416
+ evaluate_plugin_block(pre_str, buf)
417
+ compile_inline_markup(buf, link, uri, mod, wiki_name)
418
+ end
419
+ evaluate_plugin_block(pending_str || str, buf)
420
+ buf
421
+ end
422
+
423
+ def compile_inline_markup(buf, link, uri, mod, wiki_name)
424
+ case
425
+ when link
426
+ buf << compile_bracket_link(link[2...-2])
427
+ when uri
428
+ buf << compile_uri_autolink(uri)
429
+ when mod
430
+ buf << compile_modifier(mod)
431
+ when wiki_name
432
+ buf << @output.wiki_name(wiki_name)
433
+ else
434
+ raise UnexpectedError, "must not happen"
435
+ end
436
+ end
437
+
438
+ def compile_bracket_link(link)
439
+ if m = /\A(?>[^|\\]+|\\.)*\|/.match(link)
440
+ title = m[0].chop
441
+ uri = m.post_match
442
+ fixed_uri = fix_uri(uri)
443
+ if can_image_link?(uri)
444
+ @output.image_hyperlink(fixed_uri, title)
445
+ else
446
+ @output.hyperlink(fixed_uri, compile_modifier(title))
447
+ end
448
+ else
449
+ fixed_link = fix_uri(link)
450
+ if can_image_link?(link)
451
+ @output.image_hyperlink(fixed_link)
452
+ else
453
+ @output.hyperlink(fixed_link, @output.text(link))
454
+ end
455
+ end
456
+ end
457
+
458
+ def can_image_link?(uri)
459
+ image?(uri) and @options[:allow_bracket_inline_image]
460
+ end
461
+
462
+ def compile_uri_autolink(uri)
463
+ if image?(uri)
464
+ @output.image_hyperlink(fix_uri(uri))
465
+ else
466
+ @output.hyperlink(fix_uri(uri), @output.text(uri))
467
+ end
468
+ end
469
+
470
+ def fix_uri(uri)
471
+ if %r|://| !~ uri and /\Amailto:/ !~ uri
472
+ uri.sub(/\A\w+:/, "")
473
+ else
474
+ uri
475
+ end
476
+ end
477
+
478
+ IMAGE_EXTS = %w(.jpg .jpeg .gif .png)
479
+
480
+ def image?(uri)
481
+ IMAGE_EXTS.include?(File.extname(uri).downcase)
482
+ end
483
+
484
+ STRONG = "'''"
485
+ EM = "''"
486
+ DEL = "=="
487
+
488
+ STRONG_RE = /'''.+?'''/
489
+ EM_RE = /''.+?''/
490
+ DEL_RE = /==.+?==/
491
+
492
+ MODIFIER_RE = Regexp.union(STRONG_RE, EM_RE, DEL_RE)
493
+
494
+ MODTAG = {
495
+ STRONG => "strong",
496
+ EM => "em",
497
+ DEL => "del"
498
+ }
499
+
500
+ def compile_modifier(str)
501
+ buf = @output.container
502
+ while m = / (#{MODIFIER_RE})
503
+ /xo.match(str)
504
+ evaluate_plugin_block(m.pre_match, buf)
505
+ case
506
+ when chunk = m[1]
507
+ mod, s = split_mod(chunk)
508
+ mid = MODTAG[mod]
509
+ buf << @output.__send__(mid, compile_modifier(s))
510
+ else
511
+ raise UnexpectedError, "must not happen"
512
+ end
513
+ str = m.post_match
514
+ end
515
+ evaluate_plugin_block(str, buf)
516
+ buf
517
+ end
518
+
519
+ def split_mod(str)
520
+ case str
521
+ when /\A'''/
522
+ return str[0, 3], str[3...-3]
523
+ when /\A''/
524
+ return str[0, 2], str[2...-2]
525
+ when /\A==/
526
+ return str[0, 2], str[2...-2]
527
+ else
528
+ raise UnexpectedError, "must not happen: #{str.inspect}"
529
+ end
530
+ end
531
+
532
+ def strip(str)
533
+ rstrip(lstrip(str))
534
+ end
535
+
536
+ def rstrip(str)
537
+ str.sub(/[ \t\r\n\v\f]+\z/, "")
538
+ end
539
+
540
+ def lstrip(str)
541
+ str.sub(/\A[ \t\r\n\v\f]+/, "")
542
+ end
543
+
544
+
545
+ class HTMLOutput
546
+ def initialize(suffix = " />")
547
+ @suffix = suffix
548
+ @f = nil
549
+ end
550
+
551
+ def reset
552
+ @f = StringIO.new
553
+ end
554
+
555
+ def finish
556
+ @f.string
557
+ end
558
+
559
+ def container(_for=nil)
560
+ case _for
561
+ when :paragraph
562
+ []
563
+ else
564
+ ""
565
+ end
566
+ end
567
+
568
+ #
569
+ # Procedures
570
+ #
571
+
572
+ def headline(level, title)
573
+ @f.puts "<h#{level}>#{title}</h#{level}>"
574
+ end
575
+
576
+ def hrule
577
+ @f.puts "<hr#{@suffix}"
578
+ end
579
+
580
+ def list_begin
581
+ end
582
+
583
+ def list_end
584
+ @f.puts
585
+ end
586
+
587
+ def list_open(type)
588
+ @f.puts "<#{type}>"
589
+ end
590
+
591
+ def list_close(type)
592
+ @f.print "</#{type}>"
593
+ end
594
+
595
+ def listitem_open
596
+ @f.print "<li>"
597
+ end
598
+
599
+ def listitem_close
600
+ @f.puts "</li>"
601
+ end
602
+
603
+ def listitem(item)
604
+ @f.print item
605
+ end
606
+
607
+ def dlist_open
608
+ @f.puts "<dl>"
609
+ end
610
+
611
+ def dlist_close
612
+ @f.puts "</dl>"
613
+ end
614
+
615
+ def dlist_item(dt, dd)
616
+ case
617
+ when dd.empty?
618
+ @f.puts "<dt>#{dt}</dt>"
619
+ when dt.empty?
620
+ @f.puts "<dd>#{dd}</dd>"
621
+ else
622
+ @f.puts "<dt>#{dt}</dt>"
623
+ @f.puts "<dd>#{dd}</dd>"
624
+ end
625
+ end
626
+
627
+ def table_open
628
+ @f.puts %Q(<table border="1">)
629
+ end
630
+
631
+ def table_close
632
+ @f.puts "</table>"
633
+ end
634
+
635
+ def table_record_open
636
+ @f.print "<tr>"
637
+ end
638
+
639
+ def table_record_close
640
+ @f.puts "</tr>"
641
+ end
642
+
643
+ def table_head(item, rs, cs)
644
+ @f.print "<th#{tdattr(rs, cs)}>#{item}</th>"
645
+ end
646
+
647
+ def table_data(item, rs, cs)
648
+ @f.print "<td#{tdattr(rs, cs)}>#{item}</td>"
649
+ end
650
+
651
+ def tdattr(rs, cs)
652
+ buf = ""
653
+ buf << %Q( rowspan="#{rs}") if rs
654
+ buf << %Q( colspan="#{cs}") if cs
655
+ buf
656
+ end
657
+ private :tdattr
658
+
659
+ def blockquote_open
660
+ @f.print "<blockquote>"
661
+ end
662
+
663
+ def blockquote_close
664
+ @f.puts "</blockquote>"
665
+ end
666
+
667
+ def block_preformatted(str, info)
668
+ syntax = info ? info.downcase : nil
669
+ if syntax
670
+ begin
671
+ convertor = Syntax::Convertors::HTML.for_syntax(syntax)
672
+ @f.puts convertor.convert(str)
673
+ return
674
+ rescue NameError, RuntimeError
675
+ end
676
+ end
677
+ preformatted(text(str))
678
+ end
679
+
680
+ def preformatted(str)
681
+ @f.print "<pre>"
682
+ @f.print str
683
+ @f.puts "</pre>"
684
+ end
685
+
686
+ def paragraph(lines)
687
+ @f.puts "<p>#{lines.join("\n")}</p>"
688
+ end
689
+
690
+ def block_plugin(str)
691
+ @f.puts %Q(<div class="plugin">{{#{escape_html(str)}}}</div>)
692
+ end
693
+
694
+ #
695
+ # Functions
696
+ #
697
+
698
+ def hyperlink(uri, title)
699
+ %Q(<a href="#{escape_html_param(uri)}">#{title}</a>)
700
+ end
701
+
702
+ def wiki_name(name)
703
+ hyperlink(name, text(name))
704
+ end
705
+
706
+ def image_hyperlink(uri, alt = nil)
707
+ alt ||= uri.split(/\//).last
708
+ alt = escape_html(alt)
709
+ %Q(<img src="#{escape_html_param(uri)}" alt="#{alt}"#{@suffix})
710
+ end
711
+
712
+ def strong(item)
713
+ "<strong>#{item}</strong>"
714
+ end
715
+
716
+ def em(item)
717
+ "<em>#{item}</em>"
718
+ end
719
+
720
+ def del(item)
721
+ "<del>#{item}</del>"
722
+ end
723
+
724
+ def text(str)
725
+ escape_html(str)
726
+ end
727
+
728
+ def inline_plugin(src)
729
+ %Q(<span class="plugin">{{#{src}}}</span>)
730
+ end
731
+
732
+ #
733
+ # Utilities
734
+ #
735
+
736
+ def escape_html_param(str)
737
+ escape_quote(escape_html(str))
738
+ end
739
+
740
+ def escape_html(text)
741
+ text.gsub(/&/, "&amp;").gsub(/</, "&lt;").gsub(/>/, "&gt;")
742
+ end
743
+
744
+ def unescape_html(text)
745
+ text.gsub(/&gt;/, ">").gsub(/&lt;/, "<").gsub(/&amp;/, "&")
746
+ end
747
+
748
+ def escape_quote(text)
749
+ text.gsub(/"/, "&quot;")
750
+ end
751
+ end
752
+
753
+
754
+ class LineInput
755
+ def initialize(f)
756
+ @input = f
757
+ @buf = []
758
+ @lineno = 0
759
+ @eof_p = false
760
+ end
761
+
762
+ def inspect
763
+ "\#<#{self.class} file=#{@input.inspect} line=#{lineno()}>"
764
+ end
765
+
766
+ def eof?
767
+ @eof_p
768
+ end
769
+
770
+ def lineno
771
+ @lineno
772
+ end
773
+
774
+ def gets
775
+ unless @buf.empty?
776
+ @lineno += 1
777
+ return @buf.pop
778
+ end
779
+ return nil if @eof_p # to avoid ARGF blocking.
780
+ line = @input.gets
781
+ line = line.sub(/\r\n/, "\n") if line
782
+ @eof_p = line.nil?
783
+ @lineno += 1
784
+ line
785
+ end
786
+
787
+ def ungets(line)
788
+ return unless line
789
+ @lineno -= 1
790
+ @buf.push line
791
+ line
792
+ end
793
+
794
+ def peek
795
+ line = gets()
796
+ ungets line if line
797
+ line
798
+ end
799
+
800
+ def next?
801
+ peek() ? true : false
802
+ end
803
+
804
+ def skip_blank_lines
805
+ n = 0
806
+ while line = gets()
807
+ unless line.strip.empty?
808
+ ungets line
809
+ return n
810
+ end
811
+ n += 1
812
+ end
813
+ n
814
+ end
815
+
816
+ def gets_if(re)
817
+ line = gets()
818
+ if not line or not (re =~ line)
819
+ ungets line
820
+ return nil
821
+ end
822
+ line
823
+ end
824
+
825
+ def gets_unless(re)
826
+ line = gets()
827
+ if not line or re =~ line
828
+ ungets line
829
+ return nil
830
+ end
831
+ line
832
+ end
833
+
834
+ def each
835
+ while line = gets()
836
+ yield line
837
+ end
838
+ end
839
+
840
+ def while_match(re)
841
+ while line = gets()
842
+ unless re =~ line
843
+ ungets line
844
+ return
845
+ end
846
+ yield line
847
+ end
848
+ nil
849
+ end
850
+
851
+ def getlines_while(re)
852
+ buf = []
853
+ while_match(re) do |line|
854
+ buf.push line
855
+ end
856
+ buf
857
+ end
858
+
859
+ alias span getlines_while # from Haskell
860
+
861
+ def until_match(re)
862
+ while line = gets()
863
+ if re =~ line
864
+ ungets line
865
+ return
866
+ end
867
+ yield line
868
+ end
869
+ nil
870
+ end
871
+
872
+ def getlines_until(re)
873
+ buf = []
874
+ until_match(re) do |line|
875
+ buf.push line
876
+ end
877
+ buf
878
+ end
879
+
880
+ alias break getlines_until # from Haskell
881
+
882
+ def until_terminator(re)
883
+ while line = gets()
884
+ return if re =~ line # discard terminal line
885
+ yield line
886
+ end
887
+ nil
888
+ end
889
+
890
+ def getblock(term_re)
891
+ buf = []
892
+ until_terminator(term_re) do |line|
893
+ buf.push line
894
+ end
895
+ buf
896
+ end
897
+ end
898
+ end
899
+
900
+ if __FILE__ == $0
901
+ puts HikiDoc.to_html(ARGF.read(nil))
902
+ end