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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6123cc2792a91f94903c85d80bb940c0e759dd12c2d54f670ba29c660f522d5c
4
- data.tar.gz: 68093975766eb32942a94479bb1048feaa154b21b28781335c9215025bb7f5e6
3
+ metadata.gz: '091cd6eaa815d90823dbb657bc8307322bc0b272a83cf372eaa68456c65aa504'
4
+ data.tar.gz: ac8bc103360d5748382020bf796ee11adb1a14c6bb4147158ad15030582b4371
5
5
  SHA512:
6
- metadata.gz: 124b7849fe7ac522b201bb18f1f9c57ce8776e64300c3c72460f882c77b7fde45e7b673b3d9aeb886dc8f0bab7dee40ab1c134e27d6d799baba27d16c94d9f1d
7
- data.tar.gz: b60378421c9a11951be12290562fb02a97901c36f9abe78393178a1f2b1d225655654abf411310b2cd322a1d11b934dcb39c59cd7700aa009d7f2ad5218a21c5
6
+ metadata.gz: e2229c9330274945480d37d3547faa7c22b5079ebbc89f880f028859b6d906726ce69b1330302fd8e38b88532c73c0d2fffb1c2be97e14f562c2c847bb46cd94
7
+ data.tar.gz: c3239439910f6f2ea594c426f94889eb18be945ea2ae3a335915200e20e217b61b5c396a3639af42b6737810e75c969c45ee31f1563653dff681e0b6426078b3
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rbcli (0.1.2)
4
+ rbcli (0.1.3)
5
5
  colorize (~> 0.8)
6
6
  deep_merge (~> 1.2)
7
7
 
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 three key items:
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
- * :string
95
- * :boolean or :flag
96
- * :integer
97
- * :float
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::configurate do
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
  #########################
@@ -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.configurate &block
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
@@ -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::Parser::parse
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
@@ -1,3 +1,3 @@
1
1
  module Rbcli
2
- VERSION = "0.1.2"
2
+ VERSION = "0.1.3"
3
3
  end
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.2
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-25 00:00:00.000000000 Z
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