markita 1.1.210831 → 2.0.210906

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.
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