acclimate 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c626e3bd29a1754fcd54ef6d510992fb6f839954
4
- data.tar.gz: 939c681995bc048e0d7588584dfeec286d8df4ea
3
+ metadata.gz: afef825e7d4cfbca7f8904fa47a080fd83180df6
4
+ data.tar.gz: e3ded2d6fb6abcf8ee0f1f452f9ce75666bb8dd7
5
5
  SHA512:
6
- metadata.gz: fba0333e080bc8bb26016b793b25420812f77fc99643a2a38944d689985297241f42203e887fbf9251deb1279ce1c04207be8fd1ca02f37c1ab58417de241a12
7
- data.tar.gz: 5f06b381f3390d53f9059aff5411485b987fb9d193658bfab2df033f792b078eb5b105ce87ca16b73ef1ebdc2b28c7ca6f87511a95e926502bb17b134211af71
6
+ metadata.gz: 266b1c62853e24fef875cd348ed9721948b613b4766edb2857b20c85b4f219b882521e0b6126429d2cd6e7cd4c48c19b9dbb0be1047d57ad804e033c3ca9eef9
7
+ data.tar.gz: 419bd4a87a130f610cd03793b276ce91b18f773a460313dd4da80b1e8ae4604f1bf72ccbf494d53b502d9cf2546702da0bd9a9d451259ade74f1712a1f41a936
data/.ruby-gemset CHANGED
@@ -1 +1 @@
1
- phil_columns
1
+ acclimate
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Acclimate
2
2
 
3
- A CLI building toolkit
3
+ A CLI building toolkit. Pronounced A CLI Mate.
4
4
 
5
5
 
6
6
  ## Installation
@@ -17,6 +17,242 @@ Or install it yourself as:
17
17
 
18
18
  $ gem install acclimate
19
19
 
20
+
20
21
  ## Usage
21
22
 
22
- This release is simply a placeholder for the gem.
23
+ Acclimate provides a toolkit to aid in the boilerplate pieces of a CLI project including: configuration, commands,
24
+ helpers for the CLI class(es), error handling and some output helpers. Acclimate assumes the usage of
25
+ [Thor](http://whatisthor.com/) for creating the CLI portion of your CLI project. Please refer to Thor's documentation
26
+ for further help.
27
+
28
+ ### CliHelper
29
+
30
+ Acclimate's CliHelper module provides convenience methods as well as some sane defaults for [Thor](http://whatisthor.com/).
31
+
32
+ #### The #execute Method
33
+
34
+ The execute method provides a common API to direct your CLI commands to actual command class instances to do the work.
35
+
36
+ module AwesomeCli
37
+ class Cli < Thor
38
+ include Acclimate::CliHelper
39
+
40
+ desc "some-command", "Does something useful"
41
+ option :do_it_good, type: :boolean
42
+ def some_command( filepath )
43
+ execute AwesomeCli::Command::SomeCommand, filepath: filepath
44
+ end
45
+ end
46
+ end
47
+
48
+ The call to `#execute` instantiates the command class, and merges the filepath argument into the options arguments, passing
49
+ it into the command class' initializer.
50
+
51
+
52
+ ### Configuration
53
+
54
+ Acclimate's configuration class provides a base from which to build a custom configuration class in your
55
+ CLI project. The simplest implementation of a configuration class for your project is to inherit from
56
+ `Acclimate::Configuration` and do nothing else.
57
+
58
+ module AwesomeCli
59
+ class Configuration < Acclimate::Configuration
60
+ end
61
+ end
62
+
63
+ Now you can use the configuration class simply by passing a hash of values into it.
64
+
65
+ conf = AwesomeCli::Configuration.new( env: 'development', filepath: 'some/file/path' )
66
+
67
+ conf[:env] #=> 'development'
68
+ conf['env'] #=> 'development'
69
+ conf.env #=> 'development'
70
+ conf.slice( :env ) #=> { :env => 'development }
71
+ conf.slice( :env ).class #=> AwesomeCli::Configuration
72
+
73
+ Pretty much any method that works on a standard `Hash` will work on the configuration. This magic is a result
74
+ of `Acclimate::Configuration` inheriting from [Hashie::Mash](https://github.com/intridea/hashie#mash).
75
+
76
+ In addition, any commands (see the commands section below) that inherit from `Acclimate::Command` accept the
77
+ command line options in their initializer and expose a `#config` method that wraps the options in a configuration
78
+ object for you.
79
+
80
+ ### Commands
81
+
82
+ Acclimate's command class provides a base from which to build the commands for your CLI. Whn building an Acclimate
83
+ CLI, we use commands to encapsulate the actual behavior of each CLI defined command or sub-command. This serves multiple
84
+ purposes. First, it keeps the command logic out of the CLI class(es), which is basically the view or GUI of a CLI project.
85
+ Second, having the actual logic encapsulated in a command means including our CLI gem into another project and borrowing
86
+ the behavior is possible through direct utilization of the command class(es), without interacting with the CLI classes.
87
+
88
+ Building a command class is as simple as inheriting from `Acclimate::Command` and implementing an `#execute` method.
89
+
90
+ module AwesomeCli
91
+ class SomeCommand < Acclimate::Command
92
+ def execute
93
+ # do something useful ...
94
+ end
95
+ end
96
+ end
97
+
98
+ Due to the fact that your commands will certainly have some additional common behavior that Acclimate does not provide,
99
+ it is a good idea to have a base class for your commands. For instance, in order to use your custom configuration class
100
+ as opposed to an instance of `Acclimate::Configuration`, you must override the `#config_klass` method in your command(s).
101
+ You command base class is the ideal place to do so.
102
+
103
+ module AwesomeCli
104
+ class CommandBase < Acclimate::Command
105
+ protected
106
+ def a_helper
107
+ # do something helpful
108
+ end
109
+
110
+ def config_klass
111
+ AwesomeCli::Configuration
112
+ end
113
+ end
114
+
115
+ class SomeCommand < CommandBase
116
+ def execute
117
+ # do something useful ...
118
+ end
119
+ end
120
+ end
121
+
122
+ ### Output
123
+
124
+ Acclimate's output module can be included in any class in your CLI and is already included in any class that inherits from
125
+ `Acclimate::Command` or includes `Acclimate::CliHelper`. In addition to it own [output helpers](https://github.com/midas/acclimate/blob/master/lib/acclimate/output.rb),
126
+ the module also include [Thor's shell module](http://rdoc.info/github/wycats/thor/Thor/Shell/Basic).
127
+
128
+ #### The #confirm Method
129
+
130
+ Acclimate's `#confirm` method is the accepted strategy for outputting command status throughout the execution of the
131
+ command. The `#confirm` method outputs a statement, executes a provided block, then outputs either OK or ERROR depending
132
+ on the results of the block.
133
+
134
+ module AwesomeCli
135
+ module SomeCommand < CommandBase
136
+ def execute
137
+ confirm "Doing something useful" do
138
+ # something useful here
139
+ end
140
+
141
+ confirm "Doing something helpful" do
142
+ # something helpful here
143
+ end
144
+ end
145
+ end
146
+ end
147
+
148
+ Executing the previous command
149
+
150
+ command = AwesomeCli::SomeCommand.new
151
+ command.execute
152
+
153
+ Results in the following output
154
+
155
+ Doing something useful ... OK
156
+ Doing something helpful ... OK
157
+
158
+
159
+ ## Error Handling
160
+
161
+ Acclimate can handle known error conditions gracefully for you. In the cases wher eyou would like to output a useful
162
+ error message but not overwhelm your user with a Ruby backtrace, simply raise the `Acclimate::Error`.
163
+
164
+ raise Acclimate::error, 'External service unavailable, please try the command again later'
165
+
166
+ ### Errors and the #confirm Method
167
+
168
+ If an error is encountered within the block that should be gracefully handled by Acclimate as opposed to an ugly Ruby
169
+ stacktrace, you may raise Acclimate::Error.
170
+
171
+ module AwesomeCli
172
+ module SomeCommand < CommandBase
173
+ def execute
174
+ confirm "Doing something useful" do
175
+ raise Acclimate::Error, 'A known error description'
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ Executing the previous command
182
+
183
+ command = AwesomeCli::SomeCommand.new
184
+ command.execute
185
+
186
+ Results in the following output
187
+
188
+ Doing something useful ... ERROR
189
+ Error: A known error condition
190
+
191
+ By default, the exit code for an error is 1. This can be overridden when raising an error.
192
+
193
+ raise Acclimate::Error.new( 'A known error description', exit_code: 35 )
194
+
195
+ In some cases it is not enough to raise an `Acclimate::Error` and let the graceful error handling ensue. You may need
196
+ to clean something up or want to output additional error details. It is this case the `Acclimate::ConfirmationError` is
197
+ for.
198
+
199
+ Let's assume your command is parsing a text file and wants to output which lines were unparseable along with an error
200
+ message.
201
+
202
+ module AwesomeCli
203
+ module ParseTextFile < CommandBase
204
+ def execute
205
+ confirm "Parsing text file" do
206
+ parse_file
207
+ raise_if_in_error_lines!
208
+ end
209
+ end
210
+
211
+ protected
212
+
213
+ def raise_if_in_error_lines!
214
+ raise Acclimate::ConfirmationError.new( 'Unparseable lines were encountered', exit_code: 12,
215
+ finish_proc: report_in_error_lines )
216
+ end
217
+
218
+ def report_in_error_lines
219
+ -> {
220
+ in_error_lines.each do |line|
221
+ say_stderr( " #{line}" )
222
+ end
223
+ }
224
+ end
225
+
226
+ def parse_file
227
+ File.readlines( config.filepath ).each do |line|
228
+ begin
229
+ parse_line( line )
230
+ rescue
231
+ in_error_lines << line
232
+ end
233
+ end
234
+ end
235
+
236
+ def parse_line( line )
237
+ # some parsing logic ...
238
+ end
239
+
240
+ def in_error_lines
241
+ @in_error_lines ||= []
242
+ end
243
+
244
+ end
245
+ end
246
+
247
+ Executing the previous command
248
+
249
+ command = AwesomeCli::ParseTextFile.new
250
+ command.execute
251
+
252
+ Results in the following output
253
+
254
+ Parsing text file ... ERROR
255
+ Unparseable lines were encountered
256
+ 1 something unparseable
257
+ 7 something else unparseable
258
+
data/acclimate.gemspec CHANGED
@@ -20,5 +20,10 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency "bundler", "~> 1.5"
22
22
  spec.add_development_dependency "gem-dandy"
23
+ spec.add_development_dependency "pry-debugger"
23
24
  spec.add_development_dependency "rake"
25
+
26
+ spec.add_dependency "hashie"
27
+ spec.add_dependency "rainbow"
28
+ spec.add_dependency "thor"
24
29
  end
@@ -0,0 +1,53 @@
1
+ module Acclimate
2
+ module CliHelper
3
+
4
+ def self.included( base )
5
+ base.class_eval do
6
+ include Acclimate::Output
7
+
8
+ no_commands do
9
+
10
+ def execute( klass, additional_options={} )
11
+ klass.new( options.merge( additional_options )).execute
12
+ rescue Acclimate::Error => e
13
+ handle_error( e ) unless e.handled?
14
+ exit( e.exit_code || 1 )
15
+ end
16
+
17
+ def handle_error( e )
18
+ say "Error: #{e.message}", :red
19
+ end
20
+
21
+ end
22
+ end
23
+
24
+ base.extend ClassMethods
25
+ end
26
+
27
+ module ClassMethods
28
+
29
+ def handle_argument_error( command, error, _, __ )
30
+ method = "handle_argument_error_for_#{command.name}"
31
+
32
+ if respond_to?( method )
33
+ send( method, command, error )
34
+ else
35
+ handle_argument_error_default( command, error )
36
+ end
37
+ end
38
+
39
+ def handle_argument_error_default( command, error )
40
+ $stdout.puts "Incorrect usage of command: #{command.name}"
41
+ $stdout.puts " #{error.message}", ''
42
+ $stdout.puts "For correct usage:"
43
+ $stdout.puts " acclimate help #{command.name}"
44
+ end
45
+
46
+ def handle_no_command_error( name )
47
+ $stdout.puts "Unrecognized command: #{name}"
48
+ end
49
+
50
+ end
51
+
52
+ end
53
+ end
@@ -0,0 +1,33 @@
1
+ require 'pathname'
2
+
3
+ module Acclimate
4
+ class Command
5
+
6
+ include Acclimate::Output
7
+
8
+ def initialize( options )
9
+ @options = options
10
+ end
11
+
12
+ def execute
13
+ raise NotImplementedError, "You must implement #{self.class.name}#execute"
14
+ end
15
+
16
+ protected
17
+
18
+ attr_reader :options
19
+
20
+ def config
21
+ @config = config_klass.new( options )
22
+ end
23
+
24
+ def base_path
25
+ Pathname.new( Dir.pwd )
26
+ end
27
+
28
+ def config_klass
29
+ Acclimate::Configuration
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,21 @@
1
+ require 'hashie'
2
+
3
+ module Acclimate
4
+ class Configuration < Hashie::Mash
5
+
6
+ def initialize( options={} )
7
+ super( options )
8
+ end
9
+
10
+ def slice( *keys )
11
+ klass.new( select { |k,v| keys.map( &:to_s ).include?( k ) } )
12
+ end
13
+
14
+ protected
15
+
16
+ def klass
17
+ self.class
18
+ end
19
+
20
+ end
21
+ end
@@ -0,0 +1,27 @@
1
+ module Acclimate
2
+ class ConfirmationError < StandardError
3
+
4
+ def initialize( message, options={} )
5
+ @options = options
6
+ super( message )
7
+ end
8
+
9
+ def exit_code
10
+ options[:exit_code] || 1
11
+ end
12
+
13
+ def finish
14
+ return unless finish_proc
15
+ finish_proc.call
16
+ end
17
+
18
+ protected
19
+
20
+ attr_reader :options
21
+
22
+ def finish_proc
23
+ options[:finish_proc]
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ module Acclimate
2
+ class Error < StandardError
3
+
4
+ def initialize( message, options={} )
5
+ @options = options
6
+
7
+ super( message )
8
+ end
9
+
10
+ def exit_code
11
+ options[:exit_code] || 1
12
+ end
13
+
14
+ def handled?
15
+ options[:handled] || false
16
+ end
17
+
18
+ protected
19
+
20
+ attr_reader :options
21
+
22
+ end
23
+ end
@@ -0,0 +1,52 @@
1
+ require 'thor/shell'
2
+
3
+ module Acclimate
4
+ module Output
5
+
6
+ def self.included( base )
7
+ base.send( :include, Thor::Shell )
8
+ end
9
+
10
+ def write( msg, color=:white )
11
+ $stdout.write( Rainbow( msg ).color( color ))
12
+ end
13
+
14
+ def say( msg, color=:white )
15
+ $stdout.puts( Rainbow( msg ).color( color ))
16
+ end
17
+
18
+ def say_stderr( msg, color=:white )
19
+ $stdout.puts( Rainbow( msg ).color( color ))
20
+ end
21
+
22
+ def header( msg )
23
+ say msg, :cyan
24
+ end
25
+
26
+ def say_ok
27
+ say 'OK', :green
28
+ end
29
+
30
+ def say_error
31
+ say_stderr 'ERROR', :red
32
+ end
33
+
34
+ def say_skipping
35
+ say 'SKIPPING', :yellow
36
+ end
37
+
38
+ def confirm( msg, color=:white, &block )
39
+ write "#{msg}... ", color
40
+ block.call
41
+ say_ok
42
+ rescue ConfirmationError => e
43
+ say_error
44
+ say_stderr( e.message, :red )
45
+ e.finish
46
+ raise Acclimate::Error.new( nil, handled: true, exit_code: e.exit_code )
47
+ rescue
48
+ say_error
49
+ raise
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,3 @@
1
1
  module Acclimate
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/acclimate.rb CHANGED
@@ -1,5 +1,15 @@
1
1
  require "acclimate/version"
2
+ require 'pry-debugger'
3
+ require 'rainbow'
4
+ require 'thor'
2
5
 
3
6
  module Acclimate
4
- # Your code goes here...
7
+
8
+ autoload :CliHelper, 'acclimate/cli_helper'
9
+ autoload :Command, 'acclimate/command'
10
+ autoload :Configuration, 'acclimate/configuration'
11
+ autoload :ConfirmationError, 'acclimate/confirmation_error'
12
+ autoload :Error, 'acclimate/error'
13
+ autoload :Output, 'acclimate/output'
14
+
5
15
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: acclimate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - C. Jason Harrelson (midas )
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-debugger
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -52,6 +66,48 @@ dependencies:
52
66
  - - ">="
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: hashie
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rainbow
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: thor
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
55
111
  description: A Cli building toolkit. See README for more details.
56
112
  email:
57
113
  - jason@lookforwardenterprises.com
@@ -68,6 +124,12 @@ files:
68
124
  - Rakefile
69
125
  - acclimate.gemspec
70
126
  - lib/acclimate.rb
127
+ - lib/acclimate/cli_helper.rb
128
+ - lib/acclimate/command.rb
129
+ - lib/acclimate/configuration.rb
130
+ - lib/acclimate/confirmation_error.rb
131
+ - lib/acclimate/error.rb
132
+ - lib/acclimate/output.rb
71
133
  - lib/acclimate/version.rb
72
134
  homepage: ''
73
135
  licenses: