teepee 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 39c6bf274f4c82db707b2ba44ffe90057f87e0b3
4
- data.tar.gz: 4a57736a99aee7dd8e119defe2d288bcaa53c18a
3
+ metadata.gz: 7d3f7f012149fdff0a39dfec0d8a8dd301eec2e0
4
+ data.tar.gz: 75cc86a2801db966891c6be12570b496c21c5f15
5
5
  SHA512:
6
- metadata.gz: 6a2155902fecf31514399bcc9bf3c8a44c85f8ed1d139d09e0ef301b4e20a0a575800adb95d9b479a14644610ec20e7f0a881f570c0737b49f2d564f27acd271
7
- data.tar.gz: d81d6dfd76f3aef611b214997401705e3def22204c6c064e92244251d8249433325c44bc9ca6ed1be6b63a50cce703c24fbf90e42b8adea7d906d32bd184431d
6
+ metadata.gz: c30fedd28550b28526f5f0b259c44939fe04b0727f8ce65c403430d98788bd17a345c4c37458db4a2779581cadc7c49b8fd008bf57a0b080a4c1031b8da82de0
7
+ data.tar.gz: 5634379fef912b567b5e55bbedd3a6d623fe49d4d11968bf85522f7bfb34dcff8b91a59896f1e9912d71cd668baeb0f3ec5f8553dced5e71da420fb1901923b4
data/lib/teepee.rb CHANGED
@@ -35,536 +35,24 @@
35
35
  # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36
36
  # POSSIBILITY OF SUCH DAMAGE.
37
37
 
38
- require 'active_support/all'
39
- require 'monkey-patch'
40
-
41
- include ERB::Util
42
-
43
38
  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 "i"
283
- command_error "Complex numbers are not yet supported."
284
- when "italic", "textit", "it"
285
- html_tag :i
286
- when "underline", "u"
287
- html_tag :u
288
- when "tt", "texttt", "teletype", "typewriter"
289
- html_tag :tt
290
- when "small"
291
- html_tag :small
292
- when "big"
293
- html_tag :big
294
- when "subscript", "sub"
295
- html_tag :sub
296
- when "superscript", "sup"
297
- html_tag :sup
298
- when "user", "user-id", "user_id"
299
- user_command_handler
300
- when "link-id", "link_id"
301
- link_id_command_handler
302
- when "keyword-id", "keyword_id"
303
- keyword_id_command_handler
304
- when "tag-id", "tag_id"
305
- tag_id_command_handler
306
- when "forum-id", "forum_id"
307
- forum_id_command_handler
308
- when "folder-id", "folder_id"
309
- folder_id_command_handler
310
- when "bookmarks-folder-id", "bookmarks_folder_id", "bookmarks_folder-id", "bookmarks-folder_id",
311
- "bookmark-folder-id", "bookmark_folder_id", "bookmark_folder-id", "bookmark-folder_id"
312
- bookmarks_folder_id_command_handler
313
- when "pi"
314
- "#{Math::PI}"
315
- when "e"
316
- "#{Math::E}"
317
- when "+"
318
- injectable_math_function_handler 0, :+
319
- when "-"
320
- if (numbers = numbers_from_expressions).length == 1
321
- injectable_math_function_handler 0, :-
322
- else
323
- reducable_math_function_handler :-
324
- end
325
- when "*"
326
- injectable_math_function_handler 1, :*
327
- when "/"
328
- if (numbers = numbers_from_expressions).length == 1
329
- 1 / numbers.first
330
- else
331
- reducable_math_function_handler :/
332
- end
333
- when "%"
334
- injectable_math_function_handler numbers_from_expressions.first, :%
335
- when "^", "**"
336
- number, exponent = numbers_from_expressions
337
- number.send :**, exponent
338
- when "sin", "cos", "tan",
339
- "asin", "acos", "atan",
340
- "sinh", "cosh", "tanh",
341
- "asinh", "acosh", "atanh",
342
- "erf", "erfc",
343
- "gamma", "log10", "sqrt"
344
- math_function_handler command.word.to_sym
345
- when "d2r", "deg->rad", "degrees->radians"
346
- MathFunctions::degrees2radians number_from_expression
347
- when "r2d", "rad->deg", "radians->degrees"
348
- MathFunctions::radians2degrees number_from_expression
349
- when "lgamma"
350
- MathFunctions::lgamma number_from_expression
351
- when "ld", "log2"
352
- Math.log2 number_from_expression
353
- when "ln"
354
- Math.log number_from_expression
355
- when "log"
356
- base, number = numbers_from_expressions
357
- if number.nil?
358
- Math.log base
359
- else
360
- Math.log number, base
361
- end
362
- when "ldexp"
363
- fraction, exponent = numbers_from_expressions
364
- Math.ldexp fraction, exponent
365
- when "hypot"
366
- Math.sqrt numbers_from_expressions.map {|n| n**2}
367
- else
368
- command_error "unknown command #{command.to_html}"
369
- end
370
- end
371
-
372
- def html_tag(tag)
373
- "<#{tag}>" + expressions.map(&:to_html).join + "</#{tag}>"
374
- end
375
-
376
- def tb_href(target, string)
377
- %{<a href="#{TB_COM}/#{target}">#{string}</a>}
378
- end
379
-
380
- def numbers_from_expressions
381
- expressions
382
- .map do |number|
383
- begin
384
- Float(number.to_html)
385
- rescue ArgumentError
386
- nil
387
- end
388
- end.reject &:nil?
389
- end
390
-
391
- def number_from_expression
392
- numbers_from_expressions.first
393
- end
394
-
395
- def injectable_math_function_handler(initial, function)
396
- numbers_from_expressions.inject initial, function
397
- end
398
-
399
- def reducable_math_function_handler(function)
400
- numbers_from_expressions.reduce function
401
- end
402
-
403
- def math_function_handler(function)
404
- Math.send function, numbers_from_expressions.first
405
- end
406
-
407
- def user_command_handler
408
- user = expressions.select {|expr| expr.is_a? WordToken}.first
409
- if not user
410
- command_error "user: error: no user specified"
411
- else
412
- if @@action_view.kind_of? ActionView::Base
413
- the_user = User.smart_find user.to_s
414
- if the_user
415
- @@action_view.render partial: 'users/name_link',
416
- locals: {the_user: the_user}
417
- else
418
- command_error "unknown user #{user.to_s}"
419
- end
420
- else
421
- tb_href "users/#{user}", user.to_s
422
- end
423
- end
424
- end
425
-
426
- def id_command_handler(klass,
427
- singular = klass.to_s.camelcase_to_snakecase,
428
- plural = singular.pluralize,
429
- partial = "#{plural}/inline",
430
- view="")
431
- id = expressions.select {|expr| expr.is_a? WordToken}.first
432
- if not id
433
- command_error "#{singular}_id: error: no #{singular} ID specified"
434
- elsif not id.to_s =~ /\A[0-9]+\z/
435
- command_error "#{singular}_id: error: invalid #{singular} ID specified"
436
- else
437
- if @@action_view.kind_of? ActionView::Base
438
- thing = klass.find Integer(id.to_s)
439
- if thing
440
- @@action_view.render partial: partial,
441
- locals: {singular.to_sym => thing}
442
- else
443
- command_error "unknown #{singular} ID #{id.to_s}"
444
- end
445
- else
446
- tb_href "/#{plural}/#{id.to_s}/#{view}", "#{klass} ##{id.to_s}"
447
- end
448
- end
449
- end
450
-
451
- def link_id_command_handler
452
- id_command_handler Link
453
- end
454
-
455
- def tag_id_command_handler
456
- id_command_handler Tag
457
- end
458
-
459
- def folder_id_command_handler
460
- id_command_handler Folder
461
- end
462
-
463
- def forum_id_command_handler
464
- id_command_handler Forum
465
- end
466
-
467
- def bookmarks_folder_id_command_handler
468
- id_command_handler Folder, "folder", "folders", "folders/bookmarks_inline", "bookmarks"
469
- end
470
-
471
- class << self
472
- @@action_view = nil
473
- @@controller = nil
474
-
475
- def parse(tokens)
476
- expressions = []
477
- rest = tokens
478
- backslash, command, left_brace = rest.shift(3)
479
- right_brace = nil
480
- raise ParseError if not backslash.is_a? BackslashToken
481
- raise ParseError if not command.is_a? WordToken
482
- if not left_brace.is_a? LeftBraceToken # A command with no interior.
483
- rest.unshift left_brace if not left_brace.is_a? WhitespaceToken
484
- return [CommandParser.new(command, []), rest]
485
- end
486
- while rest.length > 0
487
- if rest.first.is_a? WordToken
488
- expressions << rest.shift
489
- elsif rest.first.is_a? WhitespaceToken
490
- expressions << rest.shift
491
- elsif rest.first.is_a? BackslashToken
492
- result, rest = CommandParser.parse(rest)
493
- expressions << result
494
- elsif rest.first.is_a? RightBraceToken
495
- right_brace = rest.shift
496
- return [CommandParser.new(command, expressions), rest]
497
- else
498
- raise ParseError
499
- end
500
- end
501
- if right_brace.nil? # Allow a forgotten final right brace.
502
- return [CommandParser.new(command, expressions), rest]
503
- end
504
- end
505
-
506
- def action_view=(new)
507
- @@action_view = new
508
- end
509
-
510
- def controller=(new)
511
- @@controller = new
512
- end
513
- end
514
- end
515
-
516
- class ParagraphParser < ParserNode
517
- attr_reader :expressions, :tokens
518
-
519
- def initialize(tokens)
520
- raise ArgumentError if not tokens.is_a? Array
521
- tokens.each {|token| raise ArgumentError if not token.kind_of? ParserNode}
522
- @tokens = tokens
523
- parse
524
- end
525
-
526
- def parse
527
- @expressions = []
528
- rest = tokens
529
- while rest.length > 0
530
- if rest.first.is_a? WordToken
531
- @expressions << rest.shift
532
- elsif rest.first.is_a? WhitespaceToken
533
- @expressions << rest.shift
534
- elsif rest.first.is_a? BackslashToken
535
- command, rest = CommandParser.parse(rest)
536
- @expressions << command
537
- else
538
- return self
539
- end
540
- end
541
- end
542
-
543
- def to_html
544
- "<p>\n" + expressions.map(&:to_html).join + "\n</p>\n"
545
- end
546
- end
547
-
548
- class Parser < ParserNode
549
- attr_reader :paragraphs, :split_tokens, :text, :tokenizer
550
-
551
- def tokens
552
- tokenizer.tokens
553
- end
39
+ end
554
40
 
555
- def initialize(text)
556
- @text = text
557
- @tokenizer = Tokenizer.new text
558
- parse
559
- end
41
+ include ERB::Util
560
42
 
561
- def parse
562
- @split_tokens = tokens.split {|token| token.class == EmptyNewlinesToken}
563
- @paragraphs = @split_tokens.map {|split_tokens| ParagraphParser.new split_tokens}
564
- end
43
+ require 'active_support/all'
44
+ require 'monkey-patch'
565
45
 
566
- def to_html
567
- paragraphs.map(&:to_html).join "\n"
568
- end
569
- end
570
- end
46
+ require 'teepee/constants'
47
+ require 'teepee/errors'
48
+ require 'teepee/parser-node'
49
+ require 'teepee/token'
50
+ require 'teepee/commander'
51
+ require 'teepee/actionable-commander'
52
+ require 'teepee/single-character-token'
53
+ require 'teepee/string-token'
54
+ require 'teepee/number-token'
55
+ require 'teepee/tokenizer'
56
+ require 'teepee/command-parser'
57
+ require 'teepee/paragraph-parser'
58
+ require 'teepee/parser'