rbcli 0.1.7 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +3 -1
- data/README.md +50 -6
- data/exe/rbcli +81 -32
- data/lib/rbcli/configuration/config.rb +6 -6
- data/lib/rbcli/configuration/configurate.rb +1 -1
- data/lib/rbcli/engine/load_project.rb +19 -0
- data/lib/rbcli/engine/parser.rb +2 -2
- data/lib/rbcli/logging/logging.rb +7 -4
- data/lib/rbcli/stateful_systems/state_storage.rb +4 -0
- data/lib/rbcli/version.rb +1 -1
- data/lib/rbcli-tool/erb_renderer.rb +43 -0
- data/lib/rbcli-tool/mdless_fix.rb +386 -0
- data/lib/rbcli-tool/project.rb +87 -0
- data/lib/rbcli-tool.rb +16 -0
- data/lib/rbcli.rb +11 -10
- data/rbcli.gemspec +1 -0
- data/skeletons/micro/executable +123 -0
- data/skeletons/mini/executable +241 -0
- data/skeletons/project/.gitignore +11 -0
- data/skeletons/project/.rbcli +0 -0
- data/skeletons/project/.rspec +3 -0
- data/skeletons/project/CODE_OF_CONDUCT.md +74 -0
- data/skeletons/project/Gemfile +6 -0
- data/skeletons/project/README.md +31 -0
- data/skeletons/project/Rakefile +6 -0
- data/skeletons/project/application/commands/command.rb +31 -0
- data/skeletons/project/application/commands/scripts/script.sh +23 -0
- data/skeletons/project/application/options.rb +30 -0
- data/skeletons/project/config/autoupdate.rb +32 -0
- data/skeletons/project/config/logging.rb +19 -0
- data/skeletons/project/config/storage.rb +34 -0
- data/skeletons/project/config/userspace.rb +28 -0
- data/skeletons/project/config/version.rb +3 -0
- data/skeletons/project/default_user_configs/user_defaults.yml +6 -0
- data/skeletons/project/exe/executable +54 -0
- data/skeletons/project/hooks/default_action.rb +16 -0
- data/skeletons/project/hooks/first_run.rb +16 -0
- data/skeletons/project/hooks/post_execution.rb +14 -0
- data/skeletons/project/hooks/pre_execution.rb +14 -0
- data/skeletons/project/spec/spec_helper.rb +14 -0
- data/skeletons/project/spec/untitled_spec.rb +9 -0
- data/skeletons/project/untitled.gemspec +40 -0
- metadata +47 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eeb0aa5b07a4650d68356dabb3e0f185f7b54adaa8911902780ee66d079a1d83
|
4
|
+
data.tar.gz: 674ce261ee73a19d22667fbfc9906bddfcd1bda88b526939b171696089ef55d9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 20eee68c0d42e059fa1e767211941e4e998e7f7c8e9f6440356544a777fd5abc268215d1036b42be3170c1b090bc9d77b176c2ffc89ffa94850c31f888887b5d
|
7
|
+
data.tar.gz: 6272c53067831c7902f42be08532f82199cee8bbb8427bc9ca6c8e2028add6f81726cec0dd381bc056cf030ec067c7249ab406538b4b0fac81b6ba79f5cb2fa9
|
data/Gemfile.lock
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rbcli (0.1.
|
4
|
+
rbcli (0.1.8)
|
5
5
|
aws-sdk-dynamodb (~> 1.6)
|
6
6
|
colorize (~> 0.8)
|
7
7
|
deep_merge (~> 1.2)
|
8
8
|
macaddr (~> 1.7)
|
9
|
+
mdless (~> 0.0)
|
9
10
|
octokit (~> 4.9)
|
10
11
|
rufus-scheduler (~> 3.5)
|
11
12
|
|
@@ -37,6 +38,7 @@ GEM
|
|
37
38
|
jmespath (1.4.0)
|
38
39
|
macaddr (1.7.1)
|
39
40
|
systemu (~> 2.6.2)
|
41
|
+
mdless (0.0.10)
|
40
42
|
minitest (5.11.3)
|
41
43
|
multipart-post (2.0.0)
|
42
44
|
octokit (4.9.0)
|
data/README.md
CHANGED
@@ -26,6 +26,8 @@ Some of its key features include:
|
|
26
26
|
|
27
27
|
* __External Script Wrapping__: High-level wrapping for Bash scripts, or any other applcication you'd like to wrap into a command.
|
28
28
|
|
29
|
+
* __Project Structure and Generators__: Create a well-defined project directory structure which organizes your code and allows you to package and distribute your application as a Gem. Generators can also help speed up the process of creating new commands, scripts, and hooks!
|
30
|
+
|
29
31
|
|
30
32
|
## Installation
|
31
33
|
|
@@ -60,14 +62,16 @@ mytool show -l
|
|
60
62
|
Note that all options and parameters will have both a short and long version of the parameter available for use.
|
61
63
|
|
62
64
|
|
63
|
-
## Getting Started
|
65
|
+
## Getting Started (Lightweight)
|
66
|
+
|
67
|
+
For a lightweight skeleton that consists of a single file, use `rbcli init -t mini -n <projectname>`, or `rbcli init -t micro -n <projectname>` for an even more simplified one.
|
64
68
|
|
65
|
-
|
69
|
+
These lightweight skeletons allow creating single-file applications/scripts using RBCli. They consolidate all of the options in the standard project format into these sections:
|
66
70
|
|
67
71
|
* The configuration
|
68
72
|
* Storage subsystem configuration (optional)
|
69
73
|
* A command declaration
|
70
|
-
* The parse command
|
74
|
+
* The parse command
|
71
75
|
|
72
76
|
### Configuration
|
73
77
|
|
@@ -84,7 +88,7 @@ Rbcli::Configurate.me do
|
|
84
88
|
|
85
89
|
config_userfile '/etc/mytool/config.yml', merge_defaults: true, required: false # (Optional) Set location of user's config file. If merge_defaults=true, user settings override default settings, and if false, defaults are not loaded at all. If required=true, application will not run if file does not exist.
|
86
90
|
config_defaults 'defaults.yml' # (Optional, Multiple) Load a YAML file as part of the default config. This can be called multiple times, and the YAML files will be merged. User config is generated from these
|
87
|
-
config_default :myopt, description: 'Testing this',
|
91
|
+
config_default :myopt, description: 'Testing this', default: true # (Optional, Multiple) Specify an individual configuration parameter and set a default value. These will also be included in generated user config
|
88
92
|
|
89
93
|
option :name, 'Give me your name', type: :string, default: 'Foo', required: false, permitted: ['Jack', 'Jill'] # (Optional, Multiple) Add a global CLI parameter. Supported types are :string, :boolean, :integer, :float, :date, and :io. Can be called multiple times.
|
90
94
|
|
@@ -147,7 +151,7 @@ class Test < Rbcli::Command
|
|
147
151
|
parameter :force, 'Force testing', type: :boolean, default: false, required: false # (Optional, Multiple) Add a command-specific CLI parameter. Can be called multiple times
|
148
152
|
|
149
153
|
config_defaults 'defaults.yml' # (Optional, Multiple) Load a YAML file as part of the default config. This can be called multiple times, and the YAML files will be merged. User config is generated from these
|
150
|
-
config_default :myopt2, description: 'Testing this again',
|
154
|
+
config_default :myopt2, description: 'Testing this again', default: true # (Optional, Multiple) Specify an individual configuration parameter and set a default value. These will also be included in generated user config
|
151
155
|
|
152
156
|
extern path: 'env | grep "^__PARAMS\|^__ARGS\|^__GLOBAL\|^__CONFIG"', envvars: {MYVAR: 'some_value'} # (Required unless `action` defined) Runs a given application, with optional environment variables, when the user runs the command.
|
153
157
|
extern envvars: {MY_OTHER_VAR: 'another_value'} do |params, args, global_opts, config| # Alternate usage. Supplying a block instead of a path allows us to modify the command based on the arguments and configuration supplied by the user.
|
@@ -202,7 +206,7 @@ The defaults chain allows you to specify sane defaults for your CLI tool through
|
|
202
206
|
* DSL Statements
|
203
207
|
* In the DSL, you can specify options individually by providing a name, description, and default value
|
204
208
|
* This is good for simpler configuration, as the descriptions provided are written out as comments in the generated user config
|
205
|
-
* `config_default :name, description: '<description_text>',
|
209
|
+
* `config_default :name, description: '<description_text>', default: <default_value>` in the DSL
|
206
210
|
|
207
211
|
Users can generate configs by running `yourclitool -g`. This will generate a config file at the tool's default location specified in the DSL. A specific location can be used via the `-c` parameter. You can test how this works by running `examples/mytool -c test.yml -g`.
|
208
212
|
|
@@ -276,6 +280,8 @@ Hash native methods:
|
|
276
280
|
|
277
281
|
Additional methods:
|
278
282
|
|
283
|
+
* `commit`
|
284
|
+
* Every assignment to the top level of the hash will result in a write to disk (for example: `Rbcli.local_state[:yourkey] = 'foo'`). If you are manipulating nested hashes, you can trigger a save manually by calling `commit`.
|
279
285
|
* `clear`
|
280
286
|
* Resets the data back to an empty hash.
|
281
287
|
* `refresh`
|
@@ -481,6 +487,44 @@ class Test < Rbcli::Command
|
|
481
487
|
end
|
482
488
|
```
|
483
489
|
|
490
|
+
## Project Structure and Generators
|
491
|
+
|
492
|
+
RBCli supports using predefined project structure, helping to organize all of the options and commands that you may use. It also
|
493
|
+
|
494
|
+
Creating a new project skeleton is as easy as running `rbcli init -n <projectname>`. It will create a folder under the currect directory using the name specified, allowing you to create a command that can be easily packaged and distributed as a gem.
|
495
|
+
|
496
|
+
The folder structure is as follows:
|
497
|
+
|
498
|
+
```
|
499
|
+
<projectname>
|
500
|
+
|
|
501
|
+
|--- application
|
502
|
+
| |
|
503
|
+
| |--- commands
|
504
|
+
| |
|
505
|
+
| |---scripts
|
506
|
+
|
|
507
|
+
|--- config
|
508
|
+
|--- default_user_configs
|
509
|
+
|--- exe
|
510
|
+
|--- hooks
|
511
|
+
|--- spec
|
512
|
+
```
|
513
|
+
|
514
|
+
It is highly recommended to __not__ create files in these folders manually, and to use the RBCli generators instead:
|
515
|
+
|
516
|
+
```shell
|
517
|
+
rbcli command -n <name>
|
518
|
+
rbcli script -n <name>
|
519
|
+
rbcli userconf -n <name>
|
520
|
+
rbcli hook -t pre
|
521
|
+
rbcli hook -t post
|
522
|
+
rbcli hook -t default
|
523
|
+
rbcli hook -t runonce
|
524
|
+
```
|
525
|
+
|
526
|
+
That said, this readme will provide you with the information required to do things manually if you so desire. More details on generators later.
|
527
|
+
|
484
528
|
|
485
529
|
## Development
|
486
530
|
|
data/exe/rbcli
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'rbcli'
|
3
|
+
require '../lib/rbcli'
|
4
4
|
|
5
5
|
Rbcli::Configurate.me do
|
6
6
|
scriptname __FILE__.split('/')[-1]
|
@@ -8,46 +8,95 @@ Rbcli::Configurate.me do
|
|
8
8
|
description 'RBCli initialization tool'
|
9
9
|
end
|
10
10
|
|
11
|
-
|
12
|
-
description 'Initialize a skeleton RBCli executable.'
|
13
|
-
usage 'This will generate a new file in the current folder'
|
14
|
-
parameter :filename, 'Name of file to generate', type: :string, required: true
|
11
|
+
require '../lib/rbcli-tool'
|
15
12
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
class Docs < Rbcli::Command
|
14
|
+
description 'Show Documentation (Beta)'
|
15
|
+
|
16
|
+
action do
|
17
|
+
readme = "#{File.dirname(__FILE__)}/../README.md"
|
18
|
+
begin
|
19
|
+
CLIMarkdown::Converter.new([readme])
|
20
|
+
rescue Errno::EPIPE
|
21
|
+
# Empty
|
22
|
+
end
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
23
|
-
class
|
24
|
-
description 'Initialize a skeleton
|
25
|
-
usage
|
26
|
-
|
26
|
+
class Init < Rbcli::Command
|
27
|
+
description 'Initialize a skeleton RBCli project.'
|
28
|
+
usage <<-EOF
|
29
|
+
This will generate a new project structure under the current directory. Use -t to specify the type:
|
30
|
+
|
31
|
+
|
32
|
+
Standard: A complete RBCli project structure. Recommended for most applications, it provides
|
33
|
+
a framework for organizing code and creating a gem to be installed/distributed.
|
34
|
+
|
35
|
+
Mini: A single-file RBCli project. All features are supported, but project structure is left to the developer.
|
36
|
+
Recommended for smaller applications, or for integrating RBCli into an existing application.
|
37
|
+
|
38
|
+
Micro: A single-file, minimal RBCli project. Similar to a mini project, but only the minimal required code is
|
39
|
+
generated. Recommended for rapid prototyping of scripts, for advanced users only.
|
40
|
+
EOF
|
41
|
+
|
42
|
+
parameter :name, 'Name of project to generate', type: :string, required: true
|
43
|
+
parameter :type, 'Specify project type', type: :string, permitted: %w(standard micro mini), default: 'standard'
|
44
|
+
parameter :description, 'A description of the project', type: :string, default: 'TODO: Description goes here'
|
27
45
|
|
28
46
|
action do |params, args, global_opts, config|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
47
|
+
dest = "#{Dir.pwd}/#{params[:name]}"
|
48
|
+
|
49
|
+
# Prepare template vars
|
50
|
+
template_vars = {
|
51
|
+
cmdname: params[:name],
|
52
|
+
description: params[:description],
|
53
|
+
rbcli_version: Rbcli::VERSION
|
54
|
+
}
|
55
|
+
|
56
|
+
proj = RBCliTool::Project.new(dest, template_vars)
|
34
57
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
elsif template_vars
|
39
|
-
print "Generating file #{dest}..."
|
40
|
-
text = File.read src
|
41
|
-
template_vars.each do |k, v|
|
42
|
-
text.gsub! /{{\*\*#{k}\*\*}}/, v
|
58
|
+
if proj.exists?
|
59
|
+
RBCliTool.continue_confirmation "The project or file #{params[:name]} already exists; contents will be overwritten."
|
60
|
+
FileUtils.rm_rf dest
|
43
61
|
end
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
62
|
+
|
63
|
+
case params[:type]
|
64
|
+
when 'micro' # Micro: Single File, simplified
|
65
|
+
puts "\nInitialization Complete!\n" if proj.create_micro
|
66
|
+
|
67
|
+
when 'mini' # Mini: Single File
|
68
|
+
puts "\nInitialization Complete!\n" if proj.create_mini
|
69
|
+
|
70
|
+
else # Standard; full project structure
|
71
|
+
if proj.create
|
72
|
+
puts "\nInitialization Complete!\n"
|
73
|
+
else
|
74
|
+
puts "\nAn RBCli Project already exists in the current directory tree at: #{proj.exists?}. Aborting.\n"
|
75
|
+
end
|
76
|
+
|
77
|
+
end # END case params[:type]
|
78
|
+
|
50
79
|
end
|
51
80
|
end
|
52
81
|
|
82
|
+
class Command < Rbcli::Command
|
83
|
+
description 'Generate an Rbcli Command under the current project'
|
84
|
+
usage <<-EOF
|
85
|
+
This will generate a new project structure under the current directory. Use -t to specify the type:
|
86
|
+
|
87
|
+
|
88
|
+
Standard: A complete RBCli project structure. Recommended for most applications, it provides
|
89
|
+
a framework for organizing code and creating a gem to be installed/distributed.
|
90
|
+
|
91
|
+
Mini: A single-file RBCli project. All features are supported, but project structure is left to the developer.
|
92
|
+
Recommended for smaller applications, or for integrating RBCli into an existing application.
|
93
|
+
|
94
|
+
Micro: A single-file, minimal RBCli project. Similar to a mini project, but only the minimal required code is
|
95
|
+
generated. Recommended for rapid prototyping of scripts, for advanced users only.
|
96
|
+
EOF
|
97
|
+
|
98
|
+
parameter :name, 'Name of command to generate', type: :string, required: true
|
99
|
+
end
|
100
|
+
|
101
|
+
|
53
102
|
Rbcli.parse
|
@@ -45,7 +45,7 @@ module Rbcli::Config
|
|
45
45
|
@categorized_defaults = nil
|
46
46
|
@loaded = false
|
47
47
|
|
48
|
-
def self.set_userfile filename, merge_defaults:
|
48
|
+
def self.set_userfile filename, merge_defaults: true, required: false
|
49
49
|
@config_file = File.expand_path filename
|
50
50
|
@merge_defaults = merge_defaults
|
51
51
|
@userfile_required = required
|
@@ -67,11 +67,11 @@ module Rbcli::Config
|
|
67
67
|
@loaded = false
|
68
68
|
end
|
69
69
|
|
70
|
-
def self.add_default name, description: nil,
|
70
|
+
def self.add_default name, description: nil, default: nil
|
71
71
|
@config_individual_lines ||= []
|
72
|
-
text = "#{name.to_s}: #{
|
72
|
+
text = "#{name.to_s}: #{default}".ljust(30) + " # #{description}"
|
73
73
|
@config_individual_lines.push text unless @config_individual_lines.include? text
|
74
|
-
@config_defaults[name.to_sym] =
|
74
|
+
@config_defaults[name.to_sym] = default
|
75
75
|
@loaded = false
|
76
76
|
end
|
77
77
|
|
@@ -81,8 +81,8 @@ module Rbcli::Config
|
|
81
81
|
@config_text ||= ''
|
82
82
|
@config_text += "\n" unless @config_text.empty?
|
83
83
|
File.readlines(filename).each do |line|
|
84
|
-
if
|
85
|
-
@config_text << "\n
|
84
|
+
if line.start_with? '---' or line.start_with? '...'
|
85
|
+
@config_text << "\n" unless @config_text.empty?
|
86
86
|
else
|
87
87
|
@config_text << line unless @config_text.include? line
|
88
88
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Rbcli
|
2
|
+
def self.load_project
|
3
|
+
project_root = File.expand_path "#{File.dirname(caller[0].split(':')[0])}/../"
|
4
|
+
|
5
|
+
# We require all ruby files as-is
|
6
|
+
%w(config hooks application application/commands).each do |dir|
|
7
|
+
dirpath = "#{project_root}/#{dir}"
|
8
|
+
Dir.glob "#{dirpath}/*.rb" do |file|
|
9
|
+
require file
|
10
|
+
end if Dir.exists? dirpath
|
11
|
+
end
|
12
|
+
|
13
|
+
# We automatically pull in default user configs
|
14
|
+
configspath = "#{project_root}/default_user_configs"
|
15
|
+
Dir.glob "#{configspath}/*.{yml,yaml}" do |file|
|
16
|
+
Rbcli::Configurate.me {config_defaults file}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/rbcli/engine/parser.rb
CHANGED
@@ -28,8 +28,8 @@ Commands:
|
|
28
28
|
opt name.to_sym, opts[:description], type: opts[:type], default: opts[:default], required: opts[:required], permitted: opts[:permitted]
|
29
29
|
end
|
30
30
|
opt :json_output, 'Output result in machine-friendly JSON format', :type => :boolean, :default => false if data[:allow_json]
|
31
|
-
opt :config_file, 'Specify a config file manually', :type => :string, :default => data[:config_userfile]
|
32
|
-
opt :generate_config, 'Generate a new config file' #defaults to false
|
31
|
+
opt :config_file, 'Specify a config file manually', :type => :string, :default => data[:config_userfile] unless data[:config_userfile].nil?
|
32
|
+
opt :generate_config, 'Generate a new config file' unless data[:config_userfile].nil? #defaults to false
|
33
33
|
stop_on Rbcli::Command.commands.keys
|
34
34
|
end
|
35
35
|
|
@@ -29,12 +29,12 @@ module Rbcli::Logger
|
|
29
29
|
|
30
30
|
Rbcli::Config::add_categorized_defaults :logger, 'Log Settings', {
|
31
31
|
log_level: {
|
32
|
-
description: '0-5, or DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN',
|
33
|
-
value: @default_level ||
|
32
|
+
description: '0-5, or DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN. Set to null (~) to disable logging.',
|
33
|
+
value: @default_level || nil
|
34
34
|
},
|
35
35
|
log_target: {
|
36
|
-
description: 'STDOUT, STDERR, or a file path',
|
37
|
-
value: @default_target ||
|
36
|
+
description: 'STDOUT, STDERR, or a file path. Set to null (~) to disable logging.',
|
37
|
+
value: @default_target || nil
|
38
38
|
}
|
39
39
|
}
|
40
40
|
end
|
@@ -45,9 +45,12 @@ module Rbcli::Logger
|
|
45
45
|
target = STDOUT
|
46
46
|
elsif Rbcli::config[:logger][:log_target].downcase == 'stderr'
|
47
47
|
target = STDERR
|
48
|
+
elsif Rbcli::config[:logger][:log_target].nil?
|
49
|
+
target = '/dev/null'
|
48
50
|
else
|
49
51
|
target = Rbcli::config[:logger][:log_target]
|
50
52
|
end
|
53
|
+
target = '/dev/null' if Rbcli::config[:logger][:log_level].nil?
|
51
54
|
@logger = Logger.new(target)
|
52
55
|
@logger.level = Rbcli::config[:logger][:log_level]
|
53
56
|
|
data/lib/rbcli/version.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module RBCliTool
|
4
|
+
class ERBRenderer
|
5
|
+
def initialize filename, varlist
|
6
|
+
@filename = filename
|
7
|
+
@vars = varlist
|
8
|
+
end
|
9
|
+
|
10
|
+
def render
|
11
|
+
ERB.new(File.read(@filename)).result(binding)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.cp_file src, dest, template_vars = nil
|
16
|
+
dest = "#{dest}#{src.split('/')[-1]}" if dest.end_with? '/'
|
17
|
+
if File.exists? dest
|
18
|
+
puts "File #{dest} already exists. Please delete it and try again."
|
19
|
+
else
|
20
|
+
print "Generating file " + dest + " ... "
|
21
|
+
|
22
|
+
if template_vars
|
23
|
+
renderer = ERBRenderer.new src, template_vars
|
24
|
+
File.open(dest, 'w') {|file| file.write renderer.render}
|
25
|
+
else
|
26
|
+
FileUtils.cp src, dest
|
27
|
+
end
|
28
|
+
|
29
|
+
FileUtils.rm_f "#{File.dirname(dest)}/.keep" if File.exists? "#{File.dirname(dest)}/.keep"
|
30
|
+
puts "Done!"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.continue_confirmation text
|
35
|
+
puts text
|
36
|
+
print "Continue? (Y/n): "
|
37
|
+
input = gets
|
38
|
+
unless input[0].downcase == 'y'
|
39
|
+
puts "\n Aborting..."
|
40
|
+
exit 0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|