rbcli 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +66 -6
- data/examples/mytool +15 -1
- data/lib/rbcli/configurate.rb +16 -3
- data/lib/rbcli/engine/parser.rb +15 -9
- data/lib/rbcli/stateful_systems/configuratestorage.rb +24 -0
- data/lib/rbcli/stateful_systems/localstorage/localstate.rb +124 -0
- data/lib/rbcli/version.rb +1 -1
- data/lib/rbcli.rb +6 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '091cd6eaa815d90823dbb657bc8307322bc0b272a83cf372eaa68456c65aa504'
|
4
|
+
data.tar.gz: ac8bc103360d5748382020bf796ee11adb1a14c6bb4147158ad15030582b4371
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2229c9330274945480d37d3547faa7c22b5079ebbc89f880f028859b6d906726ce69b1330302fd8e38b88532c73c0d2fffb1c2be97e14f562c2c847bb46cd94
|
7
|
+
data.tar.gz: c3239439910f6f2ea594c426f94889eb18be945ea2ae3a335915200e20e217b61b5c396a3639af42b6737810e75c969c45ee31f1563653dff681e0b6426078b3
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -10,8 +10,11 @@ Some of its key features include:
|
|
10
10
|
|
11
11
|
* __Config File Generation__: Easily piece together a default configuration even with declarations in different parts of the code. Then the user can generate their own configuration, and it gets stored in whatever location you'd like.
|
12
12
|
|
13
|
+
* __Multiple Hooks and Entry Points__: Define commands, pre-execution hooks, post-execution hooks, and first_run hooks to quickly and easily customize the flow of your application code.
|
14
|
+
|
13
15
|
* __Logging__: Keep track of all instances of your tool through logging. Logs can go to STDOUT, STDERR, or a given file, making them compatible with log aggregators such as Splunk, Logstash, and many others.
|
14
16
|
|
17
|
+
* __Local State Storage__: Easily manage a set of data that persists between runs. You get access to a hash that is automatically kept in-sync with a file on disk.
|
15
18
|
|
16
19
|
## Installation
|
17
20
|
|
@@ -46,9 +49,10 @@ Note that all options and parameters will have both a short and long version of
|
|
46
49
|
|
47
50
|
## Getting Started
|
48
51
|
|
49
|
-
Creating a new skeleton command is as easy as running `rbcli init <filename>`. It will have
|
52
|
+
Creating a new skeleton command is as easy as running `rbcli init <filename>`. It will have these key items:
|
50
53
|
|
51
54
|
* The configuration
|
55
|
+
* Storage subsystem configuration (optional)
|
52
56
|
* A command declaration
|
53
57
|
* The parse command
|
54
58
|
|
@@ -70,7 +74,7 @@ Rbcli::configurate do
|
|
70
74
|
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
|
71
75
|
config_default :myopt, description: 'Testing this', value: true # (Optional, Multiple) Specify an individual configuration parameter and set a default value. These will also be included in generated user config
|
72
76
|
|
73
|
-
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.
|
77
|
+
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.
|
74
78
|
|
75
79
|
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)
|
76
80
|
puts "Hello, #{opts[:name]}."
|
@@ -84,6 +88,10 @@ option :name, 'Give me your name', type: :string, default: 'Foo', required: fals
|
|
84
88
|
post_hook do |opts| # (Optional) Allows providing a block of code that runs after any command
|
85
89
|
puts 'This is a post-command hook. It executes after the command.'
|
86
90
|
end
|
91
|
+
|
92
|
+
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.
|
93
|
+
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."
|
94
|
+
end
|
87
95
|
end
|
88
96
|
```
|
89
97
|
|
@@ -91,10 +99,10 @@ end
|
|
91
99
|
|
92
100
|
For the `option` parameters that you want to create, the following types are supported:
|
93
101
|
|
94
|
-
*
|
95
|
-
*
|
96
|
-
*
|
97
|
-
*
|
102
|
+
* `:string`
|
103
|
+
* `:boolean` or `:flag`
|
104
|
+
* `:integer`
|
105
|
+
* `:float`
|
98
106
|
|
99
107
|
If a default value is not set, it will default to `nil`.
|
100
108
|
|
@@ -103,6 +111,16 @@ If you want to declare more than one option, you can call it multiple times. The
|
|
103
111
|
Once parsed, options will be placed in a hash where they can be accessed via their names as shown above. You can see this demonstrated in the `default_action`, `pre_hook`, and `post_hook` blocks.
|
104
112
|
|
105
113
|
|
114
|
+
### Storage Configuration (optional)
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
Rbcli::Configurate.storage do
|
118
|
+
local_state '/var/mytool/localstate', force_creation: true, ignore_file_errors: false # (Optional) Creates a hash that is automatically saved to a file locally for state persistance. It is accessible to all commands at Rbcli.localstate[:yourkeyhere]
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
This block configures different storage interfaces. For more details please see the [Storage Subsystems](#storage_subsystems) section below.
|
123
|
+
|
106
124
|
### Command Declaration
|
107
125
|
|
108
126
|
Commands are added by subclassing `Rbcli::Command`. There are a few parameters to set for it, which get used by the different components of Rbcli.
|
@@ -203,6 +221,48 @@ logger:
|
|
203
221
|
log_target: stderr # STDOUT, STDERR, or a file path
|
204
222
|
```
|
205
223
|
|
224
|
+
## <a name="storage_subsystems"></a>Storage Subsystems
|
225
|
+
|
226
|
+
```ruby
|
227
|
+
Rbcli::Configurate.storage do
|
228
|
+
local_state '/var/mytool/localstate', force_creation: true, ignore_file_errors: false # (Optional) Creates a hash that is automatically saved to a file locally for state persistance. It is accessible to all commands at Rbcli.localstate[:yourkeyhere]
|
229
|
+
end
|
230
|
+
```
|
231
|
+
|
232
|
+
### Local State
|
233
|
+
|
234
|
+
RBCli's local state storage gives you access to a hash that is automatically persisted to disk when changes are made.
|
235
|
+
|
236
|
+
Once configured you can access it with a standard hash syntax:
|
237
|
+
|
238
|
+
```ruby
|
239
|
+
Rbcli.localstate[:yourkeyhere]
|
240
|
+
```
|
241
|
+
|
242
|
+
For performance reasons, the only methods available for use are `=` (assignment operator), `delete`, `each`, and `key?`. Also, the `clear` method has been added, which resets the data back to an empty hash. Keys are accessed via either symbols or strings indifferently.
|
243
|
+
|
244
|
+
Every assignment will result in a write to disk, so if an operation will require a large number of assignments/writes it should be performed to a different hash before beign assigned to this one.
|
245
|
+
|
246
|
+
#### Configuration Parameters
|
247
|
+
|
248
|
+
```ruby
|
249
|
+
Rbcli::Configurate.storage do
|
250
|
+
local_state '/var/mytool/localstate', force_creation: true, ignore_file_errors: false # (Optional) Creates a hash that is automatically saved to a file locally for state persistance. It is accessible to all commands at Rbcli.localstate[:yourkeyhere]
|
251
|
+
end
|
252
|
+
```
|
253
|
+
|
254
|
+
There are three parameters to configure it with:
|
255
|
+
* The `path` as a string (self-explanatory)
|
256
|
+
* `force_creation`
|
257
|
+
* This will attempt to create the path and file if it does not exist (equivalent to an `mkdir -p` and `touch` in linux)
|
258
|
+
* `ignore_file_errors`
|
259
|
+
* RBCli's default behavior is to raise an exception if the file can not be created, read, or updated at any point in time
|
260
|
+
* If this is set to `true`, 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.
|
261
|
+
* If creation fails and file does not exist, you start with an empty hash
|
262
|
+
* If file exists but can't be read, you will have an empty hash
|
263
|
+
* 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.
|
264
|
+
|
265
|
+
|
206
266
|
## Development
|
207
267
|
|
208
268
|
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.
|
data/examples/mytool
CHANGED
@@ -8,7 +8,7 @@ require 'rbcli'
|
|
8
8
|
# This block is where rbcli is configured.
|
9
9
|
#########################
|
10
10
|
|
11
|
-
Rbcli::
|
11
|
+
Rbcli::Configurate.me do
|
12
12
|
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.
|
13
13
|
version '0.1.0' # (Required) The version number
|
14
14
|
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.
|
@@ -34,6 +34,20 @@ Rbcli::configurate do
|
|
34
34
|
post_hook do |opts| # (Optional) Allows providing a block of code that runs after any command
|
35
35
|
puts 'This is a post-command hook. It executes after the command.'
|
36
36
|
end
|
37
|
+
|
38
|
+
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.
|
39
|
+
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."
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
###############################
|
44
|
+
## State Configuration Block ##
|
45
|
+
###############################
|
46
|
+
# The state-related componets
|
47
|
+
# are configured here.
|
48
|
+
###############################
|
49
|
+
Rbcli::Configurate.storage do
|
50
|
+
local_state '/var/mytool/localstate', force_creation: true, ignore_file_errors: false # (Optional) Creates a hash that is automatically saved to a file locally for state persistance. It is accessible to all commands at Rbcli.localstate[:yourkeyhere]
|
37
51
|
end
|
38
52
|
|
39
53
|
#########################
|
data/lib/rbcli/configurate.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
module Rbcli
|
1
|
+
module Rbcli::Configurate
|
2
2
|
|
3
3
|
@data = {
|
4
4
|
scriptname: nil,
|
@@ -9,10 +9,12 @@ module Rbcli
|
|
9
9
|
options: {},
|
10
10
|
default_action: nil,
|
11
11
|
pre_hook: nil,
|
12
|
-
post_hook: nil
|
12
|
+
post_hook: nil,
|
13
|
+
first_run: nil,
|
14
|
+
halt_after_first_run: false
|
13
15
|
}
|
14
16
|
|
15
|
-
def self.
|
17
|
+
def self.me &block
|
16
18
|
@self_before_instance_eval = eval "self", block.binding
|
17
19
|
instance_eval &block
|
18
20
|
end
|
@@ -84,6 +86,11 @@ module Rbcli
|
|
84
86
|
@data[:post_hook] = block
|
85
87
|
end
|
86
88
|
|
89
|
+
def self.first_run halt_after_running: false, &block
|
90
|
+
@data[:halt_after_first_run] = halt_after_running
|
91
|
+
@data[:first_run] = block
|
92
|
+
end
|
93
|
+
|
87
94
|
##
|
88
95
|
# Data Retrieval
|
89
96
|
##
|
@@ -91,4 +98,10 @@ module Rbcli
|
|
91
98
|
@data
|
92
99
|
end
|
93
100
|
|
101
|
+
end
|
102
|
+
|
103
|
+
module Rbcli
|
104
|
+
def self.configuration
|
105
|
+
Rbcli::Configurate::configuration
|
106
|
+
end
|
94
107
|
end
|
data/lib/rbcli/engine/parser.rb
CHANGED
@@ -55,18 +55,24 @@ Commands:
|
|
55
55
|
|
56
56
|
end
|
57
57
|
|
58
|
-
def self.opts
|
59
|
-
@cliopts
|
60
|
-
end
|
61
|
-
|
62
|
-
def self.cmd
|
63
|
-
@cmd
|
64
|
-
end
|
65
|
-
|
66
58
|
end
|
67
59
|
|
68
60
|
module Rbcli
|
69
61
|
def self.parse
|
70
|
-
Rbcli
|
62
|
+
if Rbcli.configuration[:first_run]
|
63
|
+
if Rbcli.local_state
|
64
|
+
if Rbcli.local_state.rbclidata.key? :first_run
|
65
|
+
Rbcli::Parser::parse
|
66
|
+
else
|
67
|
+
Rbcli.configuration[:first_run].call
|
68
|
+
Rbcli.local_state.set_rbclidata :first_run, true
|
69
|
+
Rbcli::Parser::parse unless Rbcli.configuration[:halt_after_first_run]
|
70
|
+
end
|
71
|
+
else
|
72
|
+
raise StandardError.new "Error: Can not use `first_run` without also configuring `local_state`."
|
73
|
+
end
|
74
|
+
else
|
75
|
+
Rbcli::Parser::parse
|
76
|
+
end
|
71
77
|
end
|
72
78
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Rbcli::Configurate
|
2
|
+
def self.storage &block
|
3
|
+
Rbcli::ConfigurateStorage.configure &block
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
module Rbcli::ConfigurateStorage
|
9
|
+
@data = {
|
10
|
+
localstate: nil
|
11
|
+
}
|
12
|
+
|
13
|
+
def self.configure &block
|
14
|
+
@self_before_instance_eval = eval "self", block.binding
|
15
|
+
instance_eval &block
|
16
|
+
end
|
17
|
+
|
18
|
+
##
|
19
|
+
# Data Retrieval
|
20
|
+
##
|
21
|
+
def self.data
|
22
|
+
@data
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
## Configuration Interface
|
5
|
+
module Rbcli::ConfigurateStorage
|
6
|
+
@data[:localstate] = nil
|
7
|
+
|
8
|
+
def self.local_state path, force_creation: false, ignore_file_errors: false
|
9
|
+
@data[:localstate] = Rbcli::LocalState.new path, force_creation: force_creation, ignore_file_errors: ignore_file_errors
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
## User Interface
|
14
|
+
module Rbcli
|
15
|
+
def self.local_state
|
16
|
+
Rbcli::ConfigurateStorage.data[:localstate]
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
## Local State Class
|
22
|
+
class Rbcli::LocalState
|
23
|
+
|
24
|
+
def initialize path, force_creation: false, ignore_file_errors: false
|
25
|
+
@path = File.expand_path path
|
26
|
+
@ignore_file_errors = ignore_file_errors
|
27
|
+
|
28
|
+
base_data = {
|
29
|
+
data: {},
|
30
|
+
rbcli: {}
|
31
|
+
}
|
32
|
+
|
33
|
+
if File.exist? @path
|
34
|
+
load
|
35
|
+
elsif force_creation
|
36
|
+
create_file
|
37
|
+
@data = base_data
|
38
|
+
save
|
39
|
+
else
|
40
|
+
error "File #{@path} does not exist." unless @ignore_file_errors
|
41
|
+
@data = base_data
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def []= key, value
|
46
|
+
@data[:data][key.to_sym] = value
|
47
|
+
save
|
48
|
+
@data[:data][key.to_sym]
|
49
|
+
end
|
50
|
+
|
51
|
+
def [] key
|
52
|
+
@data[:data][key.to_sym]
|
53
|
+
end
|
54
|
+
|
55
|
+
def delete key, &block
|
56
|
+
result = @data[:data].delete key.to_sym, block
|
57
|
+
save
|
58
|
+
result
|
59
|
+
end
|
60
|
+
|
61
|
+
def clear
|
62
|
+
@data[:data] = {}
|
63
|
+
save
|
64
|
+
end
|
65
|
+
|
66
|
+
def each &block
|
67
|
+
@data[:data].each &block
|
68
|
+
save
|
69
|
+
end
|
70
|
+
|
71
|
+
def key? key
|
72
|
+
@data[:data].key? key.to_sym
|
73
|
+
end
|
74
|
+
|
75
|
+
def to_h
|
76
|
+
@data[:data]
|
77
|
+
end
|
78
|
+
|
79
|
+
# For internal use
|
80
|
+
|
81
|
+
def rbclidata key = nil
|
82
|
+
return @data[:rbcli][key.to_sym] unless key.nil?
|
83
|
+
@data[:rbcli]
|
84
|
+
end
|
85
|
+
|
86
|
+
def set_rbclidata key, value
|
87
|
+
@data[:rbcli][key.to_sym] = value
|
88
|
+
save
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def create_file
|
94
|
+
begin
|
95
|
+
FileUtils.mkdir_p File.dirname(@path)
|
96
|
+
FileUtils.touch @path
|
97
|
+
rescue Errno::EACCES => e
|
98
|
+
error "Can not create file #{@path}. Please make sure the directory is writeable." unless @ignore_file_errors
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def load
|
103
|
+
begin
|
104
|
+
@data = JSON.parse(File.read(@path)).deep_symbolize!
|
105
|
+
rescue Errno::ENOENT, Errno::EACCES => e
|
106
|
+
error "Can not read from file #{@path}. Please make sure the file exists and is readable." unless @ignore_file_errors
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def save
|
111
|
+
begin
|
112
|
+
File.write @path, JSON.dump(@data)
|
113
|
+
rescue Errno::ENOENT, Errno::EACCES => e
|
114
|
+
error "Can not write to file #{@path}. Please make sure the file exists and is writeable." unless @ignore_file_errors
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def error text
|
119
|
+
raise Rbcli::LocalStateError.new "Error accessing local state: #{text}"
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
class Rbcli::LocalStateError < StandardError; end
|
data/lib/rbcli/version.rb
CHANGED
data/lib/rbcli.rb
CHANGED
@@ -5,6 +5,12 @@ module Rbcli end # Empty module declaration required to declare submodules freel
|
|
5
5
|
require "rbcli/version"
|
6
6
|
|
7
7
|
require "rbcli/util"
|
8
|
+
|
9
|
+
# STATE TOOLS
|
10
|
+
require "rbcli/stateful_systems/configuratestorage"
|
11
|
+
require "rbcli/stateful_systems/localstorage/localstate"
|
12
|
+
# END STATE TOOLS
|
13
|
+
|
8
14
|
require "rbcli/configurate"
|
9
15
|
|
10
16
|
require "rbcli/engine/command"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rbcli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andrew Khoury
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-05-
|
11
|
+
date: 2018-05-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -105,6 +105,8 @@ files:
|
|
105
105
|
- lib/rbcli/configurate.rb
|
106
106
|
- lib/rbcli/engine/command.rb
|
107
107
|
- lib/rbcli/engine/parser.rb
|
108
|
+
- lib/rbcli/stateful_systems/configuratestorage.rb
|
109
|
+
- lib/rbcli/stateful_systems/localstorage/localstate.rb
|
108
110
|
- lib/rbcli/util.rb
|
109
111
|
- lib/rbcli/util/config.rb
|
110
112
|
- lib/rbcli/util/hash_deep_symbolize.rb
|