hikidoc 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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