markita 1.1.210831 → 2.0.210906

Sign up to get free protection for your applications and to get access to all the features.
data/lib/markita/base.rb CHANGED
@@ -1,4 +1,33 @@
1
1
  module Markita
2
+ class Preprocess
3
+ def initialize(file)
4
+ @file = (file.is_a? String)? StringIO.new(file) : file
5
+ @regx = @template = nil
6
+ end
7
+
8
+ def gets
9
+ if line = @file.gets
10
+ case line
11
+ when @regx
12
+ line = @template if @template
13
+ $~.named_captures.each do |name, value|
14
+ line = line.gsub("&#{name.downcase};", value)
15
+ line = line.gsub("&#{name.upcase};", CGI.escape(value))
16
+ end
17
+ when %r(^! regx = /(.*)/$)
18
+ @regx = Regexp.new $1
19
+ line = gets
20
+ when %r(^! template = "(.*)"$)
21
+ @template = $1+"\n"
22
+ line = gets
23
+ else
24
+ @regx &&= (@template=nil)
25
+ end
26
+ end
27
+ line
28
+ end
29
+ end
30
+
2
31
  class Base < Sinatra::Base
3
32
  set bind: OPTIONS&.bind || '0.0.0.0'
4
33
  set port: OPTIONS&.port || '8080'
@@ -19,62 +48,43 @@ class Base < Sinatra::Base
19
48
  end
20
49
 
21
50
  def Base.header(key)
22
- <<-HEADER
23
- <!DOCTYPE html>
24
- <html>
25
- <head>
26
- <title>#{key}</title>
27
- #{HEADER_LINKS}</head>
28
- <body>
29
-
51
+ <<~HEADER
52
+ <!DOCTYPE html>
53
+ <html>
54
+ <head>
55
+ <title>#{key}</title>
56
+ #{HEADER_LINKS}</head>
57
+ <body>
30
58
  HEADER
31
59
  end
32
60
 
33
61
  def Base.footer
34
- <<-FOOTER
35
-
36
- </body>
37
- </html>
62
+ <<~FOOTER
63
+ </body>
64
+ </html>
38
65
  FOOTER
39
66
  end
40
67
 
41
- def Base.process(text, procs=POSTPROCESS)
42
- val,string,_ = {},'',nil
43
- text.each_line do |line|
44
- line.chomp!
45
- case line
46
- when ''
47
- val.clear
48
- when %r(^<(p>)?!\p{Pd}+ (.*) \p{Pd}+(</p)?>$)
49
- directive = $2
50
- case directive
51
- when %r(^(\w+): "(.*)"$)
52
- val[$1.to_sym] = $2
53
- next
54
- when %r(^(\w+): /(.*)/)
55
- val[$1.to_sym] = Regexp.new $2
56
- next
57
- else
58
- line = directive.gsub('&lt;', '<').gsub('&gt;', '>')
59
- end
60
- else
61
- # set line to IDontCare if IDontCare gets set
62
- line=_ if procs.detect{_=_1[line, val]}
63
- end
64
- string << line << "\n"
65
- end
66
- return string
68
+ DEFAULT = lambda do |line, html, file, _, _|
69
+ html << line
70
+ file.gets
67
71
  end
68
72
 
69
- def Base.page(key)
70
- Base.header(key) + yield + Base.footer
73
+ def Base.page(key, f)
74
+ html,opt,file,line = '',{},Preprocess.new(f),Base.header(key)
75
+ fct,md = nil,nil
76
+ while line = (fct||DEFAULT)[line, html, file, opt, md]
77
+ fct = nil
78
+ Markdown::PARSER.each{|r,f| break if md=r.match(line) and fct=f}
79
+ end
80
+ html << Base.footer
81
+ html
71
82
  end
72
83
 
73
84
  get PAGE_KEY do |key|
74
85
  filepath = File.join ROOT, key+'.md'
75
86
  raise Sinatra::NotFound unless File.exist? filepath
76
- text = File.read(filepath).force_encoding('utf-8')
77
- Base.page(key){ Base.process markdown Base.process(text, PREPROCESS)}
87
+ File.open(filepath, 'r'){|f| Base.page key, f}
78
88
  end
79
89
 
80
90
  get IMAGE_PATH do |path, *_|
@@ -11,61 +11,10 @@ module Markita
11
11
  end
12
12
  NOT_FOUND = File.read PATH['not_found.html']
13
13
 
14
+ EMOJIS = Hash[*File.read(PATH['emojis.tsv']).split(/\s+/)]
15
+
14
16
  PAGE_KEY = %r{/(\w[\w\/\-]*\w)}
15
17
  IMAGE_PATH = %r{/(\w[\w\/\-]*\w\.((png)|(gif)))}
16
18
 
17
- PREPROCESS = [
18
- lambda do |line, val|
19
- case line
20
- when val[:regx]
21
- # Template/Substitutions
22
- template = val[:template] || line
23
- $~.named_captures.each do |name, value|
24
- template = template.gsub("&#{name};", value)
25
- template = template.gsub("&#{name.upcase};", CGI.escape(value))
26
- end
27
- template
28
- when /^```\s*(\w+)?$/
29
- $1 ? "~~~ #{$1}" : '~~~'
30
- else
31
- nil
32
- end
33
- end
34
- ]
35
-
36
- POSTPROCESS = [
37
- lambda do |line,_|
38
- case line
39
- when %r(^(\s*)<li>\[(x| )\] (.*)</li>$)
40
- # Task Lists
41
- spaces,x,item = $1,$2,$3
42
- li = (x=='x')?
43
- %q{<li style="list-style-type: '&#9745; '">} :
44
- %q{<li style="list-style-type: '&#9744; '">}
45
- spaces+li+item+"</li>"
46
- when %r(^<p>(\w+:\[\*?\w+\] )+\((\S+)\)</p>$)
47
- # One Line Forms
48
- action,method,form = $2,'get',[]
49
- line.scan(/(\w+):\[(\*)?(\w+)\] /).each do |field, pwd, name|
50
- type = (pwd)? 'password' : 'text'
51
- method = 'post' if pwd
52
- form << %Q{ #{field}:<input type="#{type}" name="#{name}">}
53
- end
54
- form.unshift %Q(<form action="#{action}" method="#{method}">)
55
- form.push %Q( <input type="submit">) if form.length==1
56
- form.push %Q(</form>)
57
- form.join("\n")
58
- when %r(^<p><img (src="[^"]*" alt=" [^"]* ") /></p>$)
59
- %Q(<img style="display: block; margin-left: auto; margin-right: auto;" #{$1} />)
60
- when %r(^<p><img (src="[^"]*" alt=" [^"]*") />$)
61
- %Q(<p><img style="float: left;" #{$1} />)
62
- when %r(^<p><img (src="[^"]*" alt="[^"]* ") />$)
63
- %Q(<p><img style="float: right;" #{$1} />)
64
- else
65
- nil
66
- end
67
- end
68
- ]
69
-
70
19
  START_TIME = Time.now
71
20
  end
@@ -0,0 +1,355 @@
1
+ module Markita
2
+ module Markdown
3
+ Ux = /_([^_]+)_/
4
+ U = lambda {|md| "<u>#{md[1]}</u>"}
5
+
6
+ Sx = /~([^~]+)~/
7
+ S = lambda {|md| "<s>#{md[1]}</s>"}
8
+
9
+ Ix = /"([^"]+)"/
10
+ I = lambda {|md| "<i>#{md[1]}</i>"}
11
+
12
+ Bx = /\*([^\*]+)\*/
13
+ B = lambda {|md| "<b>#{md[1]}</b>"}
14
+
15
+ CODEx = /`([^`]+)`/
16
+ CODE = lambda {|md| "<code>#{md[1]}</code>"}
17
+
18
+ Ax = /\[([^\[\]]+)\]\(([^()]+)\)/
19
+ A = lambda {|md| %Q(<a href="#{md[2]}">#{md[1]}</a>)}
20
+
21
+ URLx = %r(\[(https?://[\w\.\-\/\&\+\?\%]+)\])
22
+ URL = lambda {|md| %Q(<a href="#{md[1]}">#{md[1]}</a>)}
23
+
24
+ EMOJIx = /:(\w+):/
25
+ EMOJI = lambda {|md| (_=EMOJIS[md[1]])? "&\#x#{_};" : md[0]}
26
+
27
+ FOOTNOTEx = /\[\^(\d+)\](:)?/
28
+ FOOTNOTE = lambda do |md|
29
+ if md[2]
30
+ %Q(<a id="fn:#{md[1]}" href="\#fnref:#{md[1]}">#{md[1]}:</a>)
31
+ else
32
+ %Q(<a id="fnref:#{md[1]}" href="\#fn:#{md[1]}"><sup>#{md[1]}</sup></a>)
33
+ end
34
+ end
35
+
36
+ def Markdown.tag(line, regx, md2string, &block)
37
+ if md = regx.match(line)
38
+ pre_match = (block ? block.call(md.pre_match) : md.pre_match)
39
+ string = pre_match + md2string[md]
40
+ post_match = md.post_match
41
+ while md = regx.match(post_match)
42
+ pre_match = (block ? block.call(md.pre_match) : md.pre_match)
43
+ string << pre_match + md2string[md]
44
+ post_match = md.post_match
45
+ end
46
+ string << (block ? block.call(post_match) : post_match)
47
+ return string
48
+ end
49
+ return (block ? block.call(line) : line)
50
+ end
51
+
52
+ INLINE = lambda do |line|
53
+ string = Markdown.tag(line, CODEx, CODE) do |line|
54
+ Markdown.tag(line, Ax, A) do |line|
55
+ Markdown.tag(line, URLx, URL) do |line|
56
+ string = Markdown.tag(line, Bx, B)
57
+ string = Markdown.tag(string, Ix, I)
58
+ string = Markdown.tag(string, Sx, S)
59
+ string = Markdown.tag(string, Ux, U)
60
+ string = Markdown.tag(string, FOOTNOTEx, FOOTNOTE)
61
+ Markdown.tag(string, EMOJIx, EMOJI)
62
+ end
63
+ end
64
+ end
65
+ string.sub(/ $/,'<br>')
66
+ end
67
+
68
+ PARSER = Hash.new
69
+
70
+ # Empty
71
+ PARSER[/^$/] = lambda do |_, _, file, _, _|
72
+ file.gets
73
+ end
74
+
75
+ # Ordered list
76
+ ORDERED = /^\d+. (.*)$/
77
+ PARSER[ORDERED] = lambda do |line, html, file, opt, md|
78
+ html << "<ol#{opt[:attributes]}>\n"
79
+ opt.delete(:attributes)
80
+ while md
81
+ html << " <li>#{INLINE[md[1]]}</li>\n"
82
+ md = (line=file.gets)&.match ORDERED
83
+ end
84
+ html << "</ol>\n"
85
+ line
86
+ end
87
+
88
+ # Paragraph
89
+ PARAGRAPHS = /^[\[`*"~_]?\w/
90
+ PARSER[PARAGRAPHS] = lambda do |line, html, file, opt, md|
91
+ html << "<p#{opt[:attributes]}>\n"
92
+ opt.delete(:attributes)
93
+ while md
94
+ html << INLINE[line]
95
+ md = (line=file.gets)&.match PARAGRAPHS
96
+ end
97
+ html << "</p>\n"
98
+ line
99
+ end
100
+
101
+ # Unordered list
102
+ UNORDERED = /^[*] (.*)$/
103
+ PARSER[UNORDERED] = lambda do |line, html, file, opt, md|
104
+ html << "<ul#{opt[:attributes]}>\n"
105
+ opt.delete(:attributes)
106
+ while md
107
+ html << " <li>#{INLINE[md[1]]}</li>\n"
108
+ md = (line=file.gets)&.match UNORDERED
109
+ end
110
+ html << "</ul>\n"
111
+ line
112
+ end
113
+
114
+ # Ballot box
115
+ BALLOTS = /^- \[(x| )\] (.*)$/
116
+ PARSER[BALLOTS] = lambda do |line, html, file, opt, md|
117
+ html << "<ul#{opt[:attributes]}>\n"
118
+ opt.delete(:attributes)
119
+ while md
120
+ x,t = md[1],md[2]
121
+ li = (x=='x')?
122
+ %q{<li style="list-style-type: '&#9745; '">} :
123
+ %q{<li style="list-style-type: '&#9744; '">}
124
+ html << " #{li}#{INLINE[t]}</li>\n"
125
+ md = (line=file.gets)&.match BALLOTS
126
+ end
127
+ html << "</ul>\n"
128
+ line
129
+ end
130
+
131
+ # Definition list
132
+ DEFINITIONS = /^: (.*)$/
133
+ PARSER[DEFINITIONS] = lambda do |line, html, file, opt, md|
134
+ html << "<dl#{opt[:attributes]}>\n"
135
+ opt.delete(:attributes)
136
+ while md
137
+ item = md[1]
138
+ line = ((item[-1]==':')? "<dt>#{INLINE[item[0..-2]]}</dt>\n" :
139
+ "<dd>#{INLINE[item]}</dd>\n")
140
+ html << line
141
+ md = (line=file.gets)&.match DEFINITIONS
142
+ end
143
+ html << "</dl>\n"
144
+ line
145
+ end
146
+
147
+ # Headers
148
+ HEADERS = /^([#]{1,6}) (.*)$/
149
+ PARSER[HEADERS] = lambda do |line, html, file, opt, md|
150
+ i,header = md[1].length,md[2]
151
+ id = header.strip.gsub(/\s+/,'+')
152
+ html << %Q(<a id="#{id}">\n)
153
+ html << " <h#{i}#{opt[:attributes]}>#{INLINE[header]}</h#{i}>\n"
154
+ html << "</a>\n"
155
+ opt.delete(:attributes)
156
+ file.gets
157
+ end
158
+
159
+ # Block-quote
160
+ BLOCKQS = /^> (.*)$/
161
+ PARSER[BLOCKQS] = lambda do |line, html, file, opt, md|
162
+ html << "<blockquote#{opt[:attributes]}>\n"
163
+ opt.delete(:attributes)
164
+ while md
165
+ html << INLINE[md[1]]
166
+ html << "\n"
167
+ md = (line=file.gets)&.match BLOCKQS
168
+ end
169
+ html << "</blockquote>\n"
170
+ line
171
+ end
172
+
173
+ HTML = Rouge::Formatters::HTML.new
174
+
175
+ # Code
176
+ CODES = /^[`~]{3}\s*(\w+)?$/
177
+ PARSER[CODES] = lambda do |line, html, file, opt, md|
178
+ lang = Rouge::Lexer.find md[1]
179
+ klass = lang ? ' class="highlight"' : nil
180
+ html << "<pre#{klass}#{opt[:attributes]}><code>\n"
181
+ opt.delete(:attributes)
182
+ code = ''
183
+ while line=file.gets and not CODES.match(line)
184
+ code << line
185
+ end
186
+ html << (lang ? HTML.format(lang.new.lex(code)) : code)
187
+ html << "</code></pre>\n"
188
+ # line is either nil or the code close
189
+ line and file.gets
190
+ end
191
+
192
+ # Preform
193
+ PREFORMS = /^ {4}(.*)$/
194
+ PARSER[PREFORMS] = lambda do |line, html, file, opt, md|
195
+ html << "<pre#{opt[:attributes]}>\n"
196
+ opt.delete(:attributes)
197
+ while md
198
+ html << md[1]
199
+ html << "\n"
200
+ md = (line=file.gets)&.match PREFORMS
201
+ end
202
+ html << "</pre>\n"
203
+ line
204
+ end
205
+
206
+ # Horizontal rule
207
+ HRS = /^---+$/
208
+ PARSER[HRS] = lambda do |_, html, file, opt, _|
209
+ html << "<hr#{opt[:attributes]}>\n"
210
+ opt.delete(:attributes)
211
+ file.gets
212
+ end
213
+
214
+ # Table
215
+ TABLES = /^\|.+\|$/
216
+ PARSER[TABLES] = lambda do |line, html, file, opt, _|
217
+ html << "<table#{opt[:attributes]}>\n"
218
+ opt.delete(:attributes)
219
+ html << '<thead><tr><th>'
220
+ html << line[1...-1].split('|').map{INLINE[_1.strip]}.join('</th><th>')
221
+ html << "</th></tr></thead>\n"
222
+ align = []
223
+ while (line=file.gets)&.match TABLES
224
+ html << '<tr>'
225
+ line[1...-1].split('|').each_with_index do |cell, i|
226
+ case cell
227
+ when /^\s*:-+:\s*$/
228
+ align[i] = ' align="center"'
229
+ html << '<td><hr></td>'
230
+ when /^\s*-+:\s*$/
231
+ align[i] = ' align="right"'
232
+ html << '<td><hr></td>'
233
+ when /^\s*:-+\s*$/
234
+ align[i] = ' align="left"'
235
+ html << '<td><hr></td>'
236
+ else
237
+ html << "<td#{align[i]}>#{INLINE[cell.strip]}</td>"
238
+ end
239
+ end
240
+ html << "</tr>\n"
241
+ end
242
+ html << "</table>\n"
243
+ line
244
+ end
245
+
246
+ # Splits
247
+ SPLITS = /^:?\|:?$/
248
+ PARSER[SPLITS] = lambda do |line, html, file, opt, _|
249
+ case line.chomp
250
+ when '|:'
251
+ html << %Q(<table><tr><td#{opt[:attributes]}>\n)
252
+ when '|'
253
+ html << %Q(</td><td#{opt[:attributes]}>\n)
254
+ when ':|:'
255
+ html << %Q(</td></tr><tr><td#{opt[:attributes]}>\n)
256
+ when ':|'
257
+ html << %Q(</td></tr></table>\n)
258
+ end
259
+ opt.delete(:attributes)
260
+ file.gets
261
+ end
262
+
263
+ # Image
264
+ IMAGES = /^!\[([^\[\]]+)\]\(([^\(\)]+)\)$/
265
+ PARSER[IMAGES] = lambda do |line, html, file, opt, md|
266
+ alt,src=md[1],md[2]
267
+ style = ' '
268
+ case alt
269
+ when /^ .* $/
270
+ style = %Q( style="display: block; margin-left: auto; margin-right: auto;" )
271
+ when / $/
272
+ style = %Q( style="float:left;" )
273
+ when /^ /
274
+ style = %Q( style="float:right;" )
275
+ end
276
+ html << %Q(<img src="#{src}"#{style}alt="#{alt.strip}"#{opt[:attributes]}>\n)
277
+ opt.delete(:attributes)
278
+ file.gets
279
+ end
280
+
281
+ # Forms
282
+ FORMS = /^!( (\w+:)?\[\*?\w+(="[^"]*")?\])+/
283
+ PARSER[FORMS] = lambda do |line, html, file, opt, md|
284
+ form = []
285
+ lines,fields,submit,method = 0,0,nil,nil
286
+ action = (_=/\(([^\(\)]*)\)$/.match(line))? _[1] : nil
287
+ while md
288
+ lines += 1
289
+ form << ' <br>' if lines > 1
290
+ line.scan(/(\w+:)?\[(\*)?(\w+)(="[^"]*")?\]/).each do |field, pwd, name, value|
291
+ method ||= ' method="post"' if pwd
292
+ field &&= field[0...-1]
293
+ value &&= value[2...-1]
294
+ if field
295
+ fields += 1
296
+ type = (pwd)? 'password' : 'text'
297
+ if value
298
+ form << %Q{ #{field}:<input type="#{type}" name="#{name}" value="#{value}">}
299
+ else
300
+ form << %Q{ #{field}:<input type="#{type}" name="#{name}">}
301
+ end
302
+ elsif name=='submit'
303
+ submit = value
304
+ else
305
+ form << %Q{ <input type="hidden" name="#{name}" value="#{value}">}
306
+ end
307
+ end
308
+ md = (line=file.gets)&.match FORMS
309
+ end
310
+ if submit or not fields==1
311
+ submit ||= 'Submit'
312
+ form << ' <br>' if lines > 1
313
+ form << %Q( <input type="submit" value="#{submit}">)
314
+ end
315
+ form.unshift %Q(<form action="#{action}"#{method}#{opt[:attributes]}>)
316
+ form << %Q(</form>)
317
+ html << form.join("\n")
318
+ html << "\n"
319
+ opt.delete(:attributes)
320
+ line
321
+ end
322
+
323
+ # Embed text
324
+ EMBED_TEXTS = /^!> (#{PAGE_KEY}\.txt)$/
325
+ PARSER[EMBED_TEXTS] = lambda do |line, html, file, opt, md|
326
+ if File.exist?(filename=File.join(ROOT, md[1]))
327
+ html << "<pre>\n"
328
+ html << File.read(filename)
329
+ html << "</pre>\n"
330
+ else
331
+ html << line
332
+ end
333
+ file.gets
334
+ end
335
+
336
+ # Footnotes
337
+ FOOTNOTES = /^\[\^\d+\]:/
338
+ PARSER[FOOTNOTES] = lambda do |line, html, file, opt, md|
339
+ html << "<small>\n"
340
+ while md
341
+ html << INLINE[line.chomp]+"<br>\n"
342
+ md = (line=file.gets)&.match FOOTNOTES
343
+ end
344
+ html << "</small>\n"
345
+ line
346
+ end
347
+
348
+ # Attributes
349
+ ATTRIBUTES = /^\{:( .*)\}/
350
+ PARSER[ATTRIBUTES] = lambda do |line, html, file, opt, md|
351
+ opt[:attributes] = md[1]
352
+ md.post_match
353
+ end
354
+ end
355
+ end