mysh 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +162 -51
- data/lib/mysh.rb +4 -2
- data/lib/mysh/{external_ruby.rb → external.rb} +6 -7
- data/lib/mysh/handlebars.rb +1 -1
- data/lib/mysh/init.rb +14 -9
- data/lib/mysh/input_wrapper.rb +67 -0
- data/lib/mysh/internal/action.rb +5 -16
- data/lib/mysh/internal/action_pool.rb +1 -1
- data/lib/mysh/internal/actions/cd.rb +8 -15
- data/lib/mysh/internal/actions/command_line.rb +3 -3
- data/lib/mysh/internal/actions/comment.rb +3 -11
- data/lib/mysh/internal/actions/elapsed.rb +9 -16
- data/lib/mysh/internal/actions/exit.rb +4 -11
- data/lib/mysh/internal/actions/gls.rb +2 -2
- data/lib/mysh/internal/actions/help.rb +6 -3
- data/lib/mysh/internal/actions/help/sub_help.rb +2 -2
- data/lib/mysh/internal/actions/history.rb +2 -2
- data/lib/mysh/internal/actions/load.rb +24 -30
- data/lib/mysh/internal/actions/pwd.rb +3 -11
- data/lib/mysh/internal/actions/quit.rb +3 -11
- data/lib/mysh/internal/actions/say.rb +3 -12
- data/lib/mysh/internal/actions/show.rb +16 -19
- data/lib/mysh/internal/actions/show/env.rb +5 -5
- data/lib/mysh/internal/actions/show/ruby.rb +1 -1
- data/lib/mysh/internal/actions/type.rb +11 -17
- data/lib/mysh/internal/actions/vars.rb +2 -2
- data/lib/mysh/internal/actions/vls.rb +11 -16
- data/lib/mysh/internal/manage.rb +5 -19
- data/lib/mysh/process.rb +8 -12
- data/lib/mysh/quick.rb +11 -11
- data/lib/mysh/system.rb +11 -0
- data/lib/mysh/version.rb +1 -1
- data/tests/my_shell_tests.rb +25 -7
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d3adff2eeba99442ae70d9eea972c624906238a1
|
4
|
+
data.tar.gz: 828447d5721f3a973e20ef96d6ad7aa6a82dcbc3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea9d214229c39f0185346032bdcd06b27df9cd241df796b8b427543f593995c4e76706af3f3012625ce335cbf6bb794e9b142aed11f353558505fe19f2822d2c
|
7
|
+
data.tar.gz: 5c30d99b23cb66088fa77a2e71ddf24304d03f118a67f7f1e58a2ec33a0e8b36cd9e2bdb1aeb840d5237a2e7e1a7c05d9f8179bb4c8ceb58ed13fea349a433ba
|
data/README.md
CHANGED
@@ -25,6 +25,10 @@ web sites.
|
|
25
25
|
See the original article at:
|
26
26
|
(http://www.blackbytes.info/2016/07/writing-a-shell-in-ruby/)
|
27
27
|
|
28
|
+
Oh, and one other little thing. A survey of the mysh reveals that it currently
|
29
|
+
contains 2291 lines of code. It seems that there has been some growth beyond
|
30
|
+
the 25 lines in the original article.
|
31
|
+
|
28
32
|
## Installation
|
29
33
|
|
30
34
|
Add this line to your application's Gemfile:
|
@@ -52,21 +56,21 @@ mysh <options>
|
|
52
56
|
|
53
57
|
Where the available options are:
|
54
58
|
|
55
|
-
Option | Short Form
|
56
|
-
|
57
|
-
--debug | -d
|
58
|
-
--no-debug | -nd
|
59
|
-
--help | -? -h
|
60
|
-
--init filename | -i filename
|
61
|
-
--no-init | -ni
|
62
|
-
--load filename | -l filename
|
63
|
-
--post-prompt "str" | -pp "str"
|
64
|
-
--no-post-prompt | -npp
|
65
|
-
--pre-prompt "str" | -pr "str"
|
66
|
-
--no-pre-prompt | -npr
|
67
|
-
--prompt "str" | -p "str"
|
68
|
-
--no-prompt | -np
|
69
|
-
--quit |
|
59
|
+
Option | Short Form(s)| Description | Default
|
60
|
+
---------------------|--------------|-------------------------|-----------
|
61
|
+
--debug | -d | Turn on mysh debugging. | false
|
62
|
+
--no-debug | -nd | Turn off mysh debugging.
|
63
|
+
--help | -? -h | Display mysh usage info and exit.
|
64
|
+
--init filename | -i filename | Initialize mysh by loading the specified file. | ~/mysh_init.mysh
|
65
|
+
--no-init | -ni | Do not load a file to initialize mysh.
|
66
|
+
--load filename | -l filename | Load the specified file into the mysh.
|
67
|
+
--post-prompt "str" | -pp "str" | Set the mysh line continuation prompt to "str". | $prompt
|
68
|
+
--no-post-prompt | -npp | Turn off mysh line continuation prompting.
|
69
|
+
--pre-prompt "str" | -pr "str" | Set the mysh pre prompt to "str". | $w
|
70
|
+
--no-pre-prompt | -npr | Turn off mysh pre prompting.
|
71
|
+
--prompt "str" | -p "str" | Set the mysh prompt to "str". | mysh
|
72
|
+
--no-prompt | -np | Turn off mysh prompting.
|
73
|
+
--quit | | Quit out of the mysh program.
|
70
74
|
|
71
75
|
<br>When mysh is run, the user is presented with a command prompt:
|
72
76
|
|
@@ -90,17 +94,26 @@ and acclimated to its environment. The boot/initialization process of mysh is
|
|
90
94
|
somewhat modeled after (well if I'm honest, more like inspired by) that of the
|
91
95
|
famous bash shell. On startup:
|
92
96
|
|
93
|
-
1.
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
option, or disabled with the --no-init
|
101
|
-
|
102
|
-
|
103
|
-
|
97
|
+
1. Option values are initialized to their initial, default values.
|
98
|
+
2. Process pre-boot command line options: Some command line options are
|
99
|
+
processed early. These are --help, -h, -?, --init, -i, --no-init, and -ni.
|
100
|
+
See Usage above for details on these.
|
101
|
+
3. Try to load and execute the mysh_init file. There are three possible files
|
102
|
+
for this role. They are ~/mysh_init.mysh, ~/mysh_init.rb, and ~/mysh_init.txt.
|
103
|
+
In priority, ".mysh" > ".rb" > ".txt". <br>NOTE: If an init file should be
|
104
|
+
specified with the --init option, or disabled with the --no-init option, this
|
105
|
+
step is skipped.
|
106
|
+
4. The rest of the command line options are processed at this time. Again,
|
107
|
+
see Usage above for details.
|
108
|
+
|
109
|
+
It should be noted that in the event of a conflict in settings during the boot
|
110
|
+
process, the last command/option encountered shall prevail. For example if the
|
111
|
+
~/mysh_init.mysh contains the line:
|
112
|
+
```
|
113
|
+
$debug = on
|
114
|
+
```
|
115
|
+
and the command line has the -nd option, then debug mode will be disabled
|
116
|
+
because the -nd command line option is processed after the mysh_init file.
|
104
117
|
|
105
118
|
###REPL
|
106
119
|
|
@@ -110,6 +123,10 @@ Print and Loop and is used in may utilities like irb, pry and the rails
|
|
110
123
|
console. To better use mysh, it is good to understand each of these four
|
111
124
|
operating steps.
|
112
125
|
|
126
|
+
For more information on REPLs please see:
|
127
|
+
(https://en.wikipedia.org/wiki/Read-eval-print_loop) and
|
128
|
+
(https://repl.it/languages/ruby)
|
129
|
+
|
113
130
|
####READ
|
114
131
|
|
115
132
|
The first step is just to get input from the user. For interactive sessions
|
@@ -175,7 +192,6 @@ character in the input. These signature characters are:
|
|
175
192
|
below.
|
176
193
|
* @ to get information about the system, its environment, and the ruby
|
177
194
|
installation. For more information see Shell Info below.
|
178
|
-
|
179
195
|
2. Internal Commands - These commands are recognized by having the first word
|
180
196
|
in the input match a word stored in an internal hash of command actions. For
|
181
197
|
more information see Internal Commands below.
|
@@ -333,8 +349,8 @@ for setting a variable is:
|
|
333
349
|
$name=value
|
334
350
|
|
335
351
|
Where:
|
336
|
-
* name is a word matching the regex: /[a-z][a-z0-9_]*/.
|
337
|
-
* value is
|
352
|
+
* name is a word matching the regex: /[a-z][a-z0-9_]*/. Note: lower case only.
|
353
|
+
* value is some text, with optional embedded variables and handlebars.
|
338
354
|
|
339
355
|
To erase the value of a variable, use:
|
340
356
|
|
@@ -490,6 +506,8 @@ environment.
|
|
490
506
|
|
491
507
|
Topic | Description
|
492
508
|
---------|----------------------------------------------------
|
509
|
+
version | The version of mysh in use.
|
510
|
+
init file| The init file in use or <none found> or <none>.
|
493
511
|
user | The current user name.
|
494
512
|
home | The current home directory.
|
495
513
|
name | The path/name of the mysh program currently executing.
|
@@ -546,9 +564,11 @@ say <stuff> | Display the text in the command arguments.
|
|
546
564
|
type file | Display a text file with support for optional embedded handlebars and mysh variables.
|
547
565
|
vls {mask} | Display the loaded modules, matching the optional mask, that have version info.
|
548
566
|
|
549
|
-
|
550
|
-
|
551
|
-
|
567
|
+
Notes:
|
568
|
+
1. The notation {x} means that x is optional.
|
569
|
+
2. The load command applied to a mysh script file acts exactly the same
|
570
|
+
as if the script file were executed directly from the command line. As a
|
571
|
+
result of this:
|
552
572
|
|
553
573
|
```
|
554
574
|
myfile.mysh
|
@@ -622,28 +642,51 @@ located at:
|
|
622
642
|
A survey of the contents of that folder will reveal the nature of these files.
|
623
643
|
|
624
644
|
New internal commands may also be added in code via the the add_action method
|
625
|
-
of the
|
626
|
-
|
645
|
+
of the Action class of the Mysh module. There are two ways to do this:
|
646
|
+
|
647
|
+
* The command may be created as an instance of the Action class with a command
|
648
|
+
name, description and a block that contains the action to be performed by this
|
649
|
+
command. This block takes one parameter, an input wrapper (see About Command
|
650
|
+
Arguments below for details). This approach is best when the command is simple
|
651
|
+
enough to fit into a single lambda block of code. Like this template:
|
627
652
|
|
628
653
|
```ruby
|
629
654
|
module Mysh
|
630
|
-
|
655
|
+
command_name = 'new <item>'
|
656
|
+
desc = "A succinct description of what this command does."
|
657
|
+
action = lambda do |input|
|
658
|
+
#Action packed stuff goes here!
|
659
|
+
end
|
660
|
+
|
661
|
+
COMMANDS.add_action(Action.new(command_name, desc, &action))
|
662
|
+
end
|
663
|
+
```
|
631
664
|
|
665
|
+
|
666
|
+
* The command may be created as an instance of a sub-class of the Action class.
|
667
|
+
In this case, only a name and description are needed as the sub-class should
|
668
|
+
contain all the needed code. The action method is the process_command and this
|
669
|
+
takes one parameter, an input wrapper (see About Command Arguments below for
|
670
|
+
details). This approach is required when the command action needs to be spread
|
671
|
+
across multiple methods. Like this template:
|
672
|
+
|
673
|
+
```ruby
|
674
|
+
module Mysh
|
675
|
+
class NewCommand < Action
|
632
676
|
#This method is called when the command is executed.
|
633
|
-
def
|
677
|
+
def process_command(input)
|
678
|
+
#Even more action packed stuff goes here!
|
634
679
|
end
|
635
|
-
|
636
680
|
end
|
637
681
|
|
638
682
|
desc = "A succinct description of what this command does."
|
639
683
|
command_name = 'new <item>'
|
640
684
|
COMMANDS.add_action(NewCommand.new(command_name, desc))
|
641
|
-
|
642
685
|
end
|
643
686
|
```
|
644
687
|
|
645
|
-
|
646
|
-
|
688
|
+
##### Command names:
|
689
|
+
The name of the command is a string with optional argument descriptions
|
647
690
|
separated with spaces. The command is the first word of this string. For
|
648
691
|
example a command_name of:
|
649
692
|
|
@@ -653,22 +696,90 @@ example a command_name of:
|
|
653
696
|
|
654
697
|
will create a command called "new" with a title of "new <item>"
|
655
698
|
|
656
|
-
|
657
|
-
|
658
|
-
code handles matters like
|
699
|
+
##### Command descriptions:
|
700
|
+
A string or an array of strings that describe the command. This serves as the
|
701
|
+
descriptive help for the command. The help display code handles matters like
|
702
|
+
word wrap automatically.
|
703
|
+
|
704
|
+
##### About Command Arguments
|
705
|
+
|
706
|
+
The process_command method take one parameter that is an instance of the
|
707
|
+
InputWrapper class. This class provides several ways to access the parts of the
|
708
|
+
command line. These are:
|
709
|
+
|
710
|
+
Method | Description
|
711
|
+
--------------|----------------
|
712
|
+
raw | The raw, unprocessed command line text.
|
713
|
+
cooked | The command line with variables and handlebars expanded.
|
714
|
+
raw_command | The command portion of the raw text.
|
715
|
+
quick_command | The quick command of the raw text.
|
716
|
+
raw_body | The raw text after the command.
|
717
|
+
quick_body | The raw text after the quick command.
|
718
|
+
cooked_body | The cooked text after the command.
|
719
|
+
parsed | The command and parameters parsed into an array of strings.
|
720
|
+
args | The parameters parsed into an array of strings.
|
721
|
+
|
722
|
+
Note: commands are not normally "cooked". Should this be required
|
723
|
+
use the following code snippet:
|
724
|
+
|
725
|
+
```ruby
|
726
|
+
input.raw.preprocess
|
727
|
+
```
|
728
|
+
|
729
|
+
##### Some Useful Helper Methods
|
730
|
+
|
731
|
+
Within the mysh environment, there exists a number of methods designed to make
|
732
|
+
life easier in adding new commands or in load ruby files or embedded into
|
733
|
+
handlebars. Some of these more noteworthy methods are listed below:
|
734
|
+
|
735
|
+
###### MNV[:name]
|
736
|
+
Retrieve the mysh variable "$name"
|
659
737
|
|
660
|
-
|
738
|
+
###### MNV[:name]="value"
|
739
|
+
Set/Update the mysh variable "$name". Note that the value is always a string,
|
740
|
+
even for things like "true" and "false". If the value is an empty string, the
|
741
|
+
variable is deleted.
|
661
742
|
|
662
|
-
|
663
|
-
|
743
|
+
###### mysh "string"
|
744
|
+
Execute the string as a mysh command.
|
664
745
|
|
665
|
-
|
746
|
+
###### Mysh.parse_args("string")
|
747
|
+
Parse the string into an array of arguments.
|
666
748
|
|
667
|
-
|
749
|
+
###### Mysh.input.readline(parms)
|
750
|
+
Get a line of input from the console. See the mini_readline gem for info
|
751
|
+
on the optional parms.
|
668
752
|
|
669
|
-
|
753
|
+
###### "string".preprocess(context=mysh_default_context)
|
754
|
+
Process the string for embedded variables and handlebars. By default,
|
755
|
+
any embedded ruby is evaluated in the mysh global expression binding. However,
|
756
|
+
another BindingWrapper instance may be passed to access an alternative binding.
|
670
757
|
|
671
|
-
|
758
|
+
###### "string".decorate
|
759
|
+
Given a string with a file spec, decorate that string so that it is more
|
760
|
+
pleasing to the local environment. This is a great boon to writing effortless
|
761
|
+
portable code.
|
762
|
+
|
763
|
+
#### Adding Help Topics
|
764
|
+
|
765
|
+
In mysh, help topics are generally implemented as text files often augmented
|
766
|
+
with embedded mysh variables and ruby code. It it noteworthy however that they
|
767
|
+
can also be mysh script or ruby code files. The management of these help files
|
768
|
+
is located in the file:
|
769
|
+
```
|
770
|
+
mysh/lib/mysh/internal/actions/help/sub_help.rb
|
771
|
+
```
|
772
|
+
In this file, you can locate a variable called "help". This is an array of
|
773
|
+
arrays where each line describes a help topic. Within each line is a further
|
774
|
+
array of three strings. Respectively these are:
|
775
|
+
1. The name of the help item.
|
776
|
+
2. A brief description of the help topic. This line is used in the help on help
|
777
|
+
(??) topic.
|
778
|
+
3. The name of the file, **with its extension**, that contains the actual help
|
779
|
+
information.
|
780
|
+
|
781
|
+
To add a new help topic, simply add the new help file to the help folder and
|
782
|
+
and a corresponding line entry to to the help variable.
|
672
783
|
|
673
784
|
## Contributing
|
674
785
|
|
@@ -676,7 +787,7 @@ All participation is welcomed. There are two fabulous plans to choose from:
|
|
676
787
|
|
677
788
|
#### Plan A
|
678
789
|
|
679
|
-
1. Fork it (
|
790
|
+
1. Fork it (https://github.com/PeterCamilleri/mysh/fork)
|
680
791
|
2. Switch to the development branch ('git branch development')
|
681
792
|
3. Create your feature branch ('git checkout -b my-new-feature')
|
682
793
|
4. Commit your changes ('git commit -am "Add some feature"')
|
data/lib/mysh.rb
CHANGED
@@ -7,11 +7,13 @@ require 'in_array'
|
|
7
7
|
|
8
8
|
require_relative 'mysh/exceptions'
|
9
9
|
require_relative 'mysh/binding_wrapper'
|
10
|
+
require_relative 'mysh/input_wrapper'
|
10
11
|
require_relative 'mysh/user_input'
|
11
|
-
require_relative 'mysh/quick'
|
12
12
|
require_relative 'mysh/expression'
|
13
13
|
require_relative 'mysh/internal'
|
14
|
-
require_relative 'mysh/
|
14
|
+
require_relative 'mysh/quick'
|
15
|
+
require_relative 'mysh/external'
|
16
|
+
require_relative 'mysh/system'
|
15
17
|
require_relative 'mysh/handlebars'
|
16
18
|
require_relative 'mysh/shell_variables'
|
17
19
|
require_relative 'mysh/pre_processor'
|
@@ -1,20 +1,20 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
#* mysh/
|
3
|
+
#* mysh/external.rb -- Support for executing external files.
|
4
4
|
module Mysh
|
5
5
|
|
6
|
-
#Try to execute
|
6
|
+
#Try to execute an external file.
|
7
7
|
#<br>Endemic Code Smells
|
8
8
|
#* :reek:TooManyStatements
|
9
|
-
def self.
|
10
|
-
args =
|
9
|
+
def self.try_execute_external(input)
|
10
|
+
args = input.parsed
|
11
11
|
file_name = args.shift
|
12
12
|
|
13
13
|
if (file_name)
|
14
14
|
ext = File.extname(file_name)
|
15
15
|
|
16
16
|
if ext == '.rb'
|
17
|
-
new_command = "#{RbConfig.ruby} #{
|
17
|
+
new_command = "#{RbConfig.ruby} #{input.cooked}"
|
18
18
|
puts "=> #{new_command}" if MNV[:debug]
|
19
19
|
system(new_command)
|
20
20
|
:ruby_exec
|
@@ -22,8 +22,7 @@ module Mysh
|
|
22
22
|
Mysh.process_file(file_name)
|
23
23
|
:mysh_script
|
24
24
|
elsif ext == '.txt'
|
25
|
-
|
26
|
-
show_handlebar_file(file_name, exec_host)
|
25
|
+
show_handlebar_file(file_name, BindingWrapper.new(binding))
|
27
26
|
:internal
|
28
27
|
end
|
29
28
|
end
|
data/lib/mysh/handlebars.rb
CHANGED
@@ -8,7 +8,7 @@ class Object
|
|
8
8
|
#Show a file with embedded ruby handlebars.
|
9
9
|
#<br>Note:
|
10
10
|
#The message receiver is the evaluation host for the handlebar code.
|
11
|
-
def show_handlebar_file(name, evaluator)
|
11
|
+
def show_handlebar_file(name, evaluator = $mysh_exec_host)
|
12
12
|
puts eval_handlebar_file(name, evaluator)
|
13
13
|
end
|
14
14
|
|
data/lib/mysh/init.rb
CHANGED
@@ -9,18 +9,23 @@ module Mysh
|
|
9
9
|
#Perform init phase processing.
|
10
10
|
def self.mysh_load_init
|
11
11
|
|
12
|
-
unless $mysh_init_file
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
12
|
+
unless $mysh_init_file
|
13
|
+
|
14
|
+
if (home = ENV['HOME'])
|
15
|
+
names = [home + '/mysh_init.mysh',
|
16
|
+
home + '/mysh_init.rb',
|
17
|
+
home + '/mysh_init.txt']
|
18
|
+
|
19
|
+
$mysh_init_file = names.detect {|name| File.file?(name)}
|
20
|
+
end
|
21
|
+
|
22
|
+
if $mysh_init_file
|
23
|
+
mysh "load #{$mysh_init_file.decorate}"
|
24
|
+
else
|
25
|
+
$mysh_init_file = '<none found>'
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
|
-
$mysh_init_file = '<none found>' unless $mysh_init_file
|
24
29
|
end
|
25
30
|
|
26
31
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
#* mysh/internal/input_wrapper.rb -- An action compatible wrapper for a input.
|
4
|
+
module Mysh
|
5
|
+
|
6
|
+
#* mysh/internal/input_wrapper.rb -- An action compatible wrapper for a input.
|
7
|
+
class InputWrapper
|
8
|
+
|
9
|
+
#Build an input wrapper.
|
10
|
+
def initialize(raw)
|
11
|
+
@raw = raw.chomp
|
12
|
+
@raw_command = @raw_body = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
#Access the raw text.
|
16
|
+
attr_reader :raw
|
17
|
+
|
18
|
+
#Get the first raw character.
|
19
|
+
def quick_command
|
20
|
+
@raw[0] || ""
|
21
|
+
end
|
22
|
+
|
23
|
+
#Get the balance of the raw string.
|
24
|
+
def quick_body
|
25
|
+
@raw[1..-1] || ""
|
26
|
+
end
|
27
|
+
|
28
|
+
#Get the command word if it exists.
|
29
|
+
def raw_command
|
30
|
+
@raw_command ||= @raw.split[0] || ""
|
31
|
+
end
|
32
|
+
|
33
|
+
#Get the parameter text.
|
34
|
+
def raw_body
|
35
|
+
@raw_body ||= @raw[(raw_command.length)..-1]
|
36
|
+
end
|
37
|
+
|
38
|
+
#Get the preprocessed argument text.
|
39
|
+
def cooked_body
|
40
|
+
raw_body.preprocess
|
41
|
+
end
|
42
|
+
|
43
|
+
#Access the massaged text.
|
44
|
+
def cooked
|
45
|
+
raw_command + cooked_body
|
46
|
+
end
|
47
|
+
|
48
|
+
#Get the parsed arguments
|
49
|
+
def args
|
50
|
+
Mysh.parse_args(cooked_body)
|
51
|
+
end
|
52
|
+
|
53
|
+
#Get the parsed command line.
|
54
|
+
def parsed
|
55
|
+
[raw_command] + args
|
56
|
+
end
|
57
|
+
|
58
|
+
#Set up input for a quick style command.
|
59
|
+
def quick
|
60
|
+
@raw_command = quick_command
|
61
|
+
@raw_body = quick_body
|
62
|
+
self
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|