livetext 0.9.25 → 0.9.30
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.lt3 +3 -2
- data/imports/bookish.rb +1 -2
- data/lib/livetext/errors.rb +3 -0
- data/lib/livetext/expansion.rb +106 -0
- data/lib/livetext/formatter.rb +104 -0
- data/lib/livetext/functions.rb +9 -0
- data/lib/livetext/global_helpers.rb +5 -0
- data/lib/livetext/handler/import.rb +2 -6
- data/lib/livetext/handler/mixin.rb +2 -6
- data/lib/livetext/helpers.rb +30 -27
- data/lib/livetext/html.rb +73 -0
- data/lib/livetext/more.rb +178 -0
- data/lib/livetext/parser/general.rb +1 -1
- data/lib/livetext/parser/set.rb +10 -3
- data/lib/livetext/parser/string.rb +14 -5
- data/lib/livetext/processor.rb +8 -1
- data/lib/livetext/skeleton.rb +2 -1
- data/lib/livetext/standard.rb +30 -16
- data/lib/livetext/userapi.rb +61 -22
- data/lib/livetext/version.rb +1 -1
- data/lib/livetext.rb +2 -152
- data/plugin/bootstrap_menu.rb +140 -0
- data/plugin/misc/navbar.rb +162 -0
- data/test/snapshots/basic_formatting/expected-output.txt +1 -2
- data/test/snapshots/{import_bookish/toc.tmp → bootstrap_menu/expected-error.txt} +0 -0
- data/test/snapshots/bootstrap_menu/expected-output.txt +4 -0
- data/test/snapshots/bootstrap_menu/source.lt3 +17 -0
- data/test/snapshots/error_invalid_name/foo +5 -0
- data/test/snapshots/import_bookish/expected-output.txt +4 -4
- data/test/snapshots/more_functions/expected-output.txt +1 -1
- data/test/snapshots/more_functions/source.lt3 +1 -1
- data/test/snapshots/subset.txt +50 -46
- data/test/snapshots/{mixin_bookish/toc.tmp → var_into_func/expected-error.txt} +0 -0
- data/test/snapshots/var_into_func/expected-output.txt +16 -0
- data/test/snapshots/var_into_func/source.lt3 +16 -0
- data/test/unit/all.rb +2 -1
- data/test/unit/lineparser.rb +359 -0
- data/test/unit/new_lineparser.rb +359 -0
- data/test/unit/parser/general.rb +2 -2
- data/test/unit/parser/set.rb +12 -20
- metadata +16 -11
- data/lib/livetext/formatline.rb +0 -321
- data/lib/livetext/funcall.rb +0 -84
- data/test/snapshots/error_inc_line_num/OUT +0 -17
- data/test/snapshots/error_no_such_copy/duh +0 -26
- data/test/snapshots/error_no_such_copy/mystery.txt +0 -36
- data/test/testlines.rb +0 -37
- data/test/unit/formatline.rb +0 -769
@@ -24,7 +24,7 @@ class Livetext::ParseGeneral < StringParser
|
|
24
24
|
lines.each do |line|
|
25
25
|
next if line.strip.empty?
|
26
26
|
var, value = line.split(" ", 2)
|
27
|
-
val = Livetext.interpolate(value)
|
27
|
+
# val = Livetext.interpolate(value)
|
28
28
|
var = prefix + "." + var if prefix
|
29
29
|
pairs << [var, value]
|
30
30
|
end
|
data/lib/livetext/parser/set.rb
CHANGED
@@ -53,8 +53,9 @@ class Livetext::ParseSet < StringParser
|
|
53
53
|
var = get_var
|
54
54
|
skip_equal
|
55
55
|
value = get_value
|
56
|
-
value = Livetext.interpolate(value)
|
56
|
+
# value = Livetext.interpolate(value)
|
57
57
|
pair = [var, value]
|
58
|
+
Livetext::Vars[var.to_sym] = value
|
58
59
|
pair
|
59
60
|
end
|
60
61
|
|
@@ -115,10 +116,11 @@ class Livetext::ParseSet < StringParser
|
|
115
116
|
end
|
116
117
|
|
117
118
|
def unquoted_value
|
118
|
-
char = nil
|
119
119
|
value = ""
|
120
|
+
char = nil
|
120
121
|
loop do
|
121
122
|
char = peek
|
123
|
+
break if char.nil?
|
122
124
|
break if eos?
|
123
125
|
break if char == " " || char == ","
|
124
126
|
value << char
|
@@ -133,7 +135,12 @@ class Livetext::ParseSet < StringParser
|
|
133
135
|
|
134
136
|
def get_value
|
135
137
|
char = peek
|
136
|
-
|
138
|
+
flag = quote?(char)
|
139
|
+
if flag
|
140
|
+
value = quoted_value
|
141
|
+
else
|
142
|
+
value = unquoted_value
|
143
|
+
end
|
137
144
|
value
|
138
145
|
end
|
139
146
|
end
|
@@ -11,10 +11,12 @@ class StringParser
|
|
11
11
|
@i = 0
|
12
12
|
end
|
13
13
|
|
14
|
-
def grab
|
14
|
+
def grab(n = 1)
|
15
|
+
raise "n <= 0 for #grab" if n <= 0
|
15
16
|
return nil if @eos
|
16
|
-
|
17
|
-
@i
|
17
|
+
i2 = @i + n - 1
|
18
|
+
char = @line[@i..i2]
|
19
|
+
@i += n
|
18
20
|
check_eos
|
19
21
|
char
|
20
22
|
end
|
@@ -25,9 +27,14 @@ class StringParser
|
|
25
27
|
end
|
26
28
|
|
27
29
|
def lookahead
|
30
|
+
# Get rid of this?
|
28
31
|
@line[@i + 1]
|
29
32
|
end
|
30
33
|
|
34
|
+
def remainder
|
35
|
+
@line[@i..-1]
|
36
|
+
end
|
37
|
+
|
31
38
|
def prev
|
32
39
|
return nil if @i <= 0
|
33
40
|
@line[@i-1]
|
@@ -37,9 +44,11 @@ class StringParser
|
|
37
44
|
@eos
|
38
45
|
end
|
39
46
|
|
40
|
-
def peek
|
47
|
+
def peek(n = 1)
|
48
|
+
raise "n <= 0 for #grab" if n <= 0
|
41
49
|
return nil if @eos
|
42
|
-
@
|
50
|
+
i2 = @i + n - 1
|
51
|
+
@line[@i..i2]
|
43
52
|
end
|
44
53
|
|
45
54
|
def skip_spaces
|
data/lib/livetext/processor.rb
CHANGED
@@ -35,12 +35,17 @@ class Processor
|
|
35
35
|
@indentation = @parent.indentation
|
36
36
|
@_mixins = []
|
37
37
|
@_imports = []
|
38
|
+
@html = HTML.new(@parent.api)
|
38
39
|
end
|
39
40
|
|
40
41
|
def api
|
41
42
|
@parent.api # FIXME Is this weird??
|
42
43
|
end
|
43
44
|
|
45
|
+
def html
|
46
|
+
@html
|
47
|
+
end
|
48
|
+
|
44
49
|
def output=(io)
|
45
50
|
@output = io
|
46
51
|
end
|
@@ -57,7 +62,9 @@ class Processor
|
|
57
62
|
end
|
58
63
|
|
59
64
|
def disallowed?(name)
|
60
|
-
Disallowed.include?(name.to_sym)
|
65
|
+
flag = Disallowed.include?(name.to_sym)
|
66
|
+
# api.tty "disa name = #{name.inspect} flag = #{flag}"
|
67
|
+
flag
|
61
68
|
end
|
62
69
|
|
63
70
|
def source(enum, file, line)
|
data/lib/livetext/skeleton.rb
CHANGED
data/lib/livetext/standard.rb
CHANGED
@@ -25,13 +25,13 @@ module Livetext::Standard
|
|
25
25
|
|
26
26
|
attr_reader :data
|
27
27
|
|
28
|
-
def data=(val) # FIXME this is weird, let's remove it soonish
|
29
|
-
|
30
|
-
|
28
|
+
def data=(val) # FIXME this is weird, let's remove it soonish and why are there two???
|
29
|
+
# api.tty ">>>> in #{__FILE__}: api id = #{api.object_id}"
|
30
|
+
val = val.chomp
|
31
|
+
api.data = val
|
32
|
+
api.args = format(val).split rescue []
|
31
33
|
@mixins = []
|
32
34
|
@imports = []
|
33
|
-
###
|
34
|
-
# api.data = val
|
35
35
|
end
|
36
36
|
|
37
37
|
# dumb name - bold, italic, teletype, striketrough
|
@@ -45,6 +45,12 @@ module Livetext::Standard
|
|
45
45
|
api.optional_blank_line
|
46
46
|
end
|
47
47
|
|
48
|
+
# def setvars(pairs)
|
49
|
+
# pairs.each do |var, value|
|
50
|
+
# api.setvar(var, value)
|
51
|
+
# end
|
52
|
+
# end
|
53
|
+
|
48
54
|
def backtrace(args = nil, body = nil)
|
49
55
|
@backtrace = onoff(api.args.first)
|
50
56
|
api.optional_blank_line
|
@@ -63,7 +69,8 @@ module Livetext::Standard
|
|
63
69
|
|
64
70
|
def func(args = nil, body = nil)
|
65
71
|
funcname = api.args[0]
|
66
|
-
check_disallowed(funcname)
|
72
|
+
# check_disallowed(funcname) # should any be invalid?
|
73
|
+
funcname = funcname.gsub(/\./, "__")
|
67
74
|
func_def = <<~EOS
|
68
75
|
def #{funcname}(param)
|
69
76
|
#{api.body.to_a.join("\n")}
|
@@ -143,10 +150,14 @@ module Livetext::Standard
|
|
143
150
|
end
|
144
151
|
|
145
152
|
def dot_def(args = nil, body = nil)
|
153
|
+
# api.tty "in #{__FILE__}: api id = #{api.inspect}"
|
146
154
|
name = api.args[0]
|
147
|
-
|
155
|
+
# api.tty :dd1
|
156
|
+
# api.tty name.inspect
|
148
157
|
check_disallowed(name)
|
158
|
+
# api.tty :dd2
|
149
159
|
# Difficult to avoid eval here
|
160
|
+
str = "def #{name}\n"
|
150
161
|
str << api.body(true).join("\n")
|
151
162
|
str << "\nend\n"
|
152
163
|
eval str
|
@@ -154,9 +165,9 @@ module Livetext::Standard
|
|
154
165
|
end
|
155
166
|
|
156
167
|
def set(args = nil, body = nil)
|
157
|
-
line = api.data.chomp
|
168
|
+
line = api.args.join(" ") # data.chomp
|
158
169
|
pairs = Livetext::ParseSet.new(line).parse
|
159
|
-
|
170
|
+
api.setvars(pairs)
|
160
171
|
api.optional_blank_line
|
161
172
|
end
|
162
173
|
|
@@ -172,8 +183,9 @@ module Livetext::Standard
|
|
172
183
|
else
|
173
184
|
lines = api.body
|
174
185
|
end
|
175
|
-
pairs = Livetext::ParseGeneral.parse_vars(
|
176
|
-
|
186
|
+
pairs = Livetext::ParseGeneral.parse_vars(lines, prefix: nil)
|
187
|
+
STDERR.puts "! pairs = #{pairs.inspect}"
|
188
|
+
api.setvars(pairs)
|
177
189
|
api.optional_blank_line
|
178
190
|
end
|
179
191
|
|
@@ -187,8 +199,9 @@ module Livetext::Standard
|
|
187
199
|
else
|
188
200
|
lines = api.body
|
189
201
|
end
|
190
|
-
pairs = Livetext::ParseGeneral.parse_vars(
|
191
|
-
|
202
|
+
pairs = Livetext::ParseGeneral.parse_vars(lines, prefix: nil)
|
203
|
+
STDERR.puts "pairs = #{pairs.inspect}"
|
204
|
+
api.setvars(pairs)
|
192
205
|
api.optional_blank_line
|
193
206
|
end
|
194
207
|
|
@@ -202,8 +215,7 @@ module Livetext::Standard
|
|
202
215
|
end
|
203
216
|
indent = @parent.indentation.last
|
204
217
|
indented = " " * indent
|
205
|
-
api.
|
206
|
-
# @parent.setvar(var, rhs.chomp)
|
218
|
+
api.set(var, rhs.chomp)
|
207
219
|
api.optional_blank_line
|
208
220
|
end
|
209
221
|
|
@@ -267,7 +279,9 @@ module Livetext::Standard
|
|
267
279
|
end
|
268
280
|
|
269
281
|
def r(args = nil, body = nil)
|
270
|
-
|
282
|
+
# FIXME api.data is broken
|
283
|
+
# api.out api.data # No processing at all
|
284
|
+
api.out api.args.join(" ")
|
271
285
|
api.optional_blank_line
|
272
286
|
end
|
273
287
|
|
data/lib/livetext/userapi.rb
CHANGED
@@ -1,22 +1,46 @@
|
|
1
|
-
|
2
|
-
require_relative '
|
1
|
+
require_relative 'expansion'
|
2
|
+
require_relative 'html'
|
3
3
|
|
4
4
|
# Encapsulate the UserAPI as a class
|
5
5
|
|
6
6
|
class Livetext::UserAPI
|
7
7
|
|
8
|
+
KBD = File.new("/dev/tty", "r")
|
9
|
+
TTY = File.new("/dev/tty", "w")
|
10
|
+
|
11
|
+
DotSpace = ". " # Livetext::Sigil + Livetext::Space
|
12
|
+
|
8
13
|
attr_accessor :data, :args
|
9
14
|
|
10
15
|
def initialize(live)
|
11
16
|
@live = live
|
12
17
|
@vars = live.vars
|
18
|
+
@html = HTML.new(self)
|
19
|
+
@expander = Livetext::Expansion.new(live)
|
13
20
|
end
|
14
21
|
|
15
|
-
def
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
22
|
+
def api
|
23
|
+
@live.api
|
24
|
+
end
|
25
|
+
|
26
|
+
def html
|
27
|
+
@html
|
28
|
+
end
|
29
|
+
|
30
|
+
def dot
|
31
|
+
@live
|
32
|
+
end
|
33
|
+
|
34
|
+
def setvar(var, val) # FIXME
|
35
|
+
# Livetext::Vars[var] = val # Now indifferent and "safe"
|
36
|
+
@live.vars.set(var, val)
|
37
|
+
end
|
38
|
+
|
39
|
+
def setvars(pairs)
|
40
|
+
pairs = pairs.to_a if pairs.is_a?(Hash)
|
41
|
+
pairs.each do |var, value|
|
42
|
+
@live.vars.set(var, value)
|
43
|
+
end
|
20
44
|
end
|
21
45
|
|
22
46
|
def check_existence(file, msg)
|
@@ -28,14 +52,18 @@ class Livetext::UserAPI
|
|
28
52
|
@args = format(@data).chomp.split
|
29
53
|
end
|
30
54
|
|
55
|
+
def data
|
56
|
+
@data
|
57
|
+
end
|
58
|
+
|
31
59
|
def args
|
32
60
|
return @args unless block_given?
|
33
61
|
@args.each {|arg| yield arg }
|
34
62
|
end
|
35
63
|
|
36
|
-
|
37
|
-
|
38
|
-
|
64
|
+
def vars
|
65
|
+
@vars
|
66
|
+
end
|
39
67
|
|
40
68
|
def optional_blank_line
|
41
69
|
peek = @live.peek_nextline # ???
|
@@ -45,9 +73,7 @@ class Livetext::UserAPI
|
|
45
73
|
end
|
46
74
|
|
47
75
|
def comment?(str)
|
48
|
-
|
49
|
-
c1 = sigil + Livetext::Space
|
50
|
-
str.index(c1) == 0
|
76
|
+
str.index(DotSpace) == 0
|
51
77
|
end
|
52
78
|
|
53
79
|
def trailing?(char)
|
@@ -84,7 +110,7 @@ class Livetext::UserAPI
|
|
84
110
|
break if end?(@line)
|
85
111
|
next if comment?(@line)
|
86
112
|
@line = format(@line) unless raw
|
87
|
-
lines << @line
|
113
|
+
lines << @line
|
88
114
|
end
|
89
115
|
raise "Expected .end, found end of file" unless end?(@line) # use custom exception
|
90
116
|
optional_blank_line # FIXME Delete this??
|
@@ -110,22 +136,27 @@ class Livetext::UserAPI
|
|
110
136
|
|
111
137
|
def format(line)
|
112
138
|
return "" if line == "\n" || line.nil?
|
113
|
-
line2 =
|
114
|
-
# line.replace(line2)
|
139
|
+
line2 = @expander.format(line)
|
115
140
|
line2
|
116
141
|
end
|
117
142
|
|
118
143
|
def passthru(line)
|
119
144
|
return if @live.nopass
|
120
|
-
|
121
|
-
|
122
|
-
|
145
|
+
if line == "\n"
|
146
|
+
unless @live.nopara
|
147
|
+
out "<p>"
|
148
|
+
out
|
149
|
+
end
|
150
|
+
else
|
151
|
+
text = @expander.format(line.chomp)
|
152
|
+
out text
|
153
|
+
end
|
123
154
|
end
|
124
155
|
|
125
156
|
def out(str = "", file = nil)
|
126
157
|
return if str.nil?
|
127
158
|
return file.puts str unless file.nil?
|
128
|
-
@live.body << str
|
159
|
+
@live.body << str
|
129
160
|
@live.body << "\n" unless str.end_with?("\n")
|
130
161
|
end
|
131
162
|
|
@@ -133,12 +164,20 @@ class Livetext::UserAPI
|
|
133
164
|
@live.body << str # no newline
|
134
165
|
end
|
135
166
|
|
167
|
+
def tty(*args)
|
168
|
+
TTY.puts *args
|
169
|
+
end
|
170
|
+
|
171
|
+
def err(*args)
|
172
|
+
STDERR.puts *args
|
173
|
+
end
|
174
|
+
|
136
175
|
def puts(*args)
|
137
|
-
@live.output.puts *args
|
176
|
+
@live.output.puts *args
|
138
177
|
end
|
139
178
|
|
140
179
|
def print(*args)
|
141
|
-
@live.output.print *args
|
180
|
+
@live.output.print *args
|
142
181
|
end
|
143
182
|
|
144
183
|
def debug=(val)
|
data/lib/livetext/version.rb
CHANGED
data/lib/livetext.rb
CHANGED
@@ -9,160 +9,10 @@ require_relative 'livetext/errors'
|
|
9
9
|
require_relative 'livetext/standard'
|
10
10
|
require_relative 'livetext/functions'
|
11
11
|
require_relative 'livetext/userapi'
|
12
|
-
require_relative 'livetext/
|
12
|
+
require_relative 'livetext/formatter'
|
13
13
|
require_relative 'livetext/processor'
|
14
14
|
require_relative 'livetext/helpers'
|
15
15
|
require_relative 'livetext/handler'
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
make_exception(:EndWithoutOpening, "Error: found .end with no opening command")
|
20
|
-
make_exception(:UnknownMethod, "Error: name '%1' is unknown")
|
21
|
-
|
22
|
-
# Class Livetext reopened (top level).
|
23
|
-
|
24
|
-
class Livetext
|
25
|
-
|
26
|
-
include Helpers
|
27
|
-
|
28
|
-
Vars = {}
|
29
|
-
|
30
|
-
TTY = ::File.open("/dev/tty", "w")
|
31
|
-
|
32
|
-
attr_reader :main, :sources
|
33
|
-
attr_accessor :nopass, :nopara
|
34
|
-
attr_accessor :body, :indentation
|
35
|
-
|
36
|
-
class << self
|
37
|
-
attr_accessor :output # bad solution?
|
38
|
-
end
|
39
|
-
|
40
|
-
def vars
|
41
|
-
@_vars
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.interpolate(str)
|
45
|
-
# FIXME There are issues here...
|
46
|
-
# Livetext::FormatLine.var_func_parse(str)
|
47
|
-
parse = Livetext::FormatLine.new(str)
|
48
|
-
parse.var_func_parse
|
49
|
-
end
|
50
|
-
|
51
|
-
def self.customize(mix: [], call: [], vars: {})
|
52
|
-
obj = self.new
|
53
|
-
mix = Array(mix)
|
54
|
-
call = Array(call)
|
55
|
-
mix.each {|lib| obj.mixin(lib) }
|
56
|
-
call.each {|cmd| obj.main.send(cmd[1..-1]) } # ignores leading dot, no param
|
57
|
-
vars.each_pair {|var, val| obj.setvar(var, val.to_s) }
|
58
|
-
obj
|
59
|
-
end
|
60
|
-
|
61
|
-
def peek_nextline
|
62
|
-
@main.peek_nextline # delegate
|
63
|
-
end
|
64
|
-
|
65
|
-
def nextline
|
66
|
-
@main.nextline # delegate
|
67
|
-
end
|
68
|
-
|
69
|
-
def sources
|
70
|
-
@main.sources # delegate
|
71
|
-
end
|
72
|
-
|
73
|
-
def save_location
|
74
|
-
@save_location # delegate
|
75
|
-
end
|
76
|
-
|
77
|
-
def save_location=(where)
|
78
|
-
@save_location = where # delegate
|
79
|
-
end
|
80
|
-
|
81
|
-
def dump(file = nil) # not a dot command!
|
82
|
-
file ||= ::STDOUT
|
83
|
-
file.puts @body
|
84
|
-
rescue => err
|
85
|
-
TTY.puts "#dump had an error: #{err.inspect}"
|
86
|
-
end
|
87
|
-
|
88
|
-
def graceful_error(err)
|
89
|
-
dump
|
90
|
-
raise err
|
91
|
-
end
|
92
|
-
|
93
|
-
def customize(mix: [], call: [], vars: {})
|
94
|
-
mix = Array(mix)
|
95
|
-
call = Array(call)
|
96
|
-
mix.each {|lib| mixin(lib) }
|
97
|
-
call.each {|cmd| @main.send(cmd[1..-1]) } # ignores leading dot, no param
|
98
|
-
vars.each_pair {|var, val| setvar(var, val.to_s) }
|
99
|
-
self
|
100
|
-
end
|
101
|
-
|
102
|
-
def initialize(output = ::STDOUT)
|
103
|
-
@source = nil
|
104
|
-
@_mixins = []
|
105
|
-
@_imports = []
|
106
|
-
@_outdir = "."
|
107
|
-
@no_puts = output.nil?
|
108
|
-
@body = ""
|
109
|
-
@main = Processor.new(self, output)
|
110
|
-
@indentation = [0]
|
111
|
-
@_vars = Livetext::Vars
|
112
|
-
initial_vars
|
113
|
-
@api = UserAPI.new(self)
|
114
|
-
end
|
115
|
-
|
116
|
-
def api
|
117
|
-
@api
|
118
|
-
end
|
119
|
-
|
120
|
-
def interpolate(str)
|
121
|
-
end
|
122
|
-
|
123
|
-
def initial_vars
|
124
|
-
# Other predefined variables (see also setfile)
|
125
|
-
setvar(:User, `whoami`.chomp)
|
126
|
-
setvar(:Version, Livetext::VERSION)
|
127
|
-
end
|
128
|
-
|
129
|
-
def transform(text)
|
130
|
-
setfile!("(string)")
|
131
|
-
enum = text.each_line
|
132
|
-
front = text.match(/.*?\n/).to_a.first.chomp rescue ""
|
133
|
-
@main.source(enum, "STDIN: '#{front}...'", 0)
|
134
|
-
loop do
|
135
|
-
line = @main.nextline
|
136
|
-
break if line.nil?
|
137
|
-
process_line(line)
|
138
|
-
end
|
139
|
-
result = @body
|
140
|
-
# @body = ""
|
141
|
-
result
|
142
|
-
end
|
143
|
-
|
144
|
-
# EXPERIMENTAL and incomplete
|
145
|
-
def xform(*args, file: nil, text: nil, vars: {})
|
146
|
-
case
|
147
|
-
when file && text.nil?
|
148
|
-
xform_file(file)
|
149
|
-
when file.nil? && text
|
150
|
-
transform(text)
|
151
|
-
when file.nil? && text.nil?
|
152
|
-
raise "Must specify file or text"
|
153
|
-
when file && text
|
154
|
-
raise "Cannot specify file and text"
|
155
|
-
end
|
156
|
-
self.process_file(file)
|
157
|
-
self.body
|
158
|
-
end
|
159
|
-
|
160
|
-
def xform_file(file, vars: nil)
|
161
|
-
Livetext::Vars.replace(vars) unless vars.nil?
|
162
|
-
@_vars.replace(vars) unless vars.nil?
|
163
|
-
self.process_file(file)
|
164
|
-
self.body
|
165
|
-
end
|
166
|
-
|
167
|
-
end
|
17
|
+
require_relative 'livetext/more'
|
168
18
|
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# https://getbootstrap.com/docs/5.1/components/navs-tabs/#base-nav
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
def api_out(indent_level, content)
|
6
|
+
api.out "#{(" " * (indent_level * indent_size))}#{content}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def indent_size; 2; end
|
10
|
+
|
11
|
+
def menu(indent_level: 0, &block)
|
12
|
+
api_out indent_level, "<ul class='nav'>"
|
13
|
+
read_to_end_of_block(root_indent_level: indent_level)
|
14
|
+
yield
|
15
|
+
api_out indent_level, "</ul>"
|
16
|
+
end
|
17
|
+
|
18
|
+
def menu_dropdown(title, indent_level: 0, &block)
|
19
|
+
api_out indent_level, '<li class="nav-item dropdown">'
|
20
|
+
api_out indent_level, " <a class='nav-link dropdown-toggle' data-bs-toggle='dropdown' href='#' role='button' aria-expanded='false'"
|
21
|
+
api_out indent_level, " #{title}"
|
22
|
+
api_out indent_level, ' </a>'
|
23
|
+
|
24
|
+
api_out indent_level, ' <ul class="dropdown-menu">'
|
25
|
+
yield
|
26
|
+
api_out indent_level, ' </ul>'
|
27
|
+
|
28
|
+
api_out indent_level, '</li>'
|
29
|
+
end
|
30
|
+
|
31
|
+
def menu_link(title, href, active: false, indent_level: 0)
|
32
|
+
# Work out if it's possible to determine active state or not
|
33
|
+
api_out indent_level, "<a class='nav-link #{"active" if active}' aria-current='page' href='#{href}'"
|
34
|
+
api_out indent_level, " #{title}"
|
35
|
+
api_out indent_level, '</a>'
|
36
|
+
end
|
37
|
+
|
38
|
+
def menu_dropdown_link(title, href, active: false, indent_level: 0)
|
39
|
+
# Work out if it's possible to determine active state or not
|
40
|
+
api_out indent_level, "<a class='dropdown-item #{"active" if active}' href='#{href}'"
|
41
|
+
api_out indent_level, " #{title}"
|
42
|
+
api_out indent_level, '</a>'
|
43
|
+
end
|
44
|
+
|
45
|
+
def next_line
|
46
|
+
nextline
|
47
|
+
end
|
48
|
+
|
49
|
+
def peek_next_line
|
50
|
+
peek_nextline
|
51
|
+
end
|
52
|
+
|
53
|
+
def line_indent_level(line)
|
54
|
+
indent_level = (line.size - line.lstrip.size) / indent_size.to_f
|
55
|
+
raise "Unbalanced Indenting: Expecting #{indent_size} indents" if indent_level.to_i != indent_level
|
56
|
+
indent_level = indent_level.to_i
|
57
|
+
end
|
58
|
+
|
59
|
+
def line_command(line)
|
60
|
+
base_line = line.strip
|
61
|
+
|
62
|
+
base_command = base_line.split.first
|
63
|
+
|
64
|
+
if base_command.start_with?(".")
|
65
|
+
return base_command.gsub(".")
|
66
|
+
else
|
67
|
+
raise "Command Expected: #{base_line}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def line_attributes_array(line)
|
72
|
+
line_attributes = line.strip.split("", 2).last
|
73
|
+
api.out 0, line_attributes.inspect
|
74
|
+
|
75
|
+
attributes_parsed = JSON.parse(line_attributes)
|
76
|
+
attributes_parsed.is_a?(String) ? [attributes_parsed] : attributes_parsed
|
77
|
+
end
|
78
|
+
|
79
|
+
def read_to_end_of_block(root_indent_level: 0)
|
80
|
+
expected_indent_level = root_indent_level + 1
|
81
|
+
loop do
|
82
|
+
peeked_line = peek_next_line
|
83
|
+
return if peeked_line.nil? # End of File
|
84
|
+
unless peeked_line.strip.size > 0 # Blank line
|
85
|
+
next_line
|
86
|
+
next
|
87
|
+
end
|
88
|
+
|
89
|
+
line_indent_level = line_indent_level(peeked_line)
|
90
|
+
|
91
|
+
if expected_indent_level != line_indent_level
|
92
|
+
raise "Unexpected Indent Level: Is #{line_indent_level} expected #{expected_indent_level}:\n#{peeked_line}"
|
93
|
+
end
|
94
|
+
|
95
|
+
return if root_indent_level >= line_indent_level # End of Block
|
96
|
+
|
97
|
+
line = next_line # Actually move to the next line
|
98
|
+
|
99
|
+
send(line_command(line), *line_attributes_array(line)) do
|
100
|
+
# This block doesn't run if command doesn't accept a block
|
101
|
+
read_to_end_of_block(root_indent_level: line_indent_level)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
# Loop should be something like:
|
107
|
+
# read `.menu` and log current indent level
|
108
|
+
# => send(:menu) do
|
109
|
+
# # Read until indent is
|
110
|
+
# end
|
111
|
+
|
112
|
+
def simple_test
|
113
|
+
menu do
|
114
|
+
menu_link "Home", "http://fake.com/home"
|
115
|
+
menu_dropdown("Blog") do
|
116
|
+
menu_dropdown_link("Home", "http://fake.com/blog")
|
117
|
+
end
|
118
|
+
menu_link "About", "http://fake.com/about"
|
119
|
+
end
|
120
|
+
# This should have the same output as:
|
121
|
+
# .menu
|
122
|
+
# .menu_link
|
123
|
+
# .menu_dropdown ["Blog"]
|
124
|
+
# .menu_dropdown_link ["Home", "http://fake.com/blog"]
|
125
|
+
# .menu_link ["About", "http://fake.com/about"]
|
126
|
+
# or this if we end up sticking with .end
|
127
|
+
# .menu
|
128
|
+
# .menu_link
|
129
|
+
# .menu_dropdown "Blog"
|
130
|
+
# .menu_dropdown_link ["Home", "http://fake.com/blog"]
|
131
|
+
# .end
|
132
|
+
# .menu_link ["About", "http://fake.com/about"]
|
133
|
+
# .end
|
134
|
+
|
135
|
+
|
136
|
+
# Really this should probably just re-use Rails ActionView to avoid a ton of duplicate code
|
137
|
+
# This would allow us to leverage all the Rails HTML generation logic.
|
138
|
+
# Downside of that is that it does break the paradigm a bit in that blocks with errors won't partially render
|
139
|
+
# it does however also means we would get sanitization and such for free
|
140
|
+
end
|