livetext 0.9.14 → 0.9.15

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.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/lib/cmdargs.rb +93 -0
  3. data/lib/formatline.rb +56 -83
  4. data/lib/helpers.rb +142 -4
  5. data/lib/livetext.rb +11 -141
  6. data/lib/parser/file.rb +8 -0
  7. data/lib/parser/mixin.rb +28 -15
  8. data/lib/parser/set.rb +35 -26
  9. data/lib/parser/string.rb +19 -4
  10. data/lib/processor.rb +1 -4
  11. data/lib/standard.rb +56 -96
  12. data/plugin/bookish.rb +26 -22
  13. data/plugin/calibre.rb +1 -1
  14. data/plugin/livemagick.rb +10 -10
  15. data/plugin/markdown.rb +13 -11
  16. data/plugin/pyggish.rb +94 -84
  17. data/plugin/tutorial.rb +10 -5
  18. data/test/all.rb +0 -1
  19. data/test/snapshots/OMIT.txt +7 -8
  20. data/test/snapshots/clusion.txt +35 -0
  21. data/test/snapshots/error_inc_line_num/actual-error.txt +14 -0
  22. data/test/snapshots/error_inc_line_num/actual-output.txt +7 -0
  23. data/test/snapshots/error_inc_line_num/match-error.txt +1 -1
  24. data/test/snapshots/error_inc_line_num/out-sdiff.txt +14 -0
  25. data/test/snapshots/error_invalid_name/actual-error.txt +10 -0
  26. data/test/snapshots/error_invalid_name/actual-output.txt +0 -0
  27. data/test/snapshots/error_invalid_name/match-error.txt +1 -1
  28. data/test/snapshots/error_invalid_name/out-sdiff.txt +6 -0
  29. data/test/snapshots/error_line_num/match-error.txt +1 -1
  30. data/test/snapshots/error_mismatched_end/match-error.txt +1 -1
  31. data/test/snapshots/error_missing_end/actual-error.txt +10 -0
  32. data/test/snapshots/error_missing_end/actual-output.txt +0 -0
  33. data/test/snapshots/error_missing_end/match-error.txt +1 -1
  34. data/test/snapshots/error_missing_end/out-sdiff.txt +6 -0
  35. data/test/snapshots/error_no_such_copy/actual-error.txt +10 -0
  36. data/test/snapshots/error_no_such_copy/actual-output.txt +0 -0
  37. data/test/snapshots/error_no_such_copy/match-error.txt +1 -1
  38. data/test/snapshots/error_no_such_copy/out-sdiff.txt +5 -0
  39. data/test/snapshots/error_no_such_copy/source.lt3 +0 -1
  40. data/test/snapshots/error_no_such_inc/actual-error.txt +10 -0
  41. data/test/snapshots/error_no_such_inc/actual-output.txt +0 -0
  42. data/test/snapshots/error_no_such_inc/match-error.txt +1 -1
  43. data/test/snapshots/error_no_such_inc/out-sdiff.txt +6 -0
  44. data/test/snapshots/error_no_such_mixin/actual-error.txt +37 -0
  45. data/test/snapshots/error_no_such_mixin/actual-output.txt +0 -0
  46. data/test/snapshots/error_no_such_mixin/out-sdiff.txt +6 -0
  47. data/test/snapshots/simple_import/actual-error.txt +8 -0
  48. data/test/snapshots/simple_import/actual-output.txt +3 -0
  49. data/test/snapshots/simple_import/err-sdiff.txt +9 -0
  50. data/test/snapshots/simple_import/expected-error.txt +0 -0
  51. data/test/snapshots/simple_import/expected-output.txt +7 -0
  52. data/test/snapshots/simple_import/out-sdiff.txt +9 -0
  53. data/test/snapshots/simple_import/simple_import.rb +5 -0
  54. data/test/snapshots/simple_import/source.lt3 +7 -0
  55. data/test/snapshots/simple_include/source.lt3 +0 -1
  56. data/test/snapshots.rb +3 -2
  57. data/test/unit/all.rb +1 -0
  58. data/test/unit/formatline.rb +650 -0
  59. data/test/unit/parser/importable.rb +1 -1
  60. data/test/unit/parser/mixin.rb +1 -1
  61. data/test/unit/parser/set.rb +19 -12
  62. data/test/unit/parser/string.rb +14 -14
  63. metadata +32 -5
  64. data/test/formatting-tests.rb +0 -35
  65. data/test/formatting.rb +0 -103
  66. data/test/snapshots/formatting-tests.txt +0 -124
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d9e6ad9c8d933b361da03fadc6cc4d28e0a8b699f8f50e434db9bf41f0b9d66b
4
- data.tar.gz: 49e59704162aea72ac92122d69f0006591856b9fe09f2b2fdbf6843b7253b80b
3
+ metadata.gz: a8d345a0f52ce0d7c62f40f8798793faf5136f8ce72cde8576a0284220c08082
4
+ data.tar.gz: 4e037527c3abd873c9cf8ca49685d99c68fffab9630a67c1193e898e961c4d48
5
5
  SHA512:
6
- metadata.gz: 74b1b06e0bc91d976c0f60a2136ed8bb1c4d2d5ba33359620752b44a51918a89fbba2cec9c542ae12a6368de82bdc4db64b30a4d95146caab4e5a290f9c7fa8b
7
- data.tar.gz: ed0c0fa3b4da538ddbd31cd799a84f5647d45a369f0f45d729c223f74c7b0f2445bfc0812aa0c319b9a9f5992efa6af521d550eed38a8e2c551b183e28a16722
6
+ metadata.gz: 5b2957b2fe57d0abeff6029c63e310b7d26d653e1e7848c4026d2c16f43e6c37f6c9bf2fb63f2dfcd4e719cc82113cf549413362da43c14ac12312cafad1a744
7
+ data.tar.gz: 696a9ff22bff7caae99534e1a9431e9914e9137be4212674598c2ee66a58c11bd37c56bc90c52dd0d0cae3450f8905081202fbdc08f05a6164abd2de2dc5e7ce
data/lib/cmdargs.rb ADDED
@@ -0,0 +1,93 @@
1
+ require_relative 'livetext'
2
+
3
+ =begin
4
+ Weird concepts to understand here...
5
+
6
+ 1. A Livetext dot-command (flush left) usually looks like:
7
+ .foobar
8
+ 2. A dot-command (left-indented) usually looks like:
9
+ $.foobar
10
+ 3. More generally, it may have any number of parameters (0, 1, ...)
11
+ .redirect somefile.txt append
12
+ 4. Variables and functions *can* appear (rare in practice??)
13
+ .redirect somefile$my_suffix $$my_mode
14
+ 5. A trailing # comment may appear
15
+ a. Stripped... saved in #raw ? #data ? #comment ? elsewhere?
16
+ b. NOT the "dot" as a comment!
17
+ 6. .foobar # This here is a comment
18
+ 7. #data accessor returns all data on the .foo line...
19
+ a. ...After the initial space
20
+ b. ...Including spaces
21
+ c. Including comment??
22
+ d. .foo This is o n l y a test.
23
+ # #data returns: "This is o n l y a test."
24
+ e. What about formatting???
25
+ f. What about: comments? variables? functions?
26
+
27
+ 7. Some commands have NO body while others have an OPTIONAL or REQUIRED body
28
+ a. Assume .cmd1 definition forbids a body (then a body is an error)
29
+ .cmd1 # may NOT have a body
30
+ b. Assume .cmd2 definition PERMITS a body
31
+ .cmd2 # may or MAY NOT have body/.end
32
+ c. Assume .cmd3 definition PERMITS a body
33
+ .cmd3 # REQUIRES a body/.end
34
+ . stuff...
35
+ .end
36
+ 8. Inside a body:
37
+ 8a. Leading dot has no special meaning (though the associated method may parse it!)
38
+ 8b. BUG? Currently leading dot is a comment INSIDE a body?
39
+ 8a. No leading char is special (though the associated method may parse it!)
40
+ 8a. No trailing #-comments (though the associated method may parse it!)
41
+ 8c. ?? We should or shouldn't look for variables/functions? or make it an option?
42
+ 8d. .end may naturally not be used (but see .raw where it may)
43
+ 9. the args accessor is a simple array of strings
44
+
45
+
46
+ =end
47
+
48
+ class Livetext::CmdData
49
+
50
+ attr_reader :data, :args, :nargs, :arity, :comment, :raw # , ...?
51
+
52
+ def initialize(data, body: false, arity: :N) # FIXME maybe just add **options ??
53
+ # arity: (num) fixed number 0 or more
54
+ # :N arbitrary number
55
+ # n1..n2 range
56
+ # body: true => this command has a body + .end
57
+ # how raw is raw?
58
+ # remove comment - always/sometimes/never?
59
+ # var_func_parse - always/sometimes/never?
60
+ # var_func_parse inside body??
61
+ @data = data.dup # comment? vars? funcs?
62
+ @raw = data.dup # comment? vars? funcs?
63
+ @args = data.split # simple array
64
+ @nargs = nargs # not really "needed"
65
+ check_num_args(nargs)
66
+
67
+ # @varfunc = _var_func_parse(data.dup)
68
+ end
69
+
70
+ def check_num_args(num)
71
+ num_range = /(\d{0,2})(\.\.)(\d{0,2})/
72
+ min, max = 0, 9999
73
+ md = num_range.match(@nargs).to_a
74
+ bad_args = nil
75
+ case
76
+ when @nargs == ":N" # arbitrary
77
+ # max already set
78
+ when md[2] == ".." # range: 4..6 1.. ..4
79
+ vmin, vmax = md.values_at(1, 2)
80
+ min = Integer(vmin) unless vmin.empty?
81
+ max = Integer(vmax) unless vmax.empty?
82
+ min, max = Integer(min), Integer(max)
83
+ when %r[^\d+$] =~ num
84
+ min = max = Integer(num) # can raise error
85
+ else
86
+ raise "Invalid value or range '#{num.inspect}'"
87
+ end
88
+
89
+ bad_args = @args.size.between?(min, max)
90
+ raise "Expected #{num} args but found #{@args.size}!" if bad_args
91
+ end
92
+
93
+ end
data/lib/formatline.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # Class FormatLine handles the parsing of comments, dot commands, and
2
2
  # simple formatting characters.
3
3
 
4
- class FormatLine
4
+ class FormatLine < StringParser
5
5
  SimpleFormats = {}
6
6
  SimpleFormats[:b] = %w[<b> </b>]
7
7
  SimpleFormats[:i] = %w[<i> </i>]
@@ -24,20 +24,11 @@ class FormatLine
24
24
 
25
25
  Syms = { "*" => :b, "_" => :i, "`" => :t, "~" => :s }
26
26
 
27
- def terminate?(terminators, ch)
28
- if terminators.is_a? Regexp
29
- terminators === ch
30
- else
31
- terminators.include?(ch)
32
- end
33
- end
34
-
35
27
  attr_reader :out
36
28
  attr_reader :tokenlist
37
29
 
38
30
  def initialize(line)
39
- @line = line
40
- @i = -1
31
+ super
41
32
  @token = Null.dup
42
33
  @tokenlist = []
43
34
  end
@@ -45,26 +36,27 @@ class FormatLine
45
36
  def self.parse!(line)
46
37
  return nil if line.nil?
47
38
  x = self.new(line.chomp)
48
- t = x.tokenize(line)
39
+ t = x.tokenize
40
+ # TTY.puts "tokens = \n#{t.inspect}\n "
49
41
  x.evaluate
50
42
  end
51
43
 
52
- def tokenize(line)
53
- grab
44
+ def tokenize
45
+ # add grab
54
46
  loop do
55
- case curr
56
- when Escape; grab; add curr; grab; add curr
47
+ case peek
48
+ when Escape; grab; add peek; grab; add peek
57
49
  when "$"
58
50
  dollar
59
51
  when "*", "_", "`", "~"
60
- marker curr
61
- add curr
52
+ marker peek
53
+ add peek
62
54
  when LF
63
55
  break if @i >= line.size - 1
64
56
  when nil
65
57
  break
66
58
  else
67
- add curr
59
+ add peek
68
60
  end
69
61
  grab
70
62
  end
@@ -72,24 +64,33 @@ class FormatLine
72
64
  @tokenlist
73
65
  end
74
66
 
67
+ def terminate?(terminators, ch)
68
+ if terminators.is_a? Regexp
69
+ terminators === ch
70
+ else
71
+ terminators.include?(ch)
72
+ end
73
+ end
74
+
75
75
  def self.var_func_parse(str)
76
76
  return nil if str.nil?
77
77
  x = self.new(str.chomp)
78
- x.grab
79
- loop do
80
- case x.curr
81
- when Escape; x.grab; x.add x.curr; x.grab
82
- when "$"
83
- x.dollar
84
- when LF, nil
85
- break
86
- else
87
- x.add x.curr
88
- end
89
- x.grab
78
+ char = x.peek
79
+ loop do
80
+ char = x.grab
81
+ break if char == LF || char == nil
82
+ x.handle_escaping if char == Escape
83
+ x.dollar if char == "$"
84
+ x.add char
90
85
  end
91
86
  x.add_token(:str)
92
- x.evaluate
87
+ result = x.evaluate
88
+ result
89
+ end
90
+
91
+ def handle_escaping
92
+ grab
93
+ add grab
93
94
  end
94
95
 
95
96
  def embed(sym, str)
@@ -130,27 +131,6 @@ class FormatLine
130
131
  @out
131
132
  end
132
133
 
133
- def curr
134
- @line[@i]
135
- end
136
-
137
- def prev
138
- return nil if @i <= 0
139
- @line[@i-1]
140
- end
141
-
142
- def next!
143
- @line[@i+1]
144
- end
145
-
146
- def grab
147
- @line[@i+=1]
148
- end
149
-
150
- def ungrab
151
- @line[@i-=1]
152
- end
153
-
154
134
  def grab_colon_param
155
135
  grab # grab :
156
136
  param = ""
@@ -187,7 +167,7 @@ class FormatLine
187
167
  end
188
168
  end
189
169
 
190
- add curr
170
+ add peek
191
171
  grab
192
172
  param = nil if param.empty?
193
173
  param
@@ -207,8 +187,8 @@ class FormatLine
207
187
  str = Null.dup
208
188
  grab
209
189
  loop do
210
- break if curr.nil?
211
- str << curr
190
+ break if eos?
191
+ str << peek
212
192
  break if terminate?(NoAlpha, next!)
213
193
  grab
214
194
  end
@@ -219,8 +199,8 @@ class FormatLine
219
199
  str = Null.dup
220
200
  grab
221
201
  loop do
222
- break if curr.nil?
223
- str << curr
202
+ break if eos? # peek.nil?
203
+ str << peek
224
204
  break if terminate?(NoAlphaDot, next!)
225
205
  grab
226
206
  end
@@ -229,7 +209,7 @@ class FormatLine
229
209
 
230
210
  def dollar
231
211
  grab
232
- case curr
212
+ case peek
233
213
  when LF; add "$"; add_token :str
234
214
  when " "; add "$ "; add_token :str
235
215
  when nil; add "$"; add_token :str
@@ -237,10 +217,10 @@ class FormatLine
237
217
  # when "."; dollar_dot
238
218
  when /[A-Za-z]/
239
219
  add_token :str
240
- var = curr + grab_alpha_dot
220
+ var = peek + grab_alpha_dot
241
221
  add_token(:var, var)
242
222
  else
243
- add "$" + curr
223
+ add "$" + peek
244
224
  add_token(:string)
245
225
  end
246
226
  end
@@ -258,7 +238,7 @@ class FormatLine
258
238
  when "["; param = grab_func_param; add_token(:brackets, param)
259
239
  end
260
240
  else
261
- grab; add_token :str, "$$" + curr; return
241
+ grab; add_token :str, "$$" + peek; return
262
242
  end
263
243
  end
264
244
 
@@ -275,7 +255,7 @@ class FormatLine
275
255
  end
276
256
 
277
257
  grab
278
- case curr
258
+ case peek
279
259
  when Space
280
260
  add char + " "
281
261
  add_token :str
@@ -286,7 +266,7 @@ class FormatLine
286
266
  when char; double_marker(char)
287
267
  when LBrack; long_marker(char)
288
268
  else
289
- str = curr + collect!(sym, Blank)
269
+ str = peek + collect!(sym, Blank)
290
270
  add str
291
271
  add_token sym, str
292
272
  grab
@@ -319,21 +299,19 @@ class FormatLine
319
299
  str = Null.dup # next is not " ","*","["
320
300
  grab # ZZZ
321
301
  loop do
322
- if curr == Escape
323
- str << grab # ch = escaped char
302
+ if peek == Escape
324
303
  grab
304
+ str << grab
325
305
  next
326
306
  end
327
- if terminate?(terminators, curr)
307
+ if terminate?(terminators, peek)
328
308
  break
329
309
  end
330
- # STDERR.puts "#{curr.inspect} is not a terminator"
331
- str << curr # not a terminator
310
+ str << peek # not a terminator
332
311
  grab
333
- # STDERR.puts "After grab, curr is #{curr.inspect}"
334
312
  end
335
313
 
336
- if curr == "]" # skip right bracket
314
+ if peek == "]" # skip right bracket
337
315
  grab
338
316
  end
339
317
  add str
@@ -344,8 +322,8 @@ class FormatLine
344
322
  end
345
323
 
346
324
  def escaped
347
- ch = grab
348
325
  grab
326
+ ch = grab
349
327
  ch
350
328
  end
351
329
 
@@ -356,15 +334,15 @@ class FormatLine
356
334
  grab # ZZZ
357
335
  loop do
358
336
  case
359
- when curr.nil?
337
+ when peek.nil?
360
338
  return str
361
- when curr == Escape
339
+ when peek == Escape
362
340
  str << escaped
363
341
  next
364
- when terminate?(terminators, curr)
342
+ when terminate?(terminators, peek)
365
343
  break
366
344
  else
367
- str << curr # not a terminator
345
+ str << peek # not a terminator
368
346
  end
369
347
  grab
370
348
  end
@@ -376,17 +354,14 @@ class FormatLine
376
354
  STDERR.puts "=== str = #{str.inspect}"
377
355
  end
378
356
 
379
- ############
380
-
381
- ### From FormatLine:
382
-
383
357
  def funcall(name, param)
358
+ err = "[Error evaluating $$#{name}(#{param})]"
384
359
  result =
385
360
  if self.respond_to?("func_" + name.to_s)
386
361
  self.send("func_" + name.to_s, param)
387
362
  else
388
363
  fobj = ::Livetext::Functions.new
389
- fobj.send(name, param)
364
+ fobj.send(name, param) rescue err
390
365
  end
391
366
  result
392
367
  end
@@ -396,8 +371,6 @@ class FormatLine
396
371
  result
397
372
  end
398
373
 
399
- #####
400
-
401
374
  def embedded?
402
375
  ! (['"', "'", " ", nil].include? prev)
403
376
  end
data/lib/helpers.rb CHANGED
@@ -1,10 +1,86 @@
1
1
 
2
2
  module Helpers
3
3
 
4
+ Space = " "
5
+ Sigil = "." # Can't change yet
6
+
7
+ def self.rx(str, space=nil)
8
+ Regexp.compile("^" + Regexp.escape(str) + "#{space}")
9
+ end
10
+
11
+ Comment = rx(Sigil, Space)
12
+ Dotcmd = rx(Sigil)
13
+ Ddotcmd = /^ *\$\.[A-Za-z]/
14
+
15
+ ## FIXME process_file[!] should call process[_text]
16
+
17
+ def process_file(fname, btrace=false)
18
+ setfile(fname)
19
+ text = File.readlines(fname)
20
+ enum = text.each
21
+ @backtrace = btrace
22
+ @main.source(enum, fname, 0)
23
+ line = nil
24
+ loop do
25
+ line = @main.nextline
26
+ break if line.nil?
27
+ process_line(line)
28
+ end
29
+ val = @main.finalize if @main.respond_to? :finalize
30
+ @body
31
+ end
32
+
33
+ def process_line(line) # FIXME inefficient?
34
+ nomarkup = true
35
+ case line # must apply these in order
36
+ when Comment
37
+ handle_scomment(line)
38
+ when Dotcmd
39
+ handle_dotcmd(line)
40
+ when Ddotcmd
41
+ indent = line.index("$") + 1
42
+ @indentation.push(indent)
43
+ line.sub!(/^ *\$/, "")
44
+ handle_dotcmd(line)
45
+ indentation.pop
46
+ else
47
+ @main._passthru(line)
48
+ end
49
+ end
50
+
51
+ def handle_dotcmd(line, indent = 0)
52
+ indent = @indentation.last # top of stack
53
+ line = line.sub(/# .*$/, "")
54
+ name = get_name(line).to_sym
55
+ result = nil
56
+ case
57
+ when name == :end # special case
58
+ puts @body
59
+ raise EndWithoutOpening()
60
+ when @main.respond_to?(name)
61
+ result = @main.send(name)
62
+ else
63
+ puts @body # earlier correct output, not flushed yet
64
+ raise "Name '#{name}' is unknown"
65
+ return
66
+ end
67
+ result
68
+ end
69
+
70
+ def handle_scomment(line)
71
+ end
72
+
73
+ def get_name(line)
74
+ name, data = line.split(" ", 2)
75
+ name = name[1..-1] # chop off sigil
76
+ name = "dot_" + name if %w[include def].include?(name)
77
+ @main.data = data
78
+ @main.check_disallowed(name)
79
+ name
80
+ end
81
+
4
82
  def check_disallowed(name)
5
- # raise "Illegal name '#{name}'" if _disallowed?(name)
6
- # FIXME use custom exception
7
- raise DisallowedName, name if _disallowed?(name)
83
+ raise DisallowedName(name) if disallowed?(name)
8
84
  end
9
85
 
10
86
  def check_file_exists(file)
@@ -14,7 +90,7 @@ module Helpers
14
90
  def set_variables(pairs)
15
91
  pairs.each do |pair|
16
92
  var, value = *pair
17
- @parent._setvar(var, value)
93
+ @parent.setvar(var, value)
18
94
  end
19
95
  end
20
96
 
@@ -22,4 +98,66 @@ module Helpers
22
98
  File.read(fname)
23
99
  end
24
100
 
101
+ def search_upward(file)
102
+ value = nil
103
+ return file if File.exist?(file)
104
+
105
+ count = 1
106
+ loop do
107
+ front = "../" * count
108
+ count += 1
109
+ here = Pathname.new(front).expand_path.dirname.to_s
110
+ break if here == "/"
111
+ path = front + file
112
+ value = path if File.exist?(path)
113
+ break if value
114
+ end
115
+ STDERR.puts "Cannot find #{file.inspect} from #{Dir.pwd}" unless value
116
+ return value
117
+ rescue
118
+ STDERR.puts "Can't find #{file.inspect} from #{Dir.pwd}"
119
+ return nil
120
+ end
121
+
122
+ def include_file(file)
123
+ @_args = [file]
124
+ dot_include
125
+ end
126
+
127
+ def onoff(arg) # helper
128
+ arg ||= "on"
129
+ raise ExpectedOnOff unless String === arg
130
+ case arg.downcase
131
+ when "on"
132
+ return true
133
+ when "off"
134
+ return false
135
+ else
136
+ raise ExpectedOnOff
137
+ end
138
+ end
139
+
140
+ def setvar(var, val)
141
+ str, sym = var.to_s, var.to_sym
142
+ Livetext::Vars[str] = val
143
+ Livetext::Vars[sym] = val
144
+ @_vars[str] = val
145
+ @_vars[sym] = val
146
+ end
147
+
148
+ def setfile(file)
149
+ if file
150
+ setvar(:File, file)
151
+ dir = File.dirname(File.expand_path(file))
152
+ setvar(:FileDir, dir)
153
+ else
154
+ setvar(:File, "[no file]")
155
+ setvar(:FileDir, "[no dir]")
156
+ end
157
+ end
158
+
159
+ def setfile!(file) # FIXME why does this variant exist?
160
+ setvar(:File, file)
161
+ end
162
+
25
163
  end