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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 92a8904e1d7de30d5a09b28df29ffa7b164f4afe7e8bf78806c7c94a8381804a
4
- data.tar.gz: 9cfeaf0336b6e7b4162162aa258f7082a95078ac3299153d217fccb7b110bfcb
3
+ metadata.gz: 20c0cdd948065ea59b76df0f83bd7abd0d7df99a55a8532a1a7368d3e6e46924
4
+ data.tar.gz: baff975ca361ba930e73a815283f824fb4f5d913a9f54b303a64ee0622be6b3a
5
5
  SHA512:
6
- metadata.gz: db3d8d46c342cbde06a564b7db00fe674d0eda158edc1f95bf8263e7b641739fe72ff3f1568dab3d7b79e12cc0b6a3793f9aa58c5b2c2844d1b188772c27d26e
7
- data.tar.gz: a3d8b02b2d7cbdd2aadb112149f65b1b7cb31d91649a16a176166014888e761c67924eb53e2ac4e4455773d29b70c562d0f3c018c72f0777118d6d7dd1f04076
6
+ metadata.gz: 2c415acb8d48be392624f7b64648b9873490cb6282e4f90969a63290e8aa13a4ee6c7cb6a03a05cc177339a3b917a66be894724621d65d1bd80018ddc2e49f6c
7
+ data.tar.gz: dec0d39c012cfa8c3f57955744a0cf9909c2f755c36ed0e893abdf684b20ebdd224bdbbd6c988129b9727c31947cabf5cf4fe468b64a7683efe1539e6349eb1e
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lmt (0.1.2)
4
+ lmt (0.1.3)
5
5
  methadone (~> 1.9.5)
6
6
 
7
7
  GEM
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
- bin/lmt --file {input file} --output {tangled destination}
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
- bin/lmw --file {input file} --output [weaved destination]
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 extend the tangler with Ruby code from a block.
31
- 2. The ability to write out other files.
32
- 3. Source mapping
33
- 4. Further source verification. For instance, all instances of the same block should be in the same language. Also, detect and prevent double inclusion.
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
- Ouput 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.
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 = include_includes(read_file(@input))
332
- @block, @blocks = parse_blocks(contents)
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
- Tangler.filters[filter_name]
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.