lmt 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +8 -2
- data/Rakefile +4 -3
- data/doc/lmt/lmt.rb.md +439 -19
- data/doc/lmt/lmt_expressions.md +41 -3
- data/doc/lmt/lmw.rb.md +21 -14
- data/lib/lmt/lmt.rb +151 -14
- data/lib/lmt/lmw.rb +22 -15
- data/lib/lmt/version.rb +1 -1
- data/src/lmt/lmt.rb.lmd +377 -19
- data/src/lmt/lmt_expressions.lmd +31 -3
- data/src/lmt/lmw.rb.lmd +21 -14
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 20c0cdd948065ea59b76df0f83bd7abd0d7df99a55a8532a1a7368d3e6e46924
|
4
|
+
data.tar.gz: baff975ca361ba930e73a815283f824fb4f5d913a9f54b303a64ee0622be6b3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c415acb8d48be392624f7b64648b9873490cb6282e4f90969a63290e8aa13a4ee6c7cb6a03a05cc177339a3b917a66be894724621d65d1bd80018ddc2e49f6c
|
7
|
+
data.tar.gz: dec0d39c012cfa8c3f57955744a0cf9909c2f755c36ed0e893abdf684b20ebdd224bdbbd6c988129b9727c31947cabf5cf4fe468b64a7683efe1539e6349eb1e
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -29,13 +29,13 @@ Or install it yourself as:
|
|
29
29
|
The tangle program takes input files and produces tangled output files. It is used as follows:
|
30
30
|
|
31
31
|
``` bash
|
32
|
-
|
32
|
+
lmt --file {input file} --output {tangled destination}
|
33
33
|
```
|
34
34
|
|
35
35
|
The weave program is similar but produces weaved output files. It does not recurse down include statements, and so will need to be run independently for each included file. An example usage:
|
36
36
|
|
37
37
|
``` bash
|
38
|
-
|
38
|
+
lmw --file {input file} --output [weaved destination]
|
39
39
|
```
|
40
40
|
|
41
41
|
## Development
|
@@ -56,6 +56,12 @@ To test the weave you can use the following command which will weave the weaver
|
|
56
56
|
bundle exec ruby bin/lmt --file src/lmt/lmt.rb.lmd --output lib/lmt/lmt.rb; bundle exec ruby bin/lmw --file src/lmt/lmw.rb.lmd --output doc/lmt/lmw.rb.md
|
57
57
|
```
|
58
58
|
|
59
|
+
Since this is a self-bootstraping program, it is both tested and built by running itself on itself. This means that if you add a bug, it won't run. To fix this, check out the most recent version of the output file out of git.
|
60
|
+
|
61
|
+
``` bash
|
62
|
+
git co -- src/lmt/lmt.rb
|
63
|
+
```
|
64
|
+
|
59
65
|
## Prior Art
|
60
66
|
|
61
67
|
Some related and similar tools that the reader might find interesting:
|
data/Rakefile
CHANGED
@@ -42,6 +42,7 @@ task :install => :build
|
|
42
42
|
|
43
43
|
task :build => :tangle
|
44
44
|
task :build => :weave
|
45
|
+
task :weave => :tangle
|
45
46
|
|
46
47
|
lmd_files = Rake::FileList['src/**/*.lmd']
|
47
48
|
outputs = lmd_files.pathmap('%{^src,lib}X')
|
@@ -53,10 +54,10 @@ task :weave => docs
|
|
53
54
|
lmd_files.zip(outputs, docs).each do |lmd_file, output, doc|
|
54
55
|
directory output_dir = output.pathmap('%d')
|
55
56
|
directory doc_dir = doc.pathmap('%d')
|
56
|
-
file output => [output_dir, lmd_file] do
|
57
|
-
sh "ruby bin/lmt --file #{lmd_file} --output #{output}"
|
57
|
+
file output => [output_dir, lmd_file] do #FIXME LMT steps need to be aware of includes
|
58
|
+
sh "ruby bin/lmt --file #{lmd_file} --output #{output} --dev"
|
58
59
|
end
|
59
|
-
file doc => [doc_dir, lmd_file] do
|
60
|
+
file doc => ["lib/lmt/lmw.rb", doc_dir, lmd_file] do
|
60
61
|
sh "ruby bin/lmw --file #{lmd_file} --output #{doc}"
|
61
62
|
end
|
62
63
|
end
|
data/doc/lmt/lmt.rb.md
CHANGED
@@ -24,15 +24,15 @@ In order to be useful for literate programming we need a few features:
|
|
24
24
|
4. The ability to to identify code blocks which will be expanded when referenced
|
25
25
|
5. The ability to append to or replace code blocks
|
26
26
|
6. The ability to include another file.
|
27
|
+
7. The ability to extend the tangler with Ruby code from a block.
|
28
|
+
8. Simple conditional logic to enable output only under certain circumstances
|
27
29
|
|
28
30
|
There are also a few potentially useful features that are not implemented but might be in the future:
|
29
31
|
|
30
|
-
1. The ability to
|
31
|
-
2.
|
32
|
-
3.
|
33
|
-
4.
|
34
|
-
|
35
|
-
Also, the only filter currently existing just escapes strings for ruby code. There are many more that could be useful.
|
32
|
+
1. The ability to write out other files.
|
33
|
+
2. Source mapping
|
34
|
+
3. Further source verification. For instance, all instances of the same block should be in the same language. Also, detect and prevent double inclusion.
|
35
|
+
4. include path semantics.
|
36
36
|
|
37
37
|
### Blocks
|
38
38
|
|
@@ -40,7 +40,7 @@ Markdown already supports code blocks expressed with code fences starting with t
|
|
40
40
|
|
41
41
|
There are two types of blocks: the default block and macro blocks.
|
42
42
|
|
43
|
-
|
43
|
+
Output begins with the default block. It is simply a markdown code block which has no macro name. with no further information. It looks like this.
|
44
44
|
|
45
45
|
###### Output Block
|
46
46
|
|
@@ -136,7 +136,11 @@ There are a few built in filters:
|
|
136
136
|
|
137
137
|
``` ruby
|
138
138
|
{
|
139
|
-
'ruby_escape' => ⦅ruby_escape
|
139
|
+
'ruby_escape' => ⦅ruby_escape⦆,
|
140
|
+
'double_quote' => ⦅double_quote⦆,
|
141
|
+
'add_comma' => ⦅add_comma⦆,
|
142
|
+
'indent_continuation' => ⦅indent_continuation⦆,
|
143
|
+
'indent_lines' => ⦅indent_lines⦆
|
140
144
|
}
|
141
145
|
```
|
142
146
|
|
@@ -154,6 +158,65 @@ included_string = "I am in lmt.lmd"
|
|
154
158
|
|
155
159
|
**See include:** [lmt_include.lmd](include_file)
|
156
160
|
|
161
|
+
### Extension
|
162
|
+
|
163
|
+
In order to extend the tangler, it must be possible to mark a block for evaluation. To do so we will extend the mechanism to indicate replacement. Let's use `!`. A block simply named `!` will just be executed within a contained scope. Within this scope, it will be possible to access the map of filters through the `@filters` variable. All of these blocks are executed and removed from the stream before any further processing is done.
|
164
|
+
|
165
|
+
However, after blocks have been parsed, the map of blocks will be passed to the `parse_hook` method which an extension may define. It will be passed a two arguments. The first is an array with the lines of the main block, the second is a map of block name to line arrays. It is expected to return the same data structure in a two value array. An example parse hook which adds a block to the list of know blocks follows:
|
166
|
+
|
167
|
+
###### Execute Extension Block
|
168
|
+
|
169
|
+
``` ruby
|
170
|
+
def parse_hook(main_block, blocks)
|
171
|
+
blocks["from_extension"] = ["from_extension = true\n"]
|
172
|
+
[main_block, blocks]
|
173
|
+
end
|
174
|
+
```
|
175
|
+
|
176
|
+
### Conditional Output
|
177
|
+
|
178
|
+
Under certain circumstances it is useful to have certain output only happen under certain circumstances. For instance, a file prepared for Windows might have slightly different content than the same file prepared for Linux. In order to enable this, a variable may be set within an extension block and then output may be enabled / disabled using directives based on them.
|
179
|
+
|
180
|
+
###### Execute Extension Block
|
181
|
+
|
182
|
+
``` ruby
|
183
|
+
@a_variable = true
|
184
|
+
@another_variable = false
|
185
|
+
```
|
186
|
+
|
187
|
+
We can then disable and enable output using the if, else, elsif, and end directives. The if directive takes a line of ruby code, executes it.
|
188
|
+
|
189
|
+
! if @a_variable
|
190
|
+
|
191
|
+
Since a_variable is true, the next block will be processed.
|
192
|
+
|
193
|
+
###### Code Block: Conditional Output
|
194
|
+
|
195
|
+
``` ruby
|
196
|
+
conditional_output_else = true
|
197
|
+
conditional_output_elsif = true
|
198
|
+
```
|
199
|
+
|
200
|
+
! elsif @another_variable
|
201
|
+
|
202
|
+
###### Code Block: Conditional Output
|
203
|
+
|
204
|
+
``` ruby
|
205
|
+
conditional_output_elsif = false
|
206
|
+
```
|
207
|
+
|
208
|
+
! else
|
209
|
+
|
210
|
+
And the following block will have no effect.
|
211
|
+
|
212
|
+
###### Code Block: Conditional Output
|
213
|
+
|
214
|
+
``` ruby
|
215
|
+
conditional_output_else = false
|
216
|
+
```
|
217
|
+
|
218
|
+
! end
|
219
|
+
|
157
220
|
### Self Test
|
158
221
|
|
159
222
|
Of course, we will also need a testing procedure. Since this is written as a literate program, our test procedure is: can we tangle ourself. If the output of the tangler run on this file can tangle this file, then we know that the tangler works.
|
@@ -234,6 +297,8 @@ class Tangle
|
|
234
297
|
|
235
298
|
⦅tangle_class⦆
|
236
299
|
|
300
|
+
⦅context_class⦆
|
301
|
+
|
237
302
|
⦅option_verification⦆
|
238
303
|
|
239
304
|
description "⦅description⦆"
|
@@ -257,6 +322,7 @@ The main body will first test itself then, invoke the library component, which i
|
|
257
322
|
###### Code Block: Main Body
|
258
323
|
|
259
324
|
``` ruby
|
325
|
+
@dev = options[:dev]
|
260
326
|
self_test()
|
261
327
|
tangler = Tangle::Tangler.new(options[:file])
|
262
328
|
tangler.tangle()
|
@@ -283,16 +349,11 @@ The tangler is defined within a class that contains the tangling implementation.
|
|
283
349
|
|
284
350
|
``` ruby
|
285
351
|
class Tangler
|
286
|
-
class << self
|
287
|
-
attr_reader :filters
|
288
|
-
end
|
289
|
-
|
290
|
-
@filters = ⦅filter_list⦆
|
291
|
-
|
292
352
|
⦅initializer⦆
|
293
353
|
⦅tangle⦆
|
294
354
|
⦅read_file⦆
|
295
355
|
⦅include_includes⦆
|
356
|
+
⦅handle_extensions_and_conditionals⦆
|
296
357
|
⦅parse_blocks⦆
|
297
358
|
⦅expand_macros⦆
|
298
359
|
⦅apply_filters⦆
|
@@ -300,7 +361,12 @@ class Tangler
|
|
300
361
|
⦅write⦆
|
301
362
|
|
302
363
|
private
|
364
|
+
def filters_map
|
365
|
+
@extension_context.filters
|
366
|
+
end
|
303
367
|
⦅tangle_class_privates⦆
|
368
|
+
|
369
|
+
⦅conditional_processor⦆
|
304
370
|
end
|
305
371
|
```
|
306
372
|
|
@@ -312,6 +378,8 @@ The initializer takes in the input file and sets up our state. We are keeping t
|
|
312
378
|
|
313
379
|
``` ruby
|
314
380
|
def initialize(input)
|
381
|
+
@extension_context = Context.new()
|
382
|
+
@extension_context.filters = ⦅filter_list⦆
|
315
383
|
@input = input
|
316
384
|
@block = ""
|
317
385
|
@blocks = {}
|
@@ -322,14 +390,15 @@ end
|
|
322
390
|
|
323
391
|
### Tangle
|
324
392
|
|
325
|
-
Now we have the basic tangle process wherein a file is read, includes are substituted, the blocks extracted, macros expanded recursively, and escaped double parentheses unescaped. If there is no default block, then there is no further work to be done.
|
393
|
+
Now we have the basic tangle process wherein a file is read, includes are substituted, extensions and conditionals are processed, the blocks extracted, the extension hook called, macros expanded recursively, and escaped double parentheses unescaped. If there is no default block, then there is no further work to be done.
|
326
394
|
|
327
395
|
###### Code Block: Tangle
|
328
396
|
|
329
397
|
``` ruby
|
330
398
|
def tangle()
|
331
|
-
contents =
|
332
|
-
|
399
|
+
contents = handle_extensions_and_conditionals(
|
400
|
+
include_includes(read_file(@input)))
|
401
|
+
@block, @blocks = @extension_context.parse_hook(*parse_blocks(contents))
|
333
402
|
if @block
|
334
403
|
@block = expand_macros(@block)
|
335
404
|
@block = unescape_double_parens(@block)
|
@@ -381,6 +450,122 @@ end
|
|
381
450
|
|
382
451
|
```
|
383
452
|
|
453
|
+
### Evaling the Extensions and Processing the Conditionals
|
454
|
+
|
455
|
+
The extensions are executed within the following context. This context is also
|
456
|
+
used to evaluate conditionals.
|
457
|
+
|
458
|
+
###### Code Block: Context Class
|
459
|
+
|
460
|
+
``` ruby
|
461
|
+
class Context
|
462
|
+
attr_accessor :filters
|
463
|
+
|
464
|
+
def get_binding
|
465
|
+
binding
|
466
|
+
end
|
467
|
+
|
468
|
+
def parse_hook(main_block, blocks)
|
469
|
+
[main_block, blocks]
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
```
|
474
|
+
|
475
|
+
Because conditional processing must occur concurrently with bock evaling, we have to build up each block and eval it the moment it is complete. To do so, we find all the lines that are not in an extension block. When we enter a new extension block, we clear the current extension block, and when we leave an extension block, we eval it.
|
476
|
+
|
477
|
+
###### Code Block: Handle Extensions And Conditionals
|
478
|
+
|
479
|
+
``` ruby
|
480
|
+
def handle_extensions_and_conditionals(lines)
|
481
|
+
extension_expression = ⦅extension_expression⦆
|
482
|
+
condition_processor = ConditionalProcessor.new(@extension_context)
|
483
|
+
extension_exit_expression = /```/
|
484
|
+
in_extension_block = false
|
485
|
+
current_extension_block = []
|
486
|
+
|
487
|
+
other_lines = lines.lazy
|
488
|
+
.find_all do |line|
|
489
|
+
condition_processor.should_output(line)
|
490
|
+
end.find_all do |line|
|
491
|
+
unless in_extension_block
|
492
|
+
in_extension_block = line =~ extension_expression
|
493
|
+
if in_extension_block
|
494
|
+
current_extension_block = []
|
495
|
+
end
|
496
|
+
!in_extension_block
|
497
|
+
else
|
498
|
+
in_extension_block = !(line =~ extension_exit_expression)
|
499
|
+
if in_extension_block
|
500
|
+
current_extension_block << line
|
501
|
+
else
|
502
|
+
@extension_context.get_binding.eval(current_extension_block.join)
|
503
|
+
end
|
504
|
+
false
|
505
|
+
end
|
506
|
+
end.force
|
507
|
+
|
508
|
+
condition_processor.check_block_balance()
|
509
|
+
|
510
|
+
other_lines
|
511
|
+
end
|
512
|
+
|
513
|
+
```
|
514
|
+
|
515
|
+
### Processing The Conditionals
|
516
|
+
|
517
|
+
To process the conditionals, we need a stack. Given that we are handling if, elsif, and else, we will need to track: 1) the type of statement (in case we want to add loops later), 2) the state before we encounter the if and, 3) if the else should be executed. For if statements, we can store these in an array like `[type, prior_state, execute_else]`
|
518
|
+
|
519
|
+
Since this process happens concurrently with evaling the included blocks, it's process is represented by a class. Should_output is the inside of the filter statement which is used to filter the lines.
|
520
|
+
|
521
|
+
###### Code Block: Conditional Processor
|
522
|
+
|
523
|
+
``` ruby
|
524
|
+
class ConditionalProcessor
|
525
|
+
|
526
|
+
def initialize(extension_context)
|
527
|
+
@if_expression = ⦅if_expression⦆
|
528
|
+
@elsif_expression = ⦅elsif_expression⦆
|
529
|
+
@else_expression = ⦅else_expression⦆
|
530
|
+
@end_expression = ⦅end_expression⦆
|
531
|
+
@output_enabled = true
|
532
|
+
@stack = []
|
533
|
+
@extension_context = extension_context
|
534
|
+
end
|
535
|
+
|
536
|
+
def should_output(line)
|
537
|
+
case line
|
538
|
+
when @if_expression
|
539
|
+
condition = $1
|
540
|
+
prior_state = @output_enabled
|
541
|
+
@output_enabled = !!@extension_context.get_binding.eval(condition)
|
542
|
+
@stack.push([:if, prior_state, !@output_enabled])
|
543
|
+
when @elsif_expression
|
544
|
+
throw "elsif statement missing if" if @stack.empty?
|
545
|
+
condition = $1
|
546
|
+
type, prior_state, execute_else = @stack.pop()
|
547
|
+
@output_enabled = execute_else && !!@extension_context.get_binding.eval(condition)
|
548
|
+
@stack.push([type, prior_state, execute_else && !@output_enabled])
|
549
|
+
when @else_expression
|
550
|
+
throw "else statement missing if" if @stack.empty?
|
551
|
+
type, prior_state, execute_else = @stack.pop()
|
552
|
+
@output_enabled = execute_else
|
553
|
+
@stack.push([type, prior_state, execute_else])
|
554
|
+
when @end_expression
|
555
|
+
throw "end statement missing begin" if @stack.empty?
|
556
|
+
type, prior_state, execute_else = @stack.pop()
|
557
|
+
@output_enabled = prior_state
|
558
|
+
end
|
559
|
+
@output_enabled
|
560
|
+
end
|
561
|
+
|
562
|
+
def check_block_balance
|
563
|
+
throw "unbalanced blocks" unless @stack.empty?
|
564
|
+
end
|
565
|
+
end
|
566
|
+
|
567
|
+
```
|
568
|
+
|
384
569
|
### Parsing The Blocks
|
385
570
|
|
386
571
|
Now we get to the meat of the algorithm. This uses the regular expression in [lmt_expressions](lmt_expressions.lmd#The-Code-Block-Expression)
|
@@ -604,14 +789,13 @@ Filters are applied by the following method:
|
|
604
789
|
``` ruby
|
605
790
|
def apply_filters(strings, filters)
|
606
791
|
filters.map do |filter_name|
|
607
|
-
|
792
|
+
filters_map[filter_name]
|
608
793
|
end.inject(strings) do |strings, filter|
|
609
794
|
filter.filter(strings)
|
610
795
|
end
|
611
796
|
end
|
612
797
|
```
|
613
798
|
|
614
|
-
|
615
799
|
### Ruby Escape
|
616
800
|
|
617
801
|
Ruby escape escapes strings appropriately for Ruby.
|
@@ -624,6 +808,58 @@ LineFilter.new do |line|
|
|
624
808
|
end
|
625
809
|
```
|
626
810
|
|
811
|
+
### Double Quote
|
812
|
+
|
813
|
+
Double quote surrounds strings in double quotes
|
814
|
+
|
815
|
+
###### Code Block: Double Quote
|
816
|
+
|
817
|
+
``` ruby
|
818
|
+
LineFilter.new do |line|
|
819
|
+
before_white = /^\s*/.match(line)[0]
|
820
|
+
after_white = /\s*$/.match(line)[0]
|
821
|
+
"#{before_white}\"#{line.strip}\"#{after_white}"
|
822
|
+
end
|
823
|
+
```
|
824
|
+
|
825
|
+
### Add Commas
|
826
|
+
|
827
|
+
Add commas adds comma to the end of each line.
|
828
|
+
|
829
|
+
###### Code Block: Add Comma
|
830
|
+
|
831
|
+
``` ruby
|
832
|
+
LineFilter.new do |line|
|
833
|
+
before_white = /^\s*/.match(line)[0]
|
834
|
+
after_white = /\s*$/.match(line)[0]
|
835
|
+
"#{before_white}#{line.strip},#{after_white}"
|
836
|
+
end
|
837
|
+
```
|
838
|
+
|
839
|
+
### Indent continuation
|
840
|
+
|
841
|
+
Adds two spaces to the front of each line after the first
|
842
|
+
|
843
|
+
###### Code Block: Indent Continuation
|
844
|
+
|
845
|
+
``` ruby
|
846
|
+
Filter.new do |lines|
|
847
|
+
[lines[0], *lines[1..-1].map {|l| " #{l}"}]
|
848
|
+
end
|
849
|
+
```
|
850
|
+
|
851
|
+
### Indent Lines
|
852
|
+
|
853
|
+
Adds two spaces to the front of each line
|
854
|
+
|
855
|
+
###### Code Block: Indent Lines
|
856
|
+
|
857
|
+
``` ruby
|
858
|
+
LineFilter.new do |line|
|
859
|
+
" #{line}"
|
860
|
+
end
|
861
|
+
```
|
862
|
+
|
627
863
|
## Option Verification
|
628
864
|
|
629
865
|
Option verification is described here:
|
@@ -648,6 +884,8 @@ Then we need the tests we are doing. The intentionally empty block is included
|
|
648
884
|
⦅test_filters⦆
|
649
885
|
⦅test_inclusion⦆
|
650
886
|
⦅intentionally_empty_block⦆
|
887
|
+
⦅test_extensions⦆
|
888
|
+
⦅test_conditional_output⦆
|
651
889
|
```
|
652
890
|
|
653
891
|
### Testing: Macros
|
@@ -694,11 +932,30 @@ foo
|
|
694
932
|
|
695
933
|
At the [top of the file](Filters) we described the usage of filters. Let's make sure that works. The extra `.?` in the regular expression is a workaround for an editor bug in Visual Studio Code, where, apparently, `/\\/` escapes the `/` rather than the `\`.... annoying.
|
696
934
|
|
935
|
+
###### Code Block: Some Text
|
936
|
+
|
937
|
+
``` text
|
938
|
+
some text
|
939
|
+
```
|
940
|
+
|
941
|
+
###### Code Block: A List
|
942
|
+
|
943
|
+
``` text
|
944
|
+
item 1
|
945
|
+
item 2
|
946
|
+
```
|
947
|
+
|
697
948
|
###### Code Block: Test Filters
|
698
949
|
|
699
950
|
``` ruby
|
700
951
|
⦅filter_use_description⦆
|
701
952
|
report_self_test_failure("ruby escape doesn't escape backslash") unless string_with_backslash =~ /\\.?/
|
953
|
+
some_text = ⦅some_text | double_quote⦆
|
954
|
+
report_self_test_failure("Double quote doesn't double quote") unless some_text == "⦅some_text⦆"
|
955
|
+
some_indented_text = "⦅some_text | indent_lines⦆"
|
956
|
+
report_self_test_failure("Indent lines should add two spaces to lines") unless some_indented_text == " ⦅some_text⦆"
|
957
|
+
items = [⦅a_list | double_quote | add_comma | indent_continuation⦆]
|
958
|
+
report_self_test_failure("Add comma isn't adding commas") unless items == ["item 1", "item 2"]
|
702
959
|
```
|
703
960
|
|
704
961
|
### Testing: Inclusion
|
@@ -710,6 +967,169 @@ report_self_test_failure("ruby escape doesn't escape backslash") unless string_w
|
|
710
967
|
report_self_test_failure("included replacements should replace blocks") unless included_string == "I came from lmt_include.lmd"
|
711
968
|
```
|
712
969
|
|
970
|
+
### Testing: Extensions
|
971
|
+
|
972
|
+
###### Code Block: Test Extensions
|
973
|
+
|
974
|
+
``` ruby
|
975
|
+
⦅from_extension⦆
|
976
|
+
report_self_test_failure("extension hook should be able to add blocks") unless from_extension
|
977
|
+
```
|
978
|
+
|
979
|
+
### Testing: Conditional Output
|
980
|
+
|
981
|
+
In the description, the if statement was to be executed.
|
982
|
+
|
983
|
+
###### Code Block: Test Conditional Output
|
984
|
+
|
985
|
+
``` ruby
|
986
|
+
⦅conditional_output⦆
|
987
|
+
report_self_test_failure("conditional output elseif should not be output when elseif is false") unless conditional_output_elsif
|
988
|
+
report_self_test_failure("conditional output else should not be output when if true") unless conditional_output_else
|
989
|
+
```
|
990
|
+
|
991
|
+
#### If and elsif
|
992
|
+
|
993
|
+
Neither the elsif or elsif statement are output when if is true.
|
994
|
+
|
995
|
+
###### Code Block: Test Conditional Output
|
996
|
+
|
997
|
+
``` ruby
|
998
|
+
⦅conditional_output_if_and_elsif⦆
|
999
|
+
report_self_test_failure("conditional output elsif should not be output even if true when is also true") unless conditional_output_elsif
|
1000
|
+
report_self_test_failure("conditional output else should not be output when if is true (if and elseif)") unless conditional_output_else
|
1001
|
+
```
|
1002
|
+
|
1003
|
+
###### Execute Extension Block
|
1004
|
+
|
1005
|
+
``` ruby
|
1006
|
+
@a_variable = true
|
1007
|
+
@another_variable = true
|
1008
|
+
```
|
1009
|
+
|
1010
|
+
! if @a_variable
|
1011
|
+
|
1012
|
+
###### Code Block: Conditional Output If And Elsif
|
1013
|
+
|
1014
|
+
``` ruby
|
1015
|
+
conditional_output_else = true
|
1016
|
+
conditional_output_elsif = true
|
1017
|
+
```
|
1018
|
+
|
1019
|
+
! elsif @another_variable
|
1020
|
+
|
1021
|
+
###### Code Block: Conditional Output If And Elsif
|
1022
|
+
|
1023
|
+
``` ruby
|
1024
|
+
conditional_output_elsif = false
|
1025
|
+
```
|
1026
|
+
|
1027
|
+
! else
|
1028
|
+
|
1029
|
+
###### Code Block: Conditional Output If And Elsif
|
1030
|
+
|
1031
|
+
``` ruby
|
1032
|
+
conditional_output_else = false
|
1033
|
+
```
|
1034
|
+
|
1035
|
+
! end
|
1036
|
+
|
1037
|
+
#### Elsif
|
1038
|
+
|
1039
|
+
When the if is false but the elsif true, only the elsif is output.
|
1040
|
+
|
1041
|
+
###### Code Block: Test Conditional Output
|
1042
|
+
|
1043
|
+
``` ruby
|
1044
|
+
conditional_output_if = true
|
1045
|
+
⦅conditional_output_elsif⦆
|
1046
|
+
report_self_test_failure("conditional output if should not be output when false") unless conditional_output_if
|
1047
|
+
report_self_test_failure("conditional output elseif should be output when elseif is true") unless conditional_output_elsif
|
1048
|
+
report_self_test_failure("conditional output else should not be output when elseif is true") unless conditional_output_else
|
1049
|
+
```
|
1050
|
+
|
1051
|
+
###### Execute Extension Block
|
1052
|
+
|
1053
|
+
``` ruby
|
1054
|
+
@a_variable = false
|
1055
|
+
@another_variable = true
|
1056
|
+
```
|
1057
|
+
|
1058
|
+
! if @a_variable
|
1059
|
+
|
1060
|
+
###### Code Block: Conditional Output Elsif
|
1061
|
+
|
1062
|
+
``` ruby
|
1063
|
+
conditional_output_if = false
|
1064
|
+
```
|
1065
|
+
|
1066
|
+
! elsif @another_variable
|
1067
|
+
|
1068
|
+
###### Code Block: Conditional Output Elsif
|
1069
|
+
|
1070
|
+
``` ruby
|
1071
|
+
conditional_output_else = true
|
1072
|
+
conditional_output_elsif = true
|
1073
|
+
```
|
1074
|
+
|
1075
|
+
! else
|
1076
|
+
|
1077
|
+
###### Code Block: Conditional Output Elsif
|
1078
|
+
|
1079
|
+
``` ruby
|
1080
|
+
conditional_output_else = false
|
1081
|
+
```
|
1082
|
+
|
1083
|
+
! end
|
1084
|
+
|
1085
|
+
#### Else
|
1086
|
+
|
1087
|
+
The else is output when none of the if or elsif statements are true.
|
1088
|
+
|
1089
|
+
###### Code Block: Test Conditional Output
|
1090
|
+
|
1091
|
+
``` ruby
|
1092
|
+
conditional_output_if = true
|
1093
|
+
conditional_output_elsif = true
|
1094
|
+
⦅conditional_output_else⦆
|
1095
|
+
report_self_test_failure("conditional output if should not be output when false") unless conditional_output_if
|
1096
|
+
report_self_test_failure("conditional output elseif should not be output when elseif is false") unless conditional_output_elsif
|
1097
|
+
report_self_test_failure("conditional output else should be output when neither if nor elseif is true") unless conditional_output_else
|
1098
|
+
```
|
1099
|
+
|
1100
|
+
###### Execute Extension Block
|
1101
|
+
|
1102
|
+
``` ruby
|
1103
|
+
@a_variable = false
|
1104
|
+
@another_variable = false
|
1105
|
+
```
|
1106
|
+
|
1107
|
+
! if @a_variable
|
1108
|
+
|
1109
|
+
###### Code Block: Conditional Output Else
|
1110
|
+
|
1111
|
+
``` ruby
|
1112
|
+
conditional_output_if = false
|
1113
|
+
```
|
1114
|
+
|
1115
|
+
! elsif @another_variable
|
1116
|
+
|
1117
|
+
###### Code Block: Conditional Output Else
|
1118
|
+
|
1119
|
+
``` ruby
|
1120
|
+
conditional_output_elsif = false
|
1121
|
+
```
|
1122
|
+
|
1123
|
+
! else
|
1124
|
+
|
1125
|
+
###### Code Block: Conditional Output Else
|
1126
|
+
|
1127
|
+
``` ruby
|
1128
|
+
conditional_output_else = true
|
1129
|
+
```
|
1130
|
+
|
1131
|
+
! end
|
1132
|
+
|
713
1133
|
### Regressions
|
714
1134
|
|
715
1135
|
Some regressions / edge cases that we need to watch for. These should not break our tangle operation.
|