rbcli 0.1.10 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +31 -0
- data/CODE_OF_CONDUCT.md +1 -1
- data/Gemfile.lock +12 -12
- data/LICENSE.txt +674 -21
- data/README.md +80 -443
- data/bin/console +19 -0
- data/bin/setup +20 -0
- data/docs/404.html +639 -0
- data/docs/advanced/automatic_updates/index.html +791 -0
- data/docs/advanced/command_types/index.html +946 -0
- data/docs/advanced/distributed_state_locking/index.html +777 -0
- data/docs/advanced/hooks/index.html +836 -0
- data/docs/advanced/state_storage/index.html +957 -0
- data/docs/advanced/user_config_files/index.html +818 -0
- data/docs/assets/fonts/font-awesome.css +4 -0
- data/docs/assets/fonts/material-icons.css +13 -0
- data/docs/assets/fonts/specimen/FontAwesome.ttf +0 -0
- data/docs/assets/fonts/specimen/FontAwesome.woff +0 -0
- data/docs/assets/fonts/specimen/FontAwesome.woff2 +0 -0
- data/docs/assets/fonts/specimen/MaterialIcons-Regular.ttf +0 -0
- data/docs/assets/fonts/specimen/MaterialIcons-Regular.woff +0 -0
- data/docs/assets/fonts/specimen/MaterialIcons-Regular.woff2 +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/images/icons/bitbucket.1b09e088.svg +20 -0
- data/docs/assets/images/icons/github.f0b8504a.svg +18 -0
- data/docs/assets/images/icons/gitlab.6dd19c00.svg +38 -0
- data/docs/assets/javascripts/application.a59e2a89.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.da.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.de.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.du.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.es.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.fi.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.fr.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.hu.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.it.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.jp.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.multi.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.no.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.pt.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.ro.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.ru.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.stemmer.support.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.sv.js +1 -0
- data/docs/assets/javascripts/lunr/lunr.tr.js +1 -0
- data/docs/assets/javascripts/lunr/tinyseg.js +1 -0
- data/docs/assets/javascripts/modernizr.1aa3b519.js +1 -0
- data/docs/assets/stylesheets/application-palette.6079476c.css +2 -0
- data/docs/assets/stylesheets/application.ba0fd1a6.css +2 -0
- data/docs/development/code_of_conduct/index.html +883 -0
- data/docs/development/contributing/index.html +744 -0
- data/docs/development/license/index.html +715 -0
- data/docs/imported/changelog/index.html +853 -0
- data/docs/imported/quick_reference/index.html +1057 -0
- data/docs/index.html +732 -0
- data/docs/search/search_index.json +569 -0
- data/docs/sitemap.xml +93 -0
- data/docs/tutorial/10-getting_started/index.html +806 -0
- data/docs/tutorial/20-project_layout/index.html +972 -0
- data/docs/tutorial/30-your_first_command/index.html +906 -0
- data/docs/tutorial/40-options_parameters_and_arguments/index.html +1049 -0
- data/docs/tutorial/50-publishing/index.html +838 -0
- data/docs/whoami/index.html +709 -0
- data/docs-src/docs/advanced/automatic_updates.md +42 -0
- data/docs-src/docs/advanced/command_types.md +144 -0
- data/docs-src/docs/advanced/distributed_state_locking.md +33 -0
- data/docs-src/docs/advanced/hooks.md +65 -0
- data/docs-src/docs/advanced/logging.md +35 -0
- data/docs-src/docs/advanced/state_storage.md +117 -0
- data/docs-src/docs/advanced/user_config_files.md +47 -0
- data/docs-src/docs/development/code_of_conduct.md +74 -0
- data/docs-src/docs/development/contributing.md +49 -0
- data/docs-src/docs/development/license.md +10 -0
- data/docs-src/docs/imported/changelog.md +31 -0
- data/docs-src/docs/imported/quick_reference.md +150 -0
- data/docs-src/docs/index.md +38 -0
- data/docs-src/docs/tutorial/10-getting_started.md +41 -0
- data/docs-src/docs/tutorial/20-project_layout.md +115 -0
- data/docs-src/docs/tutorial/30-your_first_command.md +126 -0
- data/docs-src/docs/tutorial/40-options_parameters_and_arguments.md +251 -0
- data/docs-src/docs/tutorial/50-publishing.md +47 -0
- data/docs-src/docs/whoami.md +28 -0
- data/docs-src/makesite.sh +14 -0
- data/docs-src/mkdocs.yml +76 -0
- data/docs-src/runsite.sh +3 -0
- data/exe/rbcli +76 -5
- data/lib/rbcli/autoupdate/autoupdate.rb +24 -4
- data/lib/rbcli/autoupdate/gem_updater.rb +23 -2
- data/lib/rbcli/autoupdate/github_updater.rb +22 -1
- data/lib/rbcli/configuration/config.rb +22 -1
- data/lib/rbcli/configuration/configurate.rb +24 -2
- data/lib/rbcli/engine/command.rb +26 -6
- data/lib/rbcli/engine/load_project.rb +29 -3
- data/lib/rbcli/engine/parser.rb +25 -4
- data/lib/rbcli/logging/logging.rb +21 -0
- data/lib/rbcli/scriptwrapping/scriptwrapper.rb +30 -11
- data/lib/rbcli/stateful_systems/configuratestorage.rb +20 -0
- data/lib/rbcli/stateful_systems/state_storage.rb +20 -0
- data/lib/rbcli/stateful_systems/storagetypes/localstate.rb +20 -0
- data/lib/rbcli/stateful_systems/storagetypes/remote_state_connectors/dynamodb.rb +20 -0
- data/lib/rbcli/stateful_systems/storagetypes/remotestate_dynamodb.rb +20 -0
- data/lib/rbcli/util/hash_deep_symbolize.rb +43 -22
- data/lib/rbcli/util/string_colorize.rb +20 -0
- data/lib/rbcli/version.rb +21 -1
- data/lib/rbcli-tool/generators.rb +20 -0
- data/lib/rbcli-tool/mdless_fix.rb +20 -0
- data/lib/rbcli-tool/project.rb +27 -2
- data/lib/rbcli-tool/util.rb +20 -0
- data/lib/rbcli-tool.rb +20 -0
- data/lib/rbcli.rb +20 -0
- data/lib-sh/lib-rbcli.sh +19 -0
- data/rbcli.gemspec +22 -3
- data/skeletons/project/CODE_OF_CONDUCT.md +1 -1
- data/skeletons/project/README.md +17 -2
- data/skeletons/project/application/commands/command.erb +10 -8
- data/skeletons/project/application/commands/script.erb +3 -1
- data/skeletons/project/application/commands/scripts/script.sh +2 -2
- data/skeletons/project/application/options.rb +12 -3
- data/skeletons/project/config/autoupdate.rb +5 -2
- data/skeletons/project/config/storage.rb +7 -6
- data/skeletons/project/config/userspace.rb +6 -1
- data/skeletons/project/exe/executable +1 -1
- data/skeletons/project/lib/.keep +0 -0
- data/skeletons/project/untitled.gemspec +4 -4
- data/skeletons/project/{default_user_configs → userconf}/user_defaults.yml +0 -0
- metadata +85 -9
- data/examples/defaults.yml +0 -4
- data/examples/myscript.sh +0 -23
- data/examples/mytool +0 -95
data/README.md
CHANGED
@@ -1,8 +1,11 @@
|
|
1
|
-
RBCli is currently in Alpha stages of development. All releases can be considered stable, though breaking changes may be made between versions.
|
1
|
+
RBCli is currently in Alpha stages of development. All releases can be considered stable, though breaking changes may be made between versions.
|
2
2
|
|
3
3
|
# RBCli
|
4
4
|
|
5
|
-
|
5
|
+
As technologists today, we work with the command line a lot. We script a lot. We write tools to share with each other to make our lives easier. We even write applications to make up for missing features in the 3rd party software that we buy. Unfortunately, when writing CLI tools, this process has typically been very painful. We've been working with low-level frameworks for decades; frameworks like `getopt` (1980) and `curses` (1977). They fit their purpose well; they were both computationally lightweight for the computers of the day, and they gave engineers full control and flexibility when it came to how things were built. Over the years, we've used them to settle on several design patterns that we know work well. Patterns as to what a CLI command looks like, what a config file looks like, what remote execution looks like, and even how to use locks (mutexes, semaphores, etc) to control application flow and data atomicity. Yet we're stuck writing the same low-level code anytime we want to write our tooling. Not anymore.
|
6
|
+
|
7
|
+
Enter RBCli. RBCli is a framework to quickly develop advanced command-line tools in Ruby. It has been written from the ground up with the needs of the modern technologist in mind, designed to make advanced CLI tool development as painless as possible. In RBCli, low-level code has been wrapped and/or replaced with higher-level methods. Much of the functionality has even been reduced to single methods: for example, it takes just one declaration to define, load, and generate a user's config file at the appropriate times. Many other features are automated and require no work by the engineer. These make RBCli a fundamental re-thining of how we develop CLI tools, enabling the rapid development of applications for everyone from hobbyists to enterprises.
|
8
|
+
|
6
9
|
|
7
10
|
Some of its key features include:
|
8
11
|
|
@@ -29,522 +32,156 @@ Some of its key features include:
|
|
29
32
|
* __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
33
|
|
31
34
|
|
32
|
-
|
33
|
-
|
34
|
-
RBCli is available on rubygems.org. You can add it to your application's `Gemfile` or `gemspec`, or install it manually via `gem install rbcli`.
|
35
|
+
For more information, take a look at the __[official documentation](http://akhoury6.github.io/rbcli/)__ or keep reading for a quick reference.
|
35
36
|
|
36
37
|
|
37
|
-
|
38
|
+
# Quick Reference
|
38
39
|
|
39
|
-
|
40
|
+
## Installation
|
40
41
|
|
41
|
-
|
42
|
-
toolname [options] command [parameters] [lineitem]
|
43
|
-
```
|
42
|
+
RBCli is available on rubygems.org. You can add it to your application's `Gemfile` or `gemspec`, or install it manually by running:
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
* `-g / --generate-config` generates a config file by writing out the defaults to a YAML file (location is configurable, more on that below)
|
48
|
-
* `-v / --version` shows the version`
|
49
|
-
* `-h / --help` shows the help
|
50
|
-
* __Command__ represents the subcommands that you will create, such as `test` or `apply`
|
51
|
-
* __Parameters__ are the same as options, but only apply to the specific subcommand being executed. In this case only the `-h / --help` parameter is provided.
|
52
|
-
* __Lineitems__ are strings that don't begin with a '-', and are passed to the command's code. These can be used as subcommands or additional parameters for your code.
|
53
|
-
|
54
|
-
So a valid command could look something like these:
|
55
|
-
|
56
|
-
```shell
|
57
|
-
mytool -n load --filename=foo.txt
|
58
|
-
mytool parse foo.txt
|
59
|
-
mytool show -l
|
44
|
+
```bash
|
45
|
+
gem install rbcli
|
60
46
|
```
|
61
47
|
|
62
|
-
|
48
|
+
Then, `cd` to the folder you'd like to create your project under and run:
|
63
49
|
|
64
|
-
|
65
|
-
|
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.
|
68
|
-
|
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:
|
70
|
-
|
71
|
-
* The configuration
|
72
|
-
* Storage subsystem configuration (optional)
|
73
|
-
* A command declaration
|
74
|
-
* The parse command
|
75
|
-
|
76
|
-
### Configuration
|
77
|
-
|
78
|
-
```ruby
|
79
|
-
require 'rbcli'
|
80
|
-
|
81
|
-
Rbcli::Configurate.me do
|
82
|
-
scriptname __FILE__.split('/')[-1] # (Required) This line identifies the tool's executable. You can change it if needed, but this should work for most cases.
|
83
|
-
version '0.1.0' # (Required) The version number
|
84
|
-
description 'This is my test CLI tool.' # (Requierd) A description that will appear when the user looks at the help with -h. This can be as long as needed.
|
85
|
-
|
86
|
-
log_level :info # (Optional) Set the default log_level for users. 0-5, or DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
|
87
|
-
log_target 'stderr' # (Optional) Set the target for logs. Valid values are STDOUT, STDERR, or a file path (as strings)
|
88
|
-
|
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.
|
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
|
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
|
92
|
-
|
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.
|
94
|
-
|
95
|
-
autoupdate github_repo: 'akhoury6/rbcli', access_token: nil, enterprise_hostname: nil, force_update: false # (Optional) Check for updates to this application at a GitHub repo. The repo must use version number tags in accordance to best practices: https://help.github.com/articles/creating-releases/
|
96
|
-
autoupdate gem: 'rbcli', force_update: false # (Optional) Check for updates to this application through RubyGems.org.
|
97
|
-
|
98
|
-
default_action do |opts| # (Optional) The default code to execute when no subcommand is given. If not present, the help is shown (same as -h)
|
99
|
-
puts "Hello, #{opts[:name]}."
|
100
|
-
puts "To see the help, use -h"
|
101
|
-
end
|
102
|
-
|
103
|
-
pre_hook do |opts| # (Optional) Allows providing a block of code that runs before any command
|
104
|
-
puts 'This is a pre-command hook. It executes before the command.'
|
105
|
-
end
|
106
|
-
|
107
|
-
post_hook do |opts| # (Optional) Allows providing a block of code that runs after any command
|
108
|
-
puts 'This is a post-command hook. It executes after the command.'
|
109
|
-
end
|
110
|
-
|
111
|
-
first_run halt_after_running: true do # (Optional) Allows providing a block of code that executes the first time that the application is run on a given system. If `halt_after_running` is set to `true` then parsing will not continue after this code is executed. All subsequent runs will not execute this code.
|
112
|
-
puts "This is the first time the mytool command is run! Don't forget to generate a config file with the `-g` option before continuing."
|
113
|
-
end
|
114
|
-
end
|
50
|
+
```bash
|
51
|
+
rbcli init -n mytool -d "A simple CLI tool"
|
115
52
|
```
|
116
53
|
|
117
|
-
|
54
|
+
Or, for a single-file tool without any folder/gem tructure, run `rbcli init -t mini -n <projectname>` or `rbcli init -t micro -n <projectname>`.
|
118
55
|
|
119
|
-
For the `option` parameters that you want to create, the following types are supported:
|
120
56
|
|
121
|
-
|
122
|
-
* `:boolean` or `:flag`
|
123
|
-
* `:integer`
|
124
|
-
* `:float`
|
57
|
+
## Creating a command
|
125
58
|
|
126
|
-
|
59
|
+
There are three types of commands: standard, scripted, and external.
|
127
60
|
|
128
|
-
|
61
|
+
* __Standard__ commands let you code the command directly in Ruby
|
62
|
+
* __Scripted__ commands provide you with a bash script, where all of the parsed information (params, options, args, and config) is shared
|
63
|
+
* __External__ commands let you wrap 3rd party applications directly
|
129
64
|
|
130
|
-
|
65
|
+
### Standard Commands
|
131
66
|
|
132
|
-
|
67
|
+
To create a new command called `foo`, run:
|
133
68
|
|
134
|
-
```
|
135
|
-
|
136
|
-
local_state '/var/mytool/localstate', force_creation: true, halt_on_error: true # (Optional) Creates a hash that is automatically saved to a file locally for state persistance. It is accessible to all commands at Rbcli.local_state[:yourkeyhere]
|
137
|
-
remote_state_dynamodb table_name: 'mytable', region: 'us-east-1', force_creation: true, halt_on_error: true, locking: true # (Optional) Creates a hash that is automatically saved to a DynamoDB table. It is recommended to keep halt_on_error=true when using a shared state.
|
138
|
-
end
|
69
|
+
```bash
|
70
|
+
rbcli command -n foo
|
139
71
|
```
|
140
72
|
|
141
|
-
|
142
|
-
|
143
|
-
### Command Declaration
|
73
|
+
You will now find the command code in `application/commands/list.rb`. Edit the `action` block to write your coode.
|
144
74
|
|
145
|
-
|
75
|
+
### Scripted Commands
|
146
76
|
|
147
|
-
|
148
|
-
class Test < Rbcli::Command # Declare a new command by subclassing Rbcli::Command
|
149
|
-
description 'This is a short description.' # (Required) Short description for the global help
|
150
|
-
usage 'This is some really long usage text description!' # (Required) Long description for the command-specific help
|
151
|
-
parameter :force, 'Force testing', type: :boolean, default: false, required: false # (Optional, Multiple) Add a command-specific CLI parameter. Can be called multiple times
|
152
|
-
|
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
|
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
|
155
|
-
|
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.
|
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.
|
158
|
-
"echo #{params[:force].to_s}__YESSS!!!"
|
159
|
-
end
|
160
|
-
|
161
|
-
action do |params, args, global_opts, config| # (Required unless `extern` defined) Block to execute if the command is called.
|
162
|
-
Rbcli::log.info { 'These logs can go to STDERR, STDOUT, or a file' } # Example log. Interface is identical to Ruby's logger
|
163
|
-
puts "\nArgs:\n#{args}" # Arguments that came after the command on the CLI (i.e.: `mytool test bar baz` will yield args=['bar', 'baz'])
|
164
|
-
puts "Params:\n#{params}" # Parameters, as described through the option statements above
|
165
|
-
puts "Global opts:\n#{global_opts}" # Global Parameters, as descirbed in the Configurate section
|
166
|
-
puts "Config:\n#{config}" # Config file values
|
167
|
-
puts "LocalState:\n#{Rbcli.local_state}" # Local persistent state storage (when available) -- if unsure use Rbcli.local_state.nil?
|
168
|
-
puts "RemoteState:\n#{Rbcli.remote_state}" # Remote persistent state storage (when available) -- if unsure use Rbcli.remote_state.nil?
|
169
|
-
puts "\nDone!!!"
|
170
|
-
end
|
171
|
-
end
|
172
|
-
```
|
77
|
+
To create a new scripted command called `bar`, run:
|
173
78
|
|
174
|
-
|
175
|
-
|
176
|
-
```ruby
|
177
|
-
Rbcli.parse # Parse CLI and execute
|
79
|
+
```bash
|
80
|
+
rbcli script -n bar
|
178
81
|
```
|
179
82
|
|
180
|
-
|
181
|
-
|
182
|
-
RBCli takes actions in a specific order when `parse` is run.
|
183
|
-
|
184
|
-
1. The default config is loaded
|
185
|
-
2. The user config is loaded if present
|
186
|
-
3. CLI is parsed for global options, which are then stored in a hash and passed on to the other parts of the code. Built-in options, however, may cause the code to branch and exit.
|
187
|
-
4. The CLI is parsed for a command.
|
188
|
-
a. If no command is entered, RBCLI will check if a `default_acction` block has been provided.
|
189
|
-
i. If the block is defined, execute it
|
190
|
-
ii. Otherwise, show the help
|
191
|
-
b. If a command has been entered, the rest of the CLI is parsed for parameters and lineitems, and the code block for the command is called
|
192
|
-
|
193
|
-
|
194
|
-
## Configuration Files
|
195
|
-
|
196
|
-
RBCli has two chains to manage configuration: the __defaults chain__ and the __user chain__.
|
197
|
-
|
198
|
-
### Defaults chain
|
83
|
+
You will then find two new files:
|
199
84
|
|
200
|
-
The
|
85
|
+
* The command declaration under `application/commands/bar.rb`
|
86
|
+
* The script code under `application/commands/scripts/bar.sh`
|
201
87
|
|
202
|
-
|
203
|
-
* You can store your defaults in one or more YAML files and RBCli will import and combine them. Note that when generating the user config, RBCli will use the YAML text as-is, so comments are transferred as well. This allows you to write descriptions for the options directly in the file that the user can see.
|
204
|
-
* This is good for tools with large or complex configuration that needs user documentation written inline
|
205
|
-
* `config_defaults "<filename>"` in the DSL
|
206
|
-
* DSL Statements
|
207
|
-
* In the DSL, you can specify options individually by providing a name, description, and default value
|
208
|
-
* This is good for simpler configuration, as the descriptions provided are written out as comments in the generated user config
|
209
|
-
* `config_default :name, description: '<description_text>', default: <default_value>` in the DSL
|
210
|
-
|
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`.
|
212
|
-
|
213
|
-
### User chain
|
88
|
+
Edit the script to write your code.
|
214
89
|
|
215
|
-
|
90
|
+
### External Commands
|
216
91
|
|
217
|
-
|
92
|
+
To create a new external command called `baz`, run:
|
218
93
|
|
219
|
-
|
220
|
-
|
221
|
-
b. (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.
|
222
|
-
2. The location specified on the command line using the `-c <filename>` option
|
223
|
-
b. `yourclitool -c localfile.yml`
|
224
|
-
|
225
|
-
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`.
|
226
|
-
|
227
|
-
|
228
|
-
## Logging
|
229
|
-
|
230
|
-
Logging with RBCli is straightforward - it looks at the config file for logging settings, and instantiates a single, globally accessible [Logger](https://ruby-doc.org/stdlib-2.4.0/libdoc/logger/rdoc/Logger.html)` object. You can access it as such:
|
231
|
-
|
232
|
-
```ruby
|
233
|
-
Rbcli::log.info { 'These logs can go to STDERR, STDOUT, or a file' }
|
234
|
-
```
|
235
|
-
|
236
|
-
Default values can be set in the configurate DSL:
|
237
|
-
|
238
|
-
```ruby
|
239
|
-
log_level :info # (Optional) Set the default log_level for users. 0-5, or DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
|
240
|
-
log_target 'stderr' # (Optional) Set the target for logs. Valid values are STDOUT, STDERR, or a file path (as strings)
|
241
|
-
```
|
242
|
-
|
243
|
-
Two configuration options will always be placed in a user's YAML file to override defaults:
|
244
|
-
|
245
|
-
```yaml
|
246
|
-
# Log Settings
|
247
|
-
logger:
|
248
|
-
log_level: warn # 0-5, or DEBUG < INFO < WARN < ERROR < FATAL < UNKNOWN
|
249
|
-
log_target: stderr # STDOUT, STDERR, or a file path
|
94
|
+
```bash
|
95
|
+
rbcli extern -n baz
|
250
96
|
```
|
251
97
|
|
98
|
+
You will then find the command code in `application/commands/baz.rb`.
|
252
99
|
|
253
|
-
|
254
|
-
|
255
|
-
```ruby
|
256
|
-
Rbcli::Configurate.storage do
|
257
|
-
local_state '/var/mytool/localstate', force_creation: true, halt_on_error: true # (Optional) Creates a hash that is automatically saved to a file locally for state persistance. It is accessible to all commands at Rbcli.local_state[:yourkeyhere]
|
258
|
-
remote_state_dynamodb table_name: 'mytable', region: 'us-east-1', force_creation: true, halt_on_error: true, locking: true # (Optional) Creates a hash that is automatically saved to a DynamoDB table. It is recommended to keep halt_on_error=true when using a shared state.
|
259
|
-
end
|
260
|
-
```
|
100
|
+
Use one of the two provided modes -- direct path mode or variable path mode -- to provide the path to the external program.
|
261
101
|
|
262
|
-
### <a name="local_state"></a> Local State
|
263
102
|
|
264
|
-
|
103
|
+
## Hooks
|
265
104
|
|
266
|
-
|
105
|
+
RBCli has several hooks that run at different points in the exectution chain. They can be created via the `rbcli` command line tool:
|
267
106
|
|
268
|
-
```
|
269
|
-
|
107
|
+
```bash
|
108
|
+
rbcli hook --default # Runs when no command is provided
|
109
|
+
rbcli hook --pre # Runs before any command
|
110
|
+
rbcli hook --post # Runs after any command
|
111
|
+
rbcli hook --firstrun # Runs the first time a user runs your application. Requires userspace config.
|
112
|
+
rbcli hook -dpof # Create all hooks at once
|
270
113
|
```
|
271
114
|
|
272
|
-
|
115
|
+
## Storage
|
273
116
|
|
274
|
-
Hash
|
117
|
+
RBCli supports both local and remote state storage. This is done by synchronizing a Hash with either the local disk or a remote database.
|
275
118
|
|
276
|
-
|
277
|
-
* `[]=` (Assignment operator)
|
278
|
-
* `delete`
|
279
|
-
* `each`
|
280
|
-
* `key?`
|
119
|
+
### Local State
|
281
120
|
|
282
|
-
|
121
|
+
RBCli can provide you with a unique hash that can be persisted to disk on any change to a top-level value.
|
283
122
|
|
284
|
-
|
285
|
-
* 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`.
|
286
|
-
* `clear`
|
287
|
-
* Resets the data back to an empty hash.
|
288
|
-
* `refresh`
|
289
|
-
* Loads the most current version of the data from the disk
|
290
|
-
* `disconnect`
|
291
|
-
* Removes the data from memory and sets `Rbcli.local_state = nil`. Data will be read from disk again on next access.
|
123
|
+
Enable local state in `config/storage.rb`.
|
292
124
|
|
125
|
+
Then access it in your Standard Commands with `Rbcli.local_state[:yourkeyhere]`.
|
293
126
|
|
294
|
-
|
127
|
+
### Remote State
|
295
128
|
|
296
|
-
|
129
|
+
Similar to the Local State above, RBCli can provide you with a unique hash that can be persisted to a remote storage location.
|
297
130
|
|
298
|
-
|
299
|
-
Rbcli::Configurate.storage do
|
300
|
-
local_state '/var/mytool/localstate', force_creation: true, halt_on_error: true # (Optional) Creates a hash that is automatically saved to a file locally for state persistance. It is accessible to all commands at Rbcli.local_state[:yourkeyhere]
|
301
|
-
end
|
302
|
-
```
|
131
|
+
Currently only AWS DynamoDB is supported, and credentials will be required for each user.
|
303
132
|
|
304
|
-
|
305
|
-
* The `path` as a string (self-explanatory)
|
306
|
-
* `force_creation`
|
307
|
-
* This will attempt to create the path and file if it does not exist (equivalent to an `mkdir -p` and `touch` in linux)
|
308
|
-
* `halt_on_error`
|
309
|
-
* RBCli's default behavior is to raise an exception if the file can not be created, read, or updated at any point in time
|
310
|
-
* If this is set to `false`, RBCli will silence any errors pertaining to file access and will fall back to whatever data is available. Note that if this is enabled, changes made to the state may not be persisted to disk.
|
311
|
-
* If creation fails and file does not exist, you start with an empty hash
|
312
|
-
* If file exists but can't be read, you will have an empty hash
|
313
|
-
* If file can be read but not written, the hash will be populated with the data. Writes will be stored in memory while the application is running, but will not be persisted to disk.
|
133
|
+
Enable remote state in `config/storage.rb`.
|
314
134
|
|
315
|
-
|
135
|
+
Then access it in your Standard Commands with `Rbcli.remote_state[:yourkeyhere]`.
|
316
136
|
|
317
|
-
RBCli's remote state storage gives you access to a hash that is automatically persisted to a remote storage location when changes are made. It has locking built-in, meaning that multiple users may share remote state without any data consistency issues!
|
318
137
|
|
319
|
-
|
138
|
+
## Userspace Configuration Files
|
320
139
|
|
321
|
-
|
322
|
-
Rbcli.remote_state[:yourkeyhere]
|
323
|
-
```
|
140
|
+
RBCli provides an easy mechanism to generate and read configuration files from your users. You set the default values and help text with the __defaults chain__, and leverage the __user chain__ to read them.
|
324
141
|
|
325
|
-
|
142
|
+
You can set defaults either by placing a YAML file in the `userconf/` folder or by specifying individual options in `application/options.rb` (global) or `application/command/*.rb` (command-specific).
|
326
143
|
|
327
|
-
|
144
|
+
Users can generate a config file, complete with help text, by running your tool with the `--generate-config` option.
|
328
145
|
|
329
|
-
#### DynamoDB Configuration
|
330
146
|
|
331
|
-
|
332
|
-
|
333
|
-
1. User's config file
|
334
|
-
2. Environment variables `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY`
|
335
|
-
3. User's AWSCLI configuration at `~/.aws/credentials`
|
147
|
+
## Logging
|
336
148
|
|
337
|
-
|
149
|
+
RBCli's logger is configured in `config/logging.rb`.
|
338
150
|
|
339
151
|
```ruby
|
340
|
-
|
341
|
-
|
342
|
-
end
|
152
|
+
log_level :info
|
153
|
+
log_target 'stderr'
|
343
154
|
```
|
344
155
|
|
345
|
-
|
346
|
-
* `table_name`
|
347
|
-
* The name of the DynamoDB table to use.
|
348
|
-
* `region`
|
349
|
-
* The AWS region that the database is located
|
350
|
-
* `force_creation`
|
351
|
-
* Creates the DynamoDB table if it does not already exist
|
352
|
-
* `halt_on_error`
|
353
|
-
* Similar to the way [Local State](#local_state) works, setting this to `false` will silence any errors in connecting to the DynamoDB table. Instead, your application will simply have access to an empty hash that does not get persisted anywhere.
|
354
|
-
* This is good for use cases that involve using this storage as a cache to "pick up where you left off in case of failure", where a connection error might mean the feature doesn't work but its not important enough to interrupt the user.
|
355
|
-
* `locking`
|
356
|
-
* This enables locking, for when you are sharing state between different instances of the application. For more information see the [section below](#distributed_locking).
|
357
|
-
|
358
|
-
#### <a name="distributed_locking">Distributed Locking and State Sharing
|
359
|
-
|
360
|
-
Distributed Locking allows a remote state to be shared among multiple users of the application without risk of data corruption. To use it, simply set the `locking:` parameter to `true` when enabling remote state (see above).
|
361
|
-
|
362
|
-
This is how locking works:
|
363
|
-
|
364
|
-
1. The application attempts to acquire a lock on the remote state when you first access it
|
365
|
-
2. If the backend is locked by a different application, wait and try again
|
366
|
-
3. If it succeeds, the lock is held and refreshed periodically
|
367
|
-
4. When the application exits, the lock is released
|
368
|
-
5. If the application does not refresh its lock, or fails to release it when it exits, the lock will automatically expire within 60 seconds
|
369
|
-
6. If another application steals the lock (unlikely but possible), and the application tries to save data, a `StandardError` will be thrown
|
370
|
-
7. You can manually attempt to lock/unlock by calling `Rbcli.remote_state.lock` or `Rbcli.remote_state.unlock`, respectively.
|
371
|
-
|
372
|
-
##### Auto-locking vs Manual Locking
|
373
|
-
|
374
|
-
Remember: all state in Rbcli is lazy-loaded. Therefore, RBCli wll only attempt to lock the data when you first try to access it. If you need to make sure that the data is locked before executing a block of code, use:
|
156
|
+
Then it can be accessed when writing your commands via:
|
375
157
|
|
376
158
|
```ruby
|
377
|
-
Rbcli.
|
159
|
+
Rbcli::log.info { 'These logs can go to STDERR, STDOUT, or a file' }
|
378
160
|
```
|
379
161
|
|
380
|
-
to
|
381
|
-
|
382
|
-
```ruby
|
383
|
-
Rbcli.remote_state.disconnect
|
384
|
-
```
|
162
|
+
The user will also be able to change the log level and target via their config file, if it is enabled.
|
385
163
|
|
386
164
|
|
387
165
|
## Automatic Update Check
|
388
166
|
|
389
|
-
RBCli can automatically notify users when an update is available.
|
390
|
-
|
391
|
-
Two sources are currently supported: Github (including Enterprise) and RubyGems.
|
392
|
-
|
393
|
-
### GitHub Update Check
|
394
|
-
|
395
|
-
The GitHub update check works best when paired with GitHub's best practices on releases. See here: https://help.github.com/articles/creating-releases/
|
396
|
-
|
397
|
-
RBCli will check your github repo's tags and compare that version number with one you configured Rbcli with.
|
398
|
-
|
399
|
-
```ruby
|
400
|
-
autoupdate github_repo: 'akhoury6/rbcli', access_token: nil, enterprise_hostname: nil, force_update: false # (Optional) Check for updates to this application at a GitHub repo. The repo must use version number tags in accordance to best practices: https://help.github.com/articles/creating-releases/
|
401
|
-
```
|
402
|
-
The `github_repo` should point to the repo using the `user/repo` syntax.
|
403
|
-
|
404
|
-
The `access_token` can be overridden by the user via their configuration file, so it can be left as `nil`. The token is not needed at all if using a public repo. For instructions on generating a new access token, see [here](https://help.github.com/articles/creating-a-personal-access-token-for-the-command-line/).
|
405
|
-
|
406
|
-
The `enterprise_hostname` setting allows you to point RBCli at a local GitHub Enterprise server.
|
407
|
-
|
408
|
-
Setting `force_update: true` will halt execution if an update is available, forcing the user to update.
|
409
|
-
|
410
|
-
### Rubygems.org Update Check
|
411
|
-
|
412
|
-
The Rubygems update check will check if there is a new published version of the gem on Rubygems.org. The latest published version is compared to the version number you configured RBCli with.
|
413
|
-
|
414
|
-
```ruby
|
415
|
-
autoupdate gem: 'rbcli', force_update: false # (Optional) Check for updates to this application through RubyGems.org.
|
416
|
-
```
|
417
|
-
|
418
|
-
The `gem` parameter should simply state the name of the gem.
|
419
|
-
|
420
|
-
Setting `force_update: true` will halt execution if an update is available, forcing the user to update.
|
421
|
-
|
422
|
-
|
423
|
-
## External Script Wrapping
|
424
|
-
|
425
|
-
RBCli has the ability to run an external application as a CLI command, passing CLI parameters and environment variables as desired. It provides two modes -- __direct path__ and __variable path__ -- which work similarly through the `extern` keyword.
|
426
|
-
|
427
|
-
When an external script is defined in a command, an `action` is no longer required.
|
428
|
-
|
429
|
-
To quickly generate a script that shows the environment variables passed to it, you can use RBCli's own tool: `rbcli script`
|
430
|
-
|
431
|
-
### Direct Path Mode
|
432
|
-
|
433
|
-
```ruby
|
434
|
-
class Test < Rbcli::Command # Declare a new command by subclassing Rbcli::Command
|
435
|
-
description 'This is a short description.' # (Required) Short description for the global help
|
436
|
-
usage 'This is some really long usage text description!' # (Required) Long description for the command-specific help
|
437
|
-
parameter :force, 'Force testing', type: :boolean, default: false, required: false # (Optional, Multiple) Add a command-specific CLI parameter. Can be called multiple times
|
438
|
-
|
439
|
-
extern path: 'env | grep "^__PARAMS\|^__ARGS\|^__GLOBAL\|^__CONFIG\|^MYVAR"', envvars: {MYVAR: 'some_value'} # (Required unless `action` defined) Runs a given application, with optional environment variables, when the user runs the command.
|
440
|
-
end
|
441
|
-
```
|
167
|
+
RBCli can automatically notify users when an update is available. Two sources are currently supported: Github (including Enterprise) and RubyGems.
|
442
168
|
|
443
|
-
|
169
|
+
You can configure automatic updates in `config/autoupdate.rb` in your project.
|
444
170
|
|
445
|
-
RBCli will automatically set several environment variables as well. As you may have guessed by the example above, they are prefixed with:
|
446
171
|
|
447
|
-
|
448
|
-
* `__ARGS`
|
449
|
-
* `__GLOBAL`
|
450
|
-
* `__CONFIG`
|
451
|
-
|
452
|
-
These prefixes are applied to their respective properties in RBCli, similar to what you would see when using an `action`.
|
453
|
-
|
454
|
-
The command in the example above will show you a list of variables, which should look something like this:
|
455
|
-
|
456
|
-
```bash
|
457
|
-
__GLOBAL_VERSION=false
|
458
|
-
__GLOBAL_HELP=false
|
459
|
-
__GLOBAL_GENERATE_CONFIG=false
|
460
|
-
__GLOBAL_CONFIG_FILE="/etc/mytool/config.yml"
|
461
|
-
__CONFIG_LOGGER={"log_level":"info","log_target":"stderr"}
|
462
|
-
__CONFIG_MYOPT=true
|
463
|
-
__CONFIG_GITHUB_UPDATE={"access_token":null,"enterprise_hostname":null}
|
464
|
-
__PARAMS_FORCE=false
|
465
|
-
__PARAMS_HELP=false
|
466
|
-
MYVAR=some_value
|
467
|
-
```
|
468
|
-
|
469
|
-
As you can see above, items which have nested values they are passed in as JSON. If you need to parse them, [JQ](https://stedolan.github.io/jq/) is recommended.
|
470
|
-
|
471
|
-
### Variable Path Mode
|
472
|
-
|
473
|
-
Variable Path Mode works the same as Direct Path Mode, only instead of providing a string we provide a block that returns a string. This allows us to generate different commands based on the CLI parameters that the user passed, or pass configuration as CLI parameters to the external application:
|
474
|
-
|
475
|
-
```ruby
|
476
|
-
class Test < Rbcli::Command # Declare a new command by subclassing Rbcli::Command
|
477
|
-
description 'This is a short description.' # (Required) Short description for the global help
|
478
|
-
usage 'This is some really long usage text description!' # (Required) Long description for the command-specific help
|
479
|
-
parameter :force, 'Force testing', type: :boolean, default: false, required: false # (Optional, Multiple) Add a command-specific CLI parameter. Can be called multiple times
|
480
|
-
|
481
|
-
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.
|
482
|
-
if params[:force]
|
483
|
-
"externalapp --test-script foo --ignore-errors"
|
484
|
-
else
|
485
|
-
"externalapp"
|
486
|
-
end
|
487
|
-
end
|
488
|
-
end
|
489
|
-
```
|
490
|
-
|
491
|
-
## Project Structure and Generators
|
492
|
-
|
493
|
-
RBCli supports using predefined project structure, helping to organize all of the options and commands that you may use. It also
|
494
|
-
|
495
|
-
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.
|
496
|
-
|
497
|
-
The folder structure is as follows:
|
498
|
-
|
499
|
-
```
|
500
|
-
<projectname>
|
501
|
-
|
|
502
|
-
|--- application
|
503
|
-
| |
|
504
|
-
| |--- commands
|
505
|
-
| |
|
506
|
-
| |---scripts
|
507
|
-
|
|
508
|
-
|--- config
|
509
|
-
|--- default_user_configs
|
510
|
-
|--- exe
|
511
|
-
|--- hooks
|
512
|
-
|--- spec
|
513
|
-
```
|
514
|
-
|
515
|
-
It is highly recommended to __not__ create files in these folders manually, and to use the RBCli generators instead:
|
516
|
-
|
517
|
-
```shell
|
518
|
-
rbcli command -n <name>
|
519
|
-
rbcli script -n <name>
|
520
|
-
rbcli userconf -n <name>
|
521
|
-
rbcli hook --default # or rbcli hook -d
|
522
|
-
rbcli hook --pre # or rbcli hook -p
|
523
|
-
rbcli hook --post # or rbcli hook -o
|
524
|
-
rbcli hook --firstrun # or rbcli hook -f
|
525
|
-
rbcli hook -dpof # all hooks at once
|
526
|
-
```
|
527
|
-
|
528
|
-
That said, this readme will provide you with the information required to do things manually if you so desire. More details on generators later.
|
529
|
-
|
530
|
-
|
531
|
-
## Development
|
532
|
-
|
533
|
-
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
534
|
-
|
535
|
-
To install this gem onto your local machine from source, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
536
|
-
|
537
|
-
|
538
|
-
## Contributing
|
539
|
-
|
540
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/akhoury6/rbcli. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
172
|
+
## Development and Contributing
|
541
173
|
|
174
|
+
For more information about development and contributing, please see the [Official Development Documentation][documentation_development]
|
542
175
|
|
543
176
|
## License
|
544
177
|
|
545
|
-
The gem is available as open source under the terms of the [
|
178
|
+
The gem is available as open source under the terms of the [GPLv3](https://github.com/akhoury6/rbcli/blob/master/CODE_OF_CONDUCT.md).
|
179
|
+
|
180
|
+
## Full Documentation
|
546
181
|
|
182
|
+
[You can find the Official Documentation for RBCli Here.][documentation_home]
|
547
183
|
|
548
|
-
## Code of Conduct
|
549
184
|
|
550
|
-
|
185
|
+
[documentation_home]: https://akhoury6.github.com/rbcli
|
186
|
+
[documentation_development]: https://akhoury6.github.com/rbcli/development/contributing/
|
187
|
+
[license_text]: https://github.com/akhoury6/rbcli/blob/master/LICENSE.txt
|
data/bin/console
CHANGED
@@ -1,4 +1,23 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
##################################################################################
|
3
|
+
# RBCli -- A framework for developing command line applications in Ruby #
|
4
|
+
# Copyright (C) 2018 Andrew Khoury #
|
5
|
+
# #
|
6
|
+
# This program is free software: you can redistribute it and/or modify #
|
7
|
+
# it under the terms of the GNU General Public License as published by #
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or #
|
9
|
+
# (at your option) any later version. #
|
10
|
+
# #
|
11
|
+
# This program is distributed in the hope that it will be useful, #
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
14
|
+
# GNU General Public License for more details. #
|
15
|
+
# #
|
16
|
+
# You should have received a copy of the GNU General Public License #
|
17
|
+
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
18
|
+
# #
|
19
|
+
# For questions regarding licensing, please contact andrew@blacknex.us #
|
20
|
+
##################################################################################
|
2
21
|
|
3
22
|
require "bundler/setup"
|
4
23
|
require "rbcli"
|