teepee 0.5.9
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 +7 -0
- data/lib/teepee.rb +568 -0
- metadata +71 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a73a0f9bde7b2730bce0660061804f994f2950c3
|
4
|
+
data.tar.gz: c0ee7a79b3cb0a133f23f019d1abfa9d050843ba
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 44af78ff299e1b02e24c15fe5aaed077b551e4d7aa0f131e1c8667cbd7da5240c961c3860713dbb2b0eca4e9d2023f86d21cb20a0b5bb284411a47e55bd7db51
|
7
|
+
data.tar.gz: 9d5d69e4462020eb75bf3807d77f65bbdb9d7db2f71597743a6f8c268ac29a54a1116b6547a79876b8ba4162ee6f6d178eb7043dbdccb85ac50638d325a42438
|
data/lib/teepee.rb
ADDED
@@ -0,0 +1,568 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
# -*- mode: Ruby -*-
|
3
|
+
|
4
|
+
# Copyright © 2013-2015, Christopher Mark Gore,
|
5
|
+
# Soli Deo Gloria,
|
6
|
+
# All rights reserved.
|
7
|
+
#
|
8
|
+
# 2317 South River Road, Saint Charles, Missouri 63303 USA.
|
9
|
+
# Web: http://cgore.com
|
10
|
+
# Email: cgore@cgore.com
|
11
|
+
#
|
12
|
+
# Redistribution and use in source and binary forms, with or without
|
13
|
+
# modification, are permitted provided that the following conditions are met:
|
14
|
+
#
|
15
|
+
# * Redistributions of source code must retain the above copyright
|
16
|
+
# notice, this list of conditions and the following disclaimer.
|
17
|
+
#
|
18
|
+
# * Redistributions in binary form must reproduce the above copyright
|
19
|
+
# notice, this list of conditions and the following disclaimer in the
|
20
|
+
# documentation and/or other materials provided with the distribution.
|
21
|
+
#
|
22
|
+
# * Neither the name of Christopher Mark Gore nor the names of other
|
23
|
+
# contributors may be used to endorse or promote products derived from
|
24
|
+
# this software without specific prior written permission.
|
25
|
+
#
|
26
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
27
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
28
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
29
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
30
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
31
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
32
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
33
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
34
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
35
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
36
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
37
|
+
|
38
|
+
require 'active_support/all'
|
39
|
+
require 'monkey-patch'
|
40
|
+
|
41
|
+
include ERB::Util
|
42
|
+
|
43
|
+
module Teepee
|
44
|
+
TB_COM = "http://thinkingbicycle.com"
|
45
|
+
|
46
|
+
module MathFunctions
|
47
|
+
class << self
|
48
|
+
def degrees2radians degrees
|
49
|
+
degrees * Math::PI / 180.0
|
50
|
+
end
|
51
|
+
|
52
|
+
def lgamma n
|
53
|
+
Math::lgamma(n).first
|
54
|
+
end
|
55
|
+
|
56
|
+
def radians2degrees radians
|
57
|
+
radians * 180.0 / Math::PI
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class ParseError < RuntimeError
|
63
|
+
end
|
64
|
+
|
65
|
+
class ParserNode
|
66
|
+
end
|
67
|
+
|
68
|
+
class Token < ParserNode
|
69
|
+
class << self
|
70
|
+
# The child classes should implement this method. If there is an
|
71
|
+
# immediate match, they should return a newly-created instance of
|
72
|
+
# themselves and the rest of the input as a string. If there is no match,
|
73
|
+
# they should return nil.
|
74
|
+
def matches? text
|
75
|
+
raise NotImplementedError,
|
76
|
+
"Child class #{self.class} should implement this."
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class SingleCharacterToken < Token
|
82
|
+
def text
|
83
|
+
self.class.character_matched
|
84
|
+
end
|
85
|
+
|
86
|
+
class << self
|
87
|
+
def character_matched
|
88
|
+
self::CHARACTER_MATCHED
|
89
|
+
end
|
90
|
+
|
91
|
+
def matches? text
|
92
|
+
if text.first == character_matched
|
93
|
+
return [self.new, text.rest]
|
94
|
+
else
|
95
|
+
return nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class StringToken < Token
|
102
|
+
attr_reader :text
|
103
|
+
|
104
|
+
def initialize(text)
|
105
|
+
raise ArgumentError if not text.is_a? String
|
106
|
+
raise ArgumentError if not text =~ self.class.full_match_regex
|
107
|
+
@text = text
|
108
|
+
end
|
109
|
+
|
110
|
+
def to_s
|
111
|
+
@text
|
112
|
+
end
|
113
|
+
|
114
|
+
def to_html
|
115
|
+
@text
|
116
|
+
end
|
117
|
+
|
118
|
+
class << self
|
119
|
+
def full_match_regex
|
120
|
+
self::FULL_MATCH_REGEX # Define this in a child class.
|
121
|
+
end
|
122
|
+
|
123
|
+
def front_match_regex
|
124
|
+
self::FRONT_MATCH_REGEX # Define this in a child class.
|
125
|
+
end
|
126
|
+
|
127
|
+
def count_regex
|
128
|
+
self::COUNT_REGEX # Define this in a child class.
|
129
|
+
end
|
130
|
+
|
131
|
+
def matches? text
|
132
|
+
if text =~ front_match_regex
|
133
|
+
count = text.index count_regex
|
134
|
+
if count.nil?
|
135
|
+
return [self.new(text), ""]
|
136
|
+
else
|
137
|
+
return [self.new(text[0 ... count]), text[count .. -1]]
|
138
|
+
end
|
139
|
+
else
|
140
|
+
return nil
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
class BackslashToken < SingleCharacterToken
|
147
|
+
CHARACTER_MATCHED = "\\"
|
148
|
+
end
|
149
|
+
|
150
|
+
class LeftBraceToken < SingleCharacterToken
|
151
|
+
CHARACTER_MATCHED = "{"
|
152
|
+
end
|
153
|
+
|
154
|
+
class RightBraceToken < SingleCharacterToken
|
155
|
+
CHARACTER_MATCHED = "}"
|
156
|
+
end
|
157
|
+
|
158
|
+
|
159
|
+
class EmptyNewlinesToken < StringToken
|
160
|
+
FULL_MATCH_REGEX = /\A\n\n+\z/
|
161
|
+
FRONT_MATCH_REGEX = /\A\n\n+/
|
162
|
+
COUNT_REGEX = /[^\n]/
|
163
|
+
|
164
|
+
def newlines
|
165
|
+
text
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
class WhitespaceToken < StringToken
|
170
|
+
FULL_MATCH_REGEX = /\A\s+\z/
|
171
|
+
FRONT_MATCH_REGEX = /\A\s+/
|
172
|
+
COUNT_REGEX = /\S/
|
173
|
+
|
174
|
+
def whitespace
|
175
|
+
text
|
176
|
+
end
|
177
|
+
|
178
|
+
def to_html
|
179
|
+
" " # Replace all whitespace tokens with a single space.
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class WordToken < StringToken
|
184
|
+
FULL_MATCH_REGEX = /\A[^\s{}\\]+\z/
|
185
|
+
FRONT_MATCH_REGEX = /[^\s{}\\]+/
|
186
|
+
COUNT_REGEX = /[\s{}\\]/
|
187
|
+
|
188
|
+
def to_html
|
189
|
+
html_escape text
|
190
|
+
end
|
191
|
+
|
192
|
+
def word
|
193
|
+
text
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
class NumberToken < Token
|
198
|
+
attr_reader :number, :text
|
199
|
+
|
200
|
+
def initialize(text)
|
201
|
+
raise ArgumentError if not text.is_a? String
|
202
|
+
@text = text
|
203
|
+
end
|
204
|
+
|
205
|
+
def parse
|
206
|
+
end
|
207
|
+
|
208
|
+
def to_s
|
209
|
+
number.to_s
|
210
|
+
end
|
211
|
+
|
212
|
+
def to_html
|
213
|
+
to_s
|
214
|
+
end
|
215
|
+
|
216
|
+
class << self
|
217
|
+
def matches? text
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
class Tokenizer
|
223
|
+
attr_reader :text, :tokens
|
224
|
+
def initialize(text)
|
225
|
+
@text = text
|
226
|
+
tokenize
|
227
|
+
end
|
228
|
+
|
229
|
+
def tokenize
|
230
|
+
@tokens = []
|
231
|
+
rest = text.gsub("\r", "")
|
232
|
+
while rest.length > 0
|
233
|
+
if result = BackslashToken.matches?(rest) or # Single Character Tokens
|
234
|
+
result = LeftBraceToken.matches?(rest) or
|
235
|
+
result = RightBraceToken.matches?(rest) or
|
236
|
+
result = EmptyNewlinesToken.matches?(rest) or # String Tokens
|
237
|
+
result = WhitespaceToken.matches?(rest) or
|
238
|
+
result = NumberToken.matches?(rest) or
|
239
|
+
result = WordToken.matches?(rest)
|
240
|
+
then
|
241
|
+
@tokens << result[0]
|
242
|
+
rest = result[1]
|
243
|
+
else
|
244
|
+
raise RuntimeError, "Couldn't tokenize the remaining text."
|
245
|
+
end
|
246
|
+
end
|
247
|
+
return @tokens
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
class CommandParser < ParserNode
|
252
|
+
attr_reader :command, :expressions
|
253
|
+
|
254
|
+
def initialize(command, expressions)
|
255
|
+
raise ArgumentError if not command.is_a? WordToken
|
256
|
+
@command = command
|
257
|
+
raise ArgumentError if not expressions.is_a? Array
|
258
|
+
expressions.each {|expression| raise ArgumentError if not expression.kind_of? ParserNode}
|
259
|
+
@expressions = expressions
|
260
|
+
end
|
261
|
+
|
262
|
+
def command_error(message)
|
263
|
+
%{<span style="color: red">[#{message}]</span>}
|
264
|
+
end
|
265
|
+
|
266
|
+
def to_html
|
267
|
+
case command.word
|
268
|
+
when "backslash", "bslash"
|
269
|
+
"\\"
|
270
|
+
when "left-brace", "left_brace", "leftbrace", "lbrace", "opening-brace", "opening_brace",
|
271
|
+
"openingbrace", "obrace"
|
272
|
+
"{"
|
273
|
+
when "right-brace", "right_brace", "rightbrace", "rbrace", "closing-brace", "closing_brace",
|
274
|
+
"closingbrace", "cbrace"
|
275
|
+
"}"
|
276
|
+
when "br", "newline"
|
277
|
+
"\n</br>\n"
|
278
|
+
when "bold", "b", "textbf"
|
279
|
+
html_tag :b
|
280
|
+
when "del", "s", "strike", "strikethrough", "strikeout"
|
281
|
+
html_tag :del
|
282
|
+
when "italic", "i", "textit", "it"
|
283
|
+
html_tag :i
|
284
|
+
when "underline", "u"
|
285
|
+
html_tag :u
|
286
|
+
when "tt", "texttt", "teletype", "typewriter"
|
287
|
+
html_tag :tt
|
288
|
+
when "small"
|
289
|
+
html_tag :small
|
290
|
+
when "big"
|
291
|
+
html_tag :big
|
292
|
+
when "subscript", "sub"
|
293
|
+
html_tag :sub
|
294
|
+
when "superscript", "sup"
|
295
|
+
html_tag :sup
|
296
|
+
when "user", "user-id", "user_id"
|
297
|
+
user_command_handler
|
298
|
+
when "link-id", "link_id"
|
299
|
+
link_id_command_handler
|
300
|
+
when "keyword-id", "keyword_id"
|
301
|
+
keyword_id_command_handler
|
302
|
+
when "tag-id", "tag_id"
|
303
|
+
tag_id_command_handler
|
304
|
+
when "forum-id", "forum_id"
|
305
|
+
forum_id_command_handler
|
306
|
+
when "folder-id", "folder_id"
|
307
|
+
folder_id_command_handler
|
308
|
+
when "bookmarks-folder-id", "bookmarks_folder_id", "bookmarks_folder-id", "bookmarks-folder_id",
|
309
|
+
"bookmark-folder-id", "bookmark_folder_id", "bookmark_folder-id", "bookmark-folder_id"
|
310
|
+
bookmarks_folder_id_command_handler
|
311
|
+
when "pi"
|
312
|
+
"#{Math::PI}"
|
313
|
+
when "e"
|
314
|
+
"#{Math::E}"
|
315
|
+
when "+"
|
316
|
+
injectable_math_function_handler 0, :+
|
317
|
+
when "-"
|
318
|
+
if (numbers = numbers_from_expressions).length == 1
|
319
|
+
injectable_math_function_handler 0, :-
|
320
|
+
else
|
321
|
+
reducable_math_function_handler :-
|
322
|
+
end
|
323
|
+
when "*"
|
324
|
+
injectable_math_function_handler 1, :*
|
325
|
+
when "/"
|
326
|
+
if (numbers = numbers_from_expressions).length == 1
|
327
|
+
1 / numbers.first
|
328
|
+
else
|
329
|
+
reducable_math_function_handler :/
|
330
|
+
end
|
331
|
+
when "%"
|
332
|
+
injectable_math_function_handler numbers_from_expressions.first, :%
|
333
|
+
when "^", "**"
|
334
|
+
number, exponent = numbers_from_expressions
|
335
|
+
number.send :**, exponent
|
336
|
+
when "sin", "cos", "tan",
|
337
|
+
"asin", "acos", "atan",
|
338
|
+
"sinh", "cosh", "tanh",
|
339
|
+
"asinh", "acosh", "atanh",
|
340
|
+
"erf", "erfc",
|
341
|
+
"gamma", "log10", "sqrt"
|
342
|
+
math_function_handler command.word.to_sym
|
343
|
+
when "d2r", "deg->rad", "degrees->radians"
|
344
|
+
MathFunctions::degrees2radians number_from_expression
|
345
|
+
when "r2d", "rad->deg", "radians->degrees"
|
346
|
+
MathFunctions::radians2degrees number_from_expression
|
347
|
+
when "lgamma"
|
348
|
+
MathFunctions::lgamma number_from_expression
|
349
|
+
when "ld", "log2"
|
350
|
+
Math.log2 number_from_expression
|
351
|
+
when "ln"
|
352
|
+
Math.log number_from_expression
|
353
|
+
when "log"
|
354
|
+
base, number = numbers_from_expressions
|
355
|
+
if number.nil?
|
356
|
+
Math.log base
|
357
|
+
else
|
358
|
+
Math.log number, base
|
359
|
+
end
|
360
|
+
when "ldexp"
|
361
|
+
fraction, exponent = numbers_from_expressions
|
362
|
+
Math.ldexp fraction, exponent
|
363
|
+
when "hypot"
|
364
|
+
Math.sqrt numbers_from_expressions.map {|n| n**2}
|
365
|
+
else
|
366
|
+
command_error "unknown command #{command.to_html}"
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def html_tag(tag)
|
371
|
+
"<#{tag}>" + expressions.map(&:to_html).join + "</#{tag}>"
|
372
|
+
end
|
373
|
+
|
374
|
+
def tb_href(target, string)
|
375
|
+
%{<a href="#{TB_COM}/#{target}">#{string}</a>}
|
376
|
+
end
|
377
|
+
|
378
|
+
def numbers_from_expressions
|
379
|
+
expressions
|
380
|
+
.map do |number|
|
381
|
+
begin
|
382
|
+
Float(number.to_html)
|
383
|
+
rescue ArgumentError
|
384
|
+
nil
|
385
|
+
end
|
386
|
+
end.reject &:nil?
|
387
|
+
end
|
388
|
+
|
389
|
+
def number_from_expression
|
390
|
+
numbers_from_expressions.first
|
391
|
+
end
|
392
|
+
|
393
|
+
def injectable_math_function_handler(initial, function)
|
394
|
+
numbers_from_expressions.inject initial, function
|
395
|
+
end
|
396
|
+
|
397
|
+
def reducable_math_function_handler(function)
|
398
|
+
numbers_from_expressions.reduce function
|
399
|
+
end
|
400
|
+
|
401
|
+
def math_function_handler(function)
|
402
|
+
Math.send function, numbers_from_expressions.first
|
403
|
+
end
|
404
|
+
|
405
|
+
def user_command_handler
|
406
|
+
user = expressions.select {|expr| expr.is_a? WordToken}.first
|
407
|
+
if not user
|
408
|
+
command_error "user: error: no user specified"
|
409
|
+
else
|
410
|
+
if @@action_view.kind_of? ActionView::Base
|
411
|
+
the_user = User.smart_find user.to_s
|
412
|
+
if the_user
|
413
|
+
@@action_view.render partial: 'users/name_link',
|
414
|
+
locals: {the_user: the_user}
|
415
|
+
else
|
416
|
+
command_error "unknown user #{user.to_s}"
|
417
|
+
end
|
418
|
+
else
|
419
|
+
tb_href "users/#{user}", user.to_s
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
def id_command_handler(klass,
|
425
|
+
singular = klass.to_s.camelcase_to_snakecase,
|
426
|
+
plural = singular.pluralize,
|
427
|
+
partial = "#{plural}/inline",
|
428
|
+
view="")
|
429
|
+
id = expressions.select {|expr| expr.is_a? WordToken}.first
|
430
|
+
if not id
|
431
|
+
command_error "#{singular}_id: error: no #{singular} ID specified"
|
432
|
+
elsif not id.to_s =~ /\A[0-9]+\z/
|
433
|
+
command_error "#{singular}_id: error: invalid #{singular} ID specified"
|
434
|
+
else
|
435
|
+
if @@action_view.kind_of? ActionView::Base
|
436
|
+
thing = klass.find Integer(id.to_s)
|
437
|
+
if thing
|
438
|
+
@@action_view.render partial: partial,
|
439
|
+
locals: {singular.to_sym => thing}
|
440
|
+
else
|
441
|
+
command_error "unknown #{singular} ID #{id.to_s}"
|
442
|
+
end
|
443
|
+
else
|
444
|
+
tb_href "/#{plural}/#{id.to_s}/#{view}", "#{klass} ##{id.to_s}"
|
445
|
+
end
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def link_id_command_handler
|
450
|
+
id_command_handler Link
|
451
|
+
end
|
452
|
+
|
453
|
+
def tag_id_command_handler
|
454
|
+
id_command_handler Tag
|
455
|
+
end
|
456
|
+
|
457
|
+
def folder_id_command_handler
|
458
|
+
id_command_handler Folder
|
459
|
+
end
|
460
|
+
|
461
|
+
def forum_id_command_handler
|
462
|
+
id_command_handler Forum
|
463
|
+
end
|
464
|
+
|
465
|
+
def bookmarks_folder_id_command_handler
|
466
|
+
id_command_handler Folder, "folder", "folders", "folders/bookmarks_inline", "bookmarks"
|
467
|
+
end
|
468
|
+
|
469
|
+
class << self
|
470
|
+
@@action_view = nil
|
471
|
+
@@controller = nil
|
472
|
+
|
473
|
+
def parse(tokens)
|
474
|
+
expressions = []
|
475
|
+
rest = tokens
|
476
|
+
backslash, command, left_brace = rest.shift(3)
|
477
|
+
right_brace = nil
|
478
|
+
raise ParseError if not backslash.is_a? BackslashToken
|
479
|
+
raise ParseError if not command.is_a? WordToken
|
480
|
+
if not left_brace.is_a? LeftBraceToken # A command with no interior.
|
481
|
+
rest.unshift left_brace if not left_brace.is_a? WhitespaceToken
|
482
|
+
return [CommandParser.new(command, []), rest]
|
483
|
+
end
|
484
|
+
while rest.length > 0
|
485
|
+
if rest.first.is_a? WordToken
|
486
|
+
expressions << rest.shift
|
487
|
+
elsif rest.first.is_a? WhitespaceToken
|
488
|
+
expressions << rest.shift
|
489
|
+
elsif rest.first.is_a? BackslashToken
|
490
|
+
result, rest = CommandParser.parse(rest)
|
491
|
+
expressions << result
|
492
|
+
elsif rest.first.is_a? RightBraceToken
|
493
|
+
right_brace = rest.shift
|
494
|
+
return [CommandParser.new(command, expressions), rest]
|
495
|
+
else
|
496
|
+
raise ParseError
|
497
|
+
end
|
498
|
+
end
|
499
|
+
if right_brace.nil? # Allow a forgotten final right brace.
|
500
|
+
return [CommandParser.new(command, expressions), rest]
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
def action_view=(new)
|
505
|
+
@@action_view = new
|
506
|
+
end
|
507
|
+
|
508
|
+
def controller=(new)
|
509
|
+
@@controller = new
|
510
|
+
end
|
511
|
+
end
|
512
|
+
end
|
513
|
+
|
514
|
+
class ParagraphParser < ParserNode
|
515
|
+
attr_reader :expressions, :tokens
|
516
|
+
|
517
|
+
def initialize(tokens)
|
518
|
+
raise ArgumentError if not tokens.is_a? Array
|
519
|
+
tokens.each {|token| raise ArgumentError if not token.kind_of? ParserNode}
|
520
|
+
@tokens = tokens
|
521
|
+
parse
|
522
|
+
end
|
523
|
+
|
524
|
+
def parse
|
525
|
+
@expressions = []
|
526
|
+
rest = tokens
|
527
|
+
while rest.length > 0
|
528
|
+
if rest.first.is_a? WordToken
|
529
|
+
@expressions << rest.shift
|
530
|
+
elsif rest.first.is_a? WhitespaceToken
|
531
|
+
@expressions << rest.shift
|
532
|
+
elsif rest.first.is_a? BackslashToken
|
533
|
+
command, rest = CommandParser.parse(rest)
|
534
|
+
@expressions << command
|
535
|
+
else
|
536
|
+
return self
|
537
|
+
end
|
538
|
+
end
|
539
|
+
end
|
540
|
+
|
541
|
+
def to_html
|
542
|
+
"<p>\n" + expressions.map(&:to_html).join + "\n</p>\n"
|
543
|
+
end
|
544
|
+
end
|
545
|
+
|
546
|
+
class Parser < ParserNode
|
547
|
+
attr_reader :paragraphs, :split_tokens, :text, :tokenizer
|
548
|
+
|
549
|
+
def tokens
|
550
|
+
tokenizer.tokens
|
551
|
+
end
|
552
|
+
|
553
|
+
def initialize(text)
|
554
|
+
@text = text
|
555
|
+
@tokenizer = Tokenizer.new text
|
556
|
+
parse
|
557
|
+
end
|
558
|
+
|
559
|
+
def parse
|
560
|
+
@split_tokens = tokens.split {|token| token.class == EmptyNewlinesToken}
|
561
|
+
@paragraphs = @split_tokens.map {|split_tokens| ParagraphParser.new split_tokens}
|
562
|
+
end
|
563
|
+
|
564
|
+
def to_html
|
565
|
+
paragraphs.map(&:to_html).join "\n"
|
566
|
+
end
|
567
|
+
end
|
568
|
+
end
|
metadata
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: teepee
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.5.9
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Christopher Mark Gore
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-11-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: monkey-patch
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Teepee is a markup language, loosely based on Lisp and TeX, for ThinkingBicycle.com.
|
42
|
+
email: cgore@cgore.com
|
43
|
+
executables: []
|
44
|
+
extensions: []
|
45
|
+
extra_rdoc_files: []
|
46
|
+
files:
|
47
|
+
- lib/teepee.rb
|
48
|
+
homepage: https://github.com/cgore/ruby-teepee
|
49
|
+
licenses: []
|
50
|
+
metadata: {}
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubyforge_project:
|
67
|
+
rubygems_version: 2.4.5.1
|
68
|
+
signing_key:
|
69
|
+
specification_version: 4
|
70
|
+
summary: Teepee is a markup language for ThinkingBicycle.com.
|
71
|
+
test_files: []
|