livetext 0.9.25 → 0.9.30

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/README.lt3 +3 -2
  3. data/imports/bookish.rb +1 -2
  4. data/lib/livetext/errors.rb +3 -0
  5. data/lib/livetext/expansion.rb +106 -0
  6. data/lib/livetext/formatter.rb +104 -0
  7. data/lib/livetext/functions.rb +9 -0
  8. data/lib/livetext/global_helpers.rb +5 -0
  9. data/lib/livetext/handler/import.rb +2 -6
  10. data/lib/livetext/handler/mixin.rb +2 -6
  11. data/lib/livetext/helpers.rb +30 -27
  12. data/lib/livetext/html.rb +73 -0
  13. data/lib/livetext/more.rb +178 -0
  14. data/lib/livetext/parser/general.rb +1 -1
  15. data/lib/livetext/parser/set.rb +10 -3
  16. data/lib/livetext/parser/string.rb +14 -5
  17. data/lib/livetext/processor.rb +8 -1
  18. data/lib/livetext/skeleton.rb +2 -1
  19. data/lib/livetext/standard.rb +30 -16
  20. data/lib/livetext/userapi.rb +61 -22
  21. data/lib/livetext/version.rb +1 -1
  22. data/lib/livetext.rb +2 -152
  23. data/plugin/bootstrap_menu.rb +140 -0
  24. data/plugin/misc/navbar.rb +162 -0
  25. data/test/snapshots/basic_formatting/expected-output.txt +1 -2
  26. data/test/snapshots/{import_bookish/toc.tmp → bootstrap_menu/expected-error.txt} +0 -0
  27. data/test/snapshots/bootstrap_menu/expected-output.txt +4 -0
  28. data/test/snapshots/bootstrap_menu/source.lt3 +17 -0
  29. data/test/snapshots/error_invalid_name/foo +5 -0
  30. data/test/snapshots/import_bookish/expected-output.txt +4 -4
  31. data/test/snapshots/more_functions/expected-output.txt +1 -1
  32. data/test/snapshots/more_functions/source.lt3 +1 -1
  33. data/test/snapshots/subset.txt +50 -46
  34. data/test/snapshots/{mixin_bookish/toc.tmp → var_into_func/expected-error.txt} +0 -0
  35. data/test/snapshots/var_into_func/expected-output.txt +16 -0
  36. data/test/snapshots/var_into_func/source.lt3 +16 -0
  37. data/test/unit/all.rb +2 -1
  38. data/test/unit/lineparser.rb +359 -0
  39. data/test/unit/new_lineparser.rb +359 -0
  40. data/test/unit/parser/general.rb +2 -2
  41. data/test/unit/parser/set.rb +12 -20
  42. metadata +16 -11
  43. data/lib/livetext/formatline.rb +0 -321
  44. data/lib/livetext/funcall.rb +0 -84
  45. data/test/snapshots/error_inc_line_num/OUT +0 -17
  46. data/test/snapshots/error_no_such_copy/duh +0 -26
  47. data/test/snapshots/error_no_such_copy/mystery.txt +0 -36
  48. data/test/testlines.rb +0 -37
  49. 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
@@ -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
- value = quote?(char) ? quoted_value : unquoted_value
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
- char = @line[@i]
17
- @i += 1
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
- @line[@i]
50
+ i2 = @i + n - 1
51
+ @line[@i..i2]
43
52
  end
44
53
 
45
54
  def skip_spaces
@@ -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)
@@ -9,9 +9,10 @@ class Livetext
9
9
  module ParsingConstants
10
10
  end
11
11
 
12
- class FormatLine < StringParser
12
+ class LineParser < StringParser
13
13
  module FunCall
14
14
  end
15
15
  end
16
+
16
17
  end
17
18
 
@@ -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
- api.data = val.chomp
30
- # @args = val.split rescue []
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
- str = "def #{name}\n"
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
- set_variables(pairs)
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(prefix, lines)
176
- set_variables(pairs)
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(prefix, lines)
191
- set_variables(pairs)
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.setvar(var, rhs.chomp)
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
- api.out api.data # No processing at all
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
 
@@ -1,22 +1,46 @@
1
-
2
- require_relative 'formatline'
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 setvar(var, val)
16
- # FIXME don't like this...
17
- str, sym = var.to_s, var.to_sym
18
- Livetext::Vars[str] = val
19
- Livetext::Vars[sym] = val
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
- def vars
37
- @vars
38
- end
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
- sigil = Livetext::Sigil
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 = Livetext::FormatLine.parse!(line)
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
- out "<p>" if line == "\n" and ! @live.nopara
121
- line = format(line)
122
- out line
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)
@@ -2,5 +2,5 @@
2
2
  # Defining VERSION
3
3
 
4
4
  class Livetext
5
- VERSION = "0.9.25"
5
+ VERSION = "0.9.30"
6
6
  end
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/formatline'
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