livetext 0.9.27 → 0.9.32
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 -1
- data/lib/livetext/expansion.rb +108 -0
- data/lib/livetext/formatter.rb +223 -0
- data/lib/livetext/functions.rb +9 -0
- data/lib/livetext/helpers.rb +41 -29
- data/lib/livetext/html.rb +73 -0
- data/lib/livetext/more.rb +33 -13
- data/lib/livetext/parser/general.rb +1 -1
- data/lib/livetext/parser/set.rb +10 -3
- data/lib/livetext/processor.rb +5 -0
- data/lib/livetext/standard.rb +67 -67
- data/lib/livetext/userapi.rb +41 -19
- data/lib/livetext/version.rb +1 -1
- data/lib/livetext.rb +1 -1
- data/plugin/bookish.rb +1 -1
- data/plugin/bootstrap_menu.rb +140 -0
- data/plugin/misc/navbar.rb +162 -0
- data/test/extra/README.txt +149 -0
- data/test/extra/bracketed.rb +121 -0
- data/test/extra/bracketed.txt +44 -0
- data/test/extra/double.rb +121 -0
- data/test/extra/double.txt +44 -0
- data/test/extra/functions.rb +148 -0
- data/test/extra/functions.txt +58 -0
- data/test/extra/single.rb +139 -0
- data/test/extra/single.txt +52 -0
- data/test/extra/testgen.rb +104 -0
- data/test/extra/variables.rb +94 -0
- data/test/extra/variables.txt +35 -0
- data/test/snapshots/basic_formatting/expected-output.txt +2 -2
- data/test/snapshots/simple_vars/source.lt3 +1 -1
- data/test/snapshots/subset.txt +49 -49
- data/test/unit/all.rb +1 -2
- data/test/unit/parser/general.rb +2 -2
- data/test/unit/parser/set.rb +0 -9
- metadata +18 -34
- data/lib/livetext/funcall.rb +0 -87
- data/lib/livetext/lineparser.rb +0 -575
- data/test/snapshots/basic_formatting/actual-error.txt +0 -0
- data/test/snapshots/basic_formatting/actual-output.txt +0 -13
- data/test/snapshots/basic_formatting/err-sdiff.txt +0 -1
- data/test/snapshots/basic_formatting/out-sdiff.txt +0 -14
- data/test/snapshots/error_inc_line_num/README.txt +0 -20
- data/test/snapshots/error_invalid_name/foo +0 -5
- data/test/snapshots/functions/actual-error.txt +0 -19
- data/test/snapshots/functions/actual-output.txt +0 -0
- data/test/snapshots/functions/err-sdiff.txt +0 -20
- data/test/snapshots/more_complex_vars/actual-error.txt +0 -0
- data/test/snapshots/more_complex_vars/actual-output.txt +0 -4
- data/test/snapshots/more_complex_vars/err-sdiff.txt +0 -1
- data/test/snapshots/more_complex_vars/out-sdiff.txt +0 -5
- data/test/snapshots/more_functions/actual-error.txt +0 -19
- data/test/snapshots/more_functions/actual-output.txt +0 -0
- data/test/snapshots/more_functions/err-sdiff.txt +0 -20
- data/test/snapshots/raw_lines/actual-error.txt +0 -22
- data/test/snapshots/raw_lines/actual-output.txt +0 -0
- data/test/snapshots/raw_lines/err-sdiff.txt +0 -23
- data/test/snapshots/simple_vars/actual-error.txt +0 -0
- data/test/snapshots/simple_vars/actual-output.txt +0 -6
- data/test/snapshots/simple_vars/err-sdiff.txt +0 -1
- data/test/snapshots/simple_vars/out-sdiff.txt +0 -7
- data/test/snapshots/var_into_func/actual-error.txt +0 -19
- data/test/snapshots/var_into_func/actual-output.txt +0 -0
- data/test/snapshots/var_into_func/err-sdiff.txt +0 -20
- data/test/snapshots/var_into_func/out-sdiff.txt +0 -17
- data/test/unit/lineparser.rb +0 -359
- data/test/unit/new_lineparser.rb +0 -359
- data/test/unit/tokenizer.rb +0 -535
@@ -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
|
@@ -0,0 +1,162 @@
|
|
1
|
+
|
2
|
+
private def get_href_title(line)
|
3
|
+
*title, href = line[1..-1].split(@space)
|
4
|
+
if href[0] == "@"
|
5
|
+
blank = true
|
6
|
+
href = href[1..-1]
|
7
|
+
else
|
8
|
+
blank = false
|
9
|
+
end
|
10
|
+
title = title.join(@space)
|
11
|
+
[href, title, blank]
|
12
|
+
end
|
13
|
+
|
14
|
+
private def add_root(url)
|
15
|
+
return url if url =~ /^http/ # already has http[s]
|
16
|
+
return url unless @root # no root defined
|
17
|
+
range1 = 0..-1
|
18
|
+
range1 = 0..-2 if @root[-1] == "/"
|
19
|
+
root = @root[range1]
|
20
|
+
range2 = 0..-1
|
21
|
+
range2 = 1..-1 if url[0] == "/"
|
22
|
+
url = url[range2]
|
23
|
+
root + "/" + url
|
24
|
+
end
|
25
|
+
|
26
|
+
private def get_brand
|
27
|
+
peek = @enum.peek
|
28
|
+
arg = peek[6..-1]
|
29
|
+
href, item, blank = get_href_title(arg)
|
30
|
+
# doesn't honor @root... and blank is unused here
|
31
|
+
if item =~ /\.(jpg|png|gif)$/i
|
32
|
+
item = "<img src='#{item}'></img>"
|
33
|
+
end
|
34
|
+
details = {class: "navbar-brand mr-auto", href: add_root(href)}
|
35
|
+
@brand = html.tag(:a, **details, cdata: item)
|
36
|
+
end
|
37
|
+
|
38
|
+
private def slash_tags
|
39
|
+
@root = @brand = nil
|
40
|
+
@classes = "navbar-light bg-light"
|
41
|
+
loop do # /brand /root ...
|
42
|
+
peek = @enum.peek
|
43
|
+
break if peek[0] != '/'
|
44
|
+
case
|
45
|
+
when peek.start_with?("/brand ")
|
46
|
+
get_brand
|
47
|
+
when peek.start_with?("/root ")
|
48
|
+
@root = peek[6..-1]
|
49
|
+
when peek.start_with?("/classes ")
|
50
|
+
@classes = peek[9..-1]
|
51
|
+
end
|
52
|
+
line = @enum.next
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
private def handle_children
|
57
|
+
value = ""
|
58
|
+
loop do
|
59
|
+
if @enum.peek.start_with?(@space) # it's a child
|
60
|
+
line = @enum.next
|
61
|
+
href, title, blank = get_href_title(line)
|
62
|
+
details = {class: "dropdown-item", href: add_root(href)}
|
63
|
+
details[:target] = "_blank" if blank
|
64
|
+
link = html.tag(:a, **details, cdata: title)
|
65
|
+
str = html.tag(:li, cdata: link)
|
66
|
+
value << str + "\n"
|
67
|
+
else
|
68
|
+
break
|
69
|
+
end
|
70
|
+
end
|
71
|
+
api.out value
|
72
|
+
end
|
73
|
+
|
74
|
+
private def no_children(href, title)
|
75
|
+
html.li(class: "nav-item") do
|
76
|
+
api.out html.tag(:a, class: "nav-link", href: add_root(href), cdata: title)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
private def has_children(href, title)
|
81
|
+
css = "nav-item dropdown"
|
82
|
+
html.li(class: css) do
|
83
|
+
details = {class: "nav-link dropdown-toggle", href: "#", id: "navbarDropdown",
|
84
|
+
role: "button", :"data-bs-toggle" => "dropdown", :"aria-expanded" => "false"}
|
85
|
+
@dropdowns += 1
|
86
|
+
api.out html.tag(:a, **details, cdata: title)
|
87
|
+
details = {class: "dropdown-menu", :"aria-labelledby" => "navbarDropdown"}
|
88
|
+
html.ul(**details) do # children...
|
89
|
+
handle_children
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private def handle_body
|
95
|
+
@dropdowns = 1
|
96
|
+
loop do
|
97
|
+
line = @enum.next
|
98
|
+
href, title, blank = get_href_title(line)
|
99
|
+
case line[0]
|
100
|
+
when "-"
|
101
|
+
no_children(href, title)
|
102
|
+
when "="
|
103
|
+
has_children(href, title)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def bootstrap
|
109
|
+
api.out <<~HTML
|
110
|
+
<head>
|
111
|
+
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css"
|
112
|
+
rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3"
|
113
|
+
crossorigin="anonymous">
|
114
|
+
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
|
115
|
+
integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
|
116
|
+
crossorigin="anonymous">
|
117
|
+
</script>
|
118
|
+
</head>
|
119
|
+
HTML
|
120
|
+
end
|
121
|
+
|
122
|
+
private def toggler
|
123
|
+
details = {class: "navbar-toggler", type: "button", "data-bs-toggle": "collapse",
|
124
|
+
"data-bs-target": "#navbarSupportedContent",
|
125
|
+
"aria-controls": "navbarSupportedContent", "aria-expanded": "false",
|
126
|
+
"aria-label": "Toggle navigation"}
|
127
|
+
str = html.tag(:button, **details, cdata: '<span class="navbar-toggler-icon"></span>')
|
128
|
+
api.out str
|
129
|
+
end
|
130
|
+
|
131
|
+
private def branding
|
132
|
+
return if @brand.empty? || @brand.nil?
|
133
|
+
api.out @brand
|
134
|
+
end
|
135
|
+
|
136
|
+
def html
|
137
|
+
@html
|
138
|
+
end
|
139
|
+
|
140
|
+
def navbar
|
141
|
+
@html = HTML.new(api)
|
142
|
+
# bootstrap
|
143
|
+
@space = " "
|
144
|
+
@enum = api.body.each
|
145
|
+
slash_tags
|
146
|
+
|
147
|
+
css = "navbar navbar-expand-md " + @classes
|
148
|
+
html.nav(class: css) do
|
149
|
+
html.div(class: "container-fluid") do
|
150
|
+
toggler
|
151
|
+
branding
|
152
|
+
content = {class: "collapse navbar-collapse", id: "navbarSupportedContent"}
|
153
|
+
html.div(**content) do
|
154
|
+
css = "navbar-nav me-auto mb-2 mb-md-0"
|
155
|
+
html.ul(class: css) do
|
156
|
+
handle_body
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
@@ -0,0 +1,149 @@
|
|
1
|
+
The testgen.rb tool takes a .txt and generates a corresponding file
|
2
|
+
of MiniTest code.
|
3
|
+
|
4
|
+
$ ruby testgen.rb variables.txt # produces variables.rb
|
5
|
+
|
6
|
+
The tests here include:
|
7
|
+
|
8
|
+
variables.txt Variable expansion
|
9
|
+
functions.txt Function call evaluation
|
10
|
+
single.txt Single sigil (see Formatting below)
|
11
|
+
double.txt Double sigil (see Formatting below)
|
12
|
+
bracketed.txt Bracketed sigil (see Formatting below)
|
13
|
+
|
14
|
+
|
15
|
+
Variables:
|
16
|
+
----------
|
17
|
+
|
18
|
+
1. A variable begins with a $ and is followed by an alpha; periods may
|
19
|
+
be embedded, but each separate piece must "look like" an identifier
|
20
|
+
|
21
|
+
$x yes
|
22
|
+
$xyz yes
|
23
|
+
$xyz.abc yes
|
24
|
+
$x123 yes
|
25
|
+
$x.123 no
|
26
|
+
$345 no
|
27
|
+
|
28
|
+
2. Rather than causing an error, invalid variables are rendered "as-is"
|
29
|
+
as soon as possible:
|
30
|
+
" $ " => " $ "
|
31
|
+
" $5 " => " $5 "
|
32
|
+
"...$" => "...$" (end of line)
|
33
|
+
|
34
|
+
3. Actual variables may be user-defined or predefined. The latter usually
|
35
|
+
begin with a capital. This is only a convention so far, nothing that is
|
36
|
+
enforced.
|
37
|
+
|
38
|
+
4. The $ may be escaped as needed. This is problematic.
|
39
|
+
|
40
|
+
5. An unknown variable will not raise an error, but will be replaced with
|
41
|
+
a warning string.
|
42
|
+
|
43
|
+
|
44
|
+
Functions:
|
45
|
+
----------
|
46
|
+
|
47
|
+
1. A function looks like a variable name, but it has two $ in front.
|
48
|
+
|
49
|
+
2. If followed by space, comma, end of line, or similar delimiter, it is
|
50
|
+
called with no parameter.
|
51
|
+
|
52
|
+
3. Note that a function name may contain periods, but may not end with
|
53
|
+
one. "$$func." is parsed as a function call (with no parameter) plus a
|
54
|
+
period.
|
55
|
+
|
56
|
+
4. Use a colon to pass a single parameter delimited by a space or end of line.
|
57
|
+
Colon at end of line is valid but probably pointless.
|
58
|
+
|
59
|
+
5. Use brackets to pass a single parameter that contains spaces. The bracketed
|
60
|
+
parameter may be terminated by end of line instead of right bracket.
|
61
|
+
|
62
|
+
6. Only one parameter (a string) may be passed, but the function may parse it
|
63
|
+
however it needs to.
|
64
|
+
|
65
|
+
7. There is no enforcement of a parameter being "present or absent" except what
|
66
|
+
the function itself may enforce.
|
67
|
+
|
68
|
+
8. An unknown function will not raise an error, but will be replaced with a warning
|
69
|
+
string.
|
70
|
+
|
71
|
+
|
72
|
+
Formatting:
|
73
|
+
-----------
|
74
|
+
|
75
|
+
1. My formatting notation would be considered quirky by many people.
|
76
|
+
The sigils or markers are:
|
77
|
+
* bold
|
78
|
+
_ underscore
|
79
|
+
` code/teletype
|
80
|
+
~ strikethrough
|
81
|
+
|
82
|
+
1. A single sigil is recognized basically at beginning of line or after a space.
|
83
|
+
my_func_name No italics here
|
84
|
+
M*A*S*H No boldface here
|
85
|
+
|
86
|
+
2. A single sigil is terminated by a space or end of line.
|
87
|
+
|
88
|
+
3. A single sigil "by itself" is rendered as-is (asterisk, underscore, whatever).
|
89
|
+
|
90
|
+
4. An escaped single sigil is rendered as-is. (This is problematic.)
|
91
|
+
|
92
|
+
5. A double sigil is recognized at start of line or after a space
|
93
|
+
|
94
|
+
6. A double sigil is terminated by a space OR a comma OR a period. (The comma
|
95
|
+
and period cases seem very common to me; they are the whole justification
|
96
|
+
for the double sigil.) End of line also terminates it.
|
97
|
+
|
98
|
+
7. A double sigil by itself is rendered as-is.
|
99
|
+
|
100
|
+
8. A bracketed sigil is in general a sigil followed by: [ data ]
|
101
|
+
|
102
|
+
9. An empty bracketed sigil simply "goes away"
|
103
|
+
" *[] " => " "
|
104
|
+
|
105
|
+
10. End of line can terminate instead of right bracket -- but it may still be empty
|
106
|
+
and therefore go away.
|
107
|
+
|
108
|
+
11. NOTE: These tests use only asterisks (bold), but the logic "should" be the same
|
109
|
+
for all sigils.
|
110
|
+
|
111
|
+
|
112
|
+
|
113
|
+
Order of evaluation, etc.:
|
114
|
+
--------------------------
|
115
|
+
|
116
|
+
1. This logic is always a compromise between syntax and the code that parses it.
|
117
|
+
I prefer simplicity whenever possible, though it may introduce complexity in
|
118
|
+
other situations. I believe that the acceptable complexity of a workaround
|
119
|
+
depends on how commonplace the situation is and how onerous the workaround is.
|
120
|
+
These are both highly subjective.
|
121
|
+
|
122
|
+
2. For example: Note that the simple formatting sigils may not be nested. However,
|
123
|
+
there are functions like $$bits provided (with the silly mnemonic "bold, italic,
|
124
|
+
teletype, strikthrough"). Every combination is provided (e.g., $$bi, $$bt).
|
125
|
+
|
126
|
+
3. Note also: Formatting is only intra-line; it doesn't span lines. If you need to
|
127
|
+
work around this, use a heredoc or make your own .def for this.
|
128
|
+
|
129
|
+
4. Finally: HTML or CSS may be inserted at will (possibly with some escaping). This
|
130
|
+
can be inline or "a file at a time" via such commands as .copy and .include
|
131
|
+
|
132
|
+
5. For these reasons: Parsing is naive and simple. Variables are parsed first.
|
133
|
+
|
134
|
+
6. Next, function calls are parsed. I said variables are parsed first; this implies
|
135
|
+
that a variable can be embedded in a function parameter. But be aware these are
|
136
|
+
"naive" substitutions (like C macros).
|
137
|
+
.set alpha = "some value"
|
138
|
+
Calling $$myfunc:$alpha (means: Calling $$myfunc:some value)
|
139
|
+
Better to say $$myfunc[$alpha] (means: Better to say $$myfunc[some value]
|
140
|
+
|
141
|
+
7. Formatting is handled last. The four sigils (* _ ` ~) and their three modes
|
142
|
+
(single, double, bracketed) make 12 passes necessary for formatting. As this is
|
143
|
+
always single-line, it has not been observed to cause a delay so far.
|
144
|
+
|
145
|
+
8. The call api.format(line) essentially expands variables, calls functions, and
|
146
|
+
finally does simple formatting. See classes Expansion and Formatter.
|
147
|
+
|
148
|
+
9. User code (e.g. inside a .def) may also call expand_variables, expand_functions,
|
149
|
+
and Formatter.format separately.
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require 'minitest/autorun'
|
2
|
+
|
3
|
+
require 'livetext'
|
4
|
+
|
5
|
+
# Just another testing class. Chill.
|
6
|
+
|
7
|
+
class TestingLivetextBracketed < MiniTest::Test
|
8
|
+
|
9
|
+
def setup
|
10
|
+
@live = Livetext.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def check_match(exp, actual)
|
14
|
+
if exp.is_a? Regexp
|
15
|
+
assert_match(exp, actual, "actual does not match expected")
|
16
|
+
else
|
17
|
+
assert_equal(exp, actual, "actual != expected")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_bracketed_001_single_bracketed_item
|
22
|
+
# Single bracketed item
|
23
|
+
# No special initialization
|
24
|
+
src = "*[abc]"
|
25
|
+
exp = "<b>abc</b>"
|
26
|
+
actual = @live.api.format(src)
|
27
|
+
check_match(exp, actual)
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_bracketed_002_end_of_line_can_replace_bracket
|
31
|
+
# End of line can replace bracket
|
32
|
+
# No special initialization
|
33
|
+
src = "*[abc"
|
34
|
+
exp = "<b>abc</b>"
|
35
|
+
actual = @live.api.format(src)
|
36
|
+
check_match(exp, actual)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_bracketed_003_end_of_line_can_replace_bracket_again
|
40
|
+
# End of line can replace bracket again
|
41
|
+
# No special initialization
|
42
|
+
src = "abc *[d"
|
43
|
+
exp = "abc <b>d</b>"
|
44
|
+
actual = @live.api.format(src)
|
45
|
+
check_match(exp, actual)
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_bracketed_004_missing_right_bracket_ignored_at_eol_if_empty
|
49
|
+
# Missing right bracket ignored at eol if empty
|
50
|
+
# No special initialization
|
51
|
+
src = "abc*["
|
52
|
+
exp = "abc*["
|
53
|
+
actual = @live.api.format(src)
|
54
|
+
check_match(exp, actual)
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_bracketed_005_two_simple_bracketed_items
|
58
|
+
# Two simple bracketed items
|
59
|
+
# No special initialization
|
60
|
+
src = "*[A], *[B], C"
|
61
|
+
exp = "<b>A</b>, <b>B</b>, C"
|
62
|
+
actual = @live.api.format(src)
|
63
|
+
check_match(exp, actual)
|
64
|
+
end
|
65
|
+
|
66
|
+
def test_bracketed_006_simple_bracketed_item
|
67
|
+
# Simple bracketed item
|
68
|
+
# No special initialization
|
69
|
+
src = "Just a *[test]..."
|
70
|
+
exp = "Just a <b>test</b>..."
|
71
|
+
actual = @live.api.format(src)
|
72
|
+
check_match(exp, actual)
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_bracketed_007_bracketed_item_with_space
|
76
|
+
# Bracketed item with space
|
77
|
+
# No special initialization
|
78
|
+
src = "A *[simple test]"
|
79
|
+
exp = "A <b>simple test</b>"
|
80
|
+
actual = @live.api.format(src)
|
81
|
+
check_match(exp, actual)
|
82
|
+
end
|
83
|
+
|
84
|
+
def test_bracketed_008_empty_bracketed_item_results_in_null
|
85
|
+
# Empty bracketed item results in null
|
86
|
+
# No special initialization
|
87
|
+
src = " *[] "
|
88
|
+
exp = " "
|
89
|
+
actual = @live.api.format(src)
|
90
|
+
check_match(exp, actual)
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_bracketed_009_bracketed_item_with_space_again
|
94
|
+
# Bracketed item with space again
|
95
|
+
# No special initialization
|
96
|
+
src = "*[ab c] d"
|
97
|
+
exp = "<b>ab c</b> d"
|
98
|
+
actual = @live.api.format(src)
|
99
|
+
check_match(exp, actual)
|
100
|
+
end
|
101
|
+
|
102
|
+
def test_bracketed_010_two_bracketed_items_with_spaces
|
103
|
+
# Two bracketed items with spaces
|
104
|
+
# No special initialization
|
105
|
+
src = "*[a b] *[c d]"
|
106
|
+
exp = "<b>a b</b> <b>c d</b>"
|
107
|
+
actual = @live.api.format(src)
|
108
|
+
check_match(exp, actual)
|
109
|
+
end
|
110
|
+
|
111
|
+
def test_bracketed_011_solitary_item_missing_right_bracket_ignored_at_eol_if_empty
|
112
|
+
# Solitary item, missing right bracket ignored at eol if empty
|
113
|
+
# No special initialization
|
114
|
+
src = "*["
|
115
|
+
exp = ""
|
116
|
+
actual = @live.api.format(src)
|
117
|
+
check_match(exp, actual)
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
Single bracketed item
|
2
|
+
"*[abc]"
|
3
|
+
"<b>abc</b>"
|
4
|
+
--------------------------------
|
5
|
+
End of line can replace bracket
|
6
|
+
"*[abc"
|
7
|
+
"<b>abc</b>"
|
8
|
+
--------------------------------
|
9
|
+
End of line can replace bracket again
|
10
|
+
"abc *[d"
|
11
|
+
"abc <b>d</b>"
|
12
|
+
--------------------------------
|
13
|
+
Missing right bracket ignored at eol if empty
|
14
|
+
"abc*["
|
15
|
+
"abc*["
|
16
|
+
--------------------------------
|
17
|
+
Two simple bracketed items
|
18
|
+
"*[A], *[B], C"
|
19
|
+
"<b>A</b>, <b>B</b>, C"
|
20
|
+
--------------------------------
|
21
|
+
Simple bracketed item
|
22
|
+
"Just a *[test]..."
|
23
|
+
"Just a <b>test</b>..."
|
24
|
+
--------------------------------
|
25
|
+
Bracketed item with space
|
26
|
+
"A *[simple test]"
|
27
|
+
"A <b>simple test</b>"
|
28
|
+
--------------------------------
|
29
|
+
Empty bracketed item results in null
|
30
|
+
" *[] "
|
31
|
+
" "
|
32
|
+
--------------------------------
|
33
|
+
Bracketed item with space again
|
34
|
+
"*[ab c] d"
|
35
|
+
"<b>ab c</b> d"
|
36
|
+
--------------------------------
|
37
|
+
Two bracketed items with spaces
|
38
|
+
"*[a b] *[c d]"
|
39
|
+
"<b>a b</b> <b>c d</b>"
|
40
|
+
--------------------------------
|
41
|
+
Solitary item, missing right bracket ignored at eol if empty
|
42
|
+
"*["
|
43
|
+
""
|
44
|
+
--------------------------------
|