cliutils 2.1.4 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -0
  3. data/.travis.yml +3 -2
  4. data/Gemfile +7 -0
  5. data/HISTORY.md +7 -0
  6. data/README.md +3 -1
  7. data/Rakefile +8 -1
  8. data/cliutils.gemspec +1 -0
  9. data/lib/cliutils/configuration.rb +1 -1
  10. data/lib/cliutils/constants.rb +1 -1
  11. data/lib/cliutils/ext/hash_extensions.rb +16 -12
  12. data/lib/cliutils/messaging.rb +2 -116
  13. data/lib/cliutils/messenger.rb +109 -0
  14. data/lib/cliutils/prefs/pref.rb +10 -10
  15. data/lib/cliutils/prefs/pref_actions/open_url_action.rb +1 -1
  16. data/lib/cliutils/prefs/pref_validators/filepath_exists_validator.rb +2 -0
  17. data/lib/cliutils/prefs/pref_validators/url_validator.rb +1 -1
  18. data/lib/cliutils/prefs.rb +1 -1
  19. data/lib/cliutils/pretty_io.rb +0 -10
  20. data/lib/cliutils.rb +1 -1
  21. data/spec/action/open_url_action_spec.rb +20 -0
  22. data/spec/action/pref_action_spec.rb +11 -0
  23. data/spec/behavior/capitalize_behavior_spec.rb +11 -0
  24. data/spec/behavior/expand_filepath_behavior_spec.rb +11 -0
  25. data/spec/behavior/lowercase_behavior_spec.rb +11 -0
  26. data/spec/behavior/pref_behavior_spec.rb +11 -0
  27. data/spec/behavior/prefix_behavior_spec.rb +12 -0
  28. data/spec/behavior/suffix_behavior_spec.rb +12 -0
  29. data/spec/behavior/titlecase_behavior_spec.rb +10 -0
  30. data/spec/behavior/uppercase_behavior_spec.rb +11 -0
  31. data/spec/configuration_spec.rb +37 -0
  32. data/spec/configurator_spec.rb +104 -0
  33. data/spec/ext/hash_extensions_spec.rb +54 -0
  34. data/spec/ext/logger_extensions_spec.rb +20 -0
  35. data/spec/ext/string_extensions_spec.rb +26 -0
  36. data/spec/messaging_spec.rb +91 -0
  37. data/spec/pref_spec.rb +144 -0
  38. data/spec/prefs_spec.rb +150 -0
  39. data/spec/spec_helper.rb +16 -0
  40. data/spec/validator/alphabetic_validator_spec.rb +20 -0
  41. data/spec/validator/alphanumeric_validator_spec.rb +20 -0
  42. data/spec/validator/date_validator_spec.rb +20 -0
  43. data/spec/validator/datetime_validator_spec.rb +20 -0
  44. data/spec/validator/filepath_exists_validator_spec.rb +20 -0
  45. data/spec/validator/non_nil_validator_spec.rb +24 -0
  46. data/spec/validator/number_validator_spec.rb +20 -0
  47. data/spec/validator/pref_validator_spec.rb +11 -0
  48. data/spec/validator/time_validator_spec.rb +20 -0
  49. data/spec/validator/url_validator_spec.rb +20 -0
  50. data/{test/test_files → support}/configuration.yaml +1 -1
  51. data/support/prefstest.yaml +27 -0
  52. data/{test/test_files → support}/test_action.rb +0 -0
  53. data/support/test_action_empty.rb +7 -0
  54. data/{test/test_files → support}/test_behavior.rb +2 -2
  55. data/support/test_behavior_empty.rb +6 -0
  56. data/{test/test_files → support}/test_validator.rb +0 -2
  57. data/support/test_validator_empty.rb +7 -0
  58. data/test/pref_test.rb +0 -1
  59. data/test/prefs_test.rb +64 -1
  60. data/test/test_helper.rb +2 -2
  61. metadata +91 -67
  62. data/lib/cliutils/logger_delegator.rb +0 -49
  63. data/test/action_tests/open_url_action_test.rb +0 -12
  64. data/test/behavior_tests/capitalize_behavior_test.rb +0 -11
  65. data/test/behavior_tests/expand_filepath_behavior_test.rb +0 -11
  66. data/test/behavior_tests/lowercase_behavior_test.rb +0 -11
  67. data/test/behavior_tests/prefix_behavior_test.rb +0 -12
  68. data/test/behavior_tests/suffix_behavior_test.rb +0 -12
  69. data/test/behavior_tests/titlecase_behavior_test.rb +0 -11
  70. data/test/behavior_tests/uppercase_behavior_test.rb +0 -11
  71. data/test/configuration_test.rb +0 -49
  72. data/test/configurator_test.rb +0 -63
  73. data/test/hash_extensions_test.rb +0 -51
  74. data/test/logger_extensions_test.rb +0 -17
  75. data/test/messaging_test.rb +0 -53
  76. data/test/string_extesions_test.rb +0 -28
  77. data/test/test_files/prefstest.yaml +0 -38
  78. data/test/validator_tests/alphabetic_validator_test.rb +0 -22
  79. data/test/validator_tests/alphanumeric_validator_test.rb +0 -22
  80. data/test/validator_tests/date_validator_test.rb +0 -22
  81. data/test/validator_tests/datetime_validator_test.rb +0 -22
  82. data/test/validator_tests/filepath_exists_validator_test.rb +0 -22
  83. data/test/validator_tests/non_nil_validator_test.rb +0 -30
  84. data/test/validator_tests/number_validator_test.rb +0 -22
  85. data/test/validator_tests/time_validator_test.rb +0 -22
  86. data/test/validator_tests/url_validator_test.rb +0 -22
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d62e541400b381493e5a6699ba4db68e959cdc06
4
- data.tar.gz: 2e733795f7de8268159811bd1a457e21066575f5
3
+ metadata.gz: 31b393aa656e5f0af3f63b272675069a58441286
4
+ data.tar.gz: fd649fe5090c1112c810e1b12b09d0120b43ac71
5
5
  SHA512:
6
- metadata.gz: a600e5780fccddf0d6d86031e383085b3f07d971ab8378de2d1ef0544bdb85f6846ae6fe9ad0348602eb8b0381efe1b81316bff5c3531ef20faffd4e26469dfe
7
- data.tar.gz: d305f17fa6e613e0ab963f32103e333386977af6f1382379de9011fc4b527b5d9c518d50e104fad5951d0fff9d99e756872927d71ccb92e79b4839fe4482d7d2
6
+ metadata.gz: 0f042f15114fb441f842918907ce8be6ff18a26e51b921e77fe749b59818411d66bcbd94c6194b4aca23a27b5474970ff35ee32e203ccb0ed6b55831f9266383
7
+ data.tar.gz: a79310fbc362ba89a2b538c60c523314dbc17b22463f630763f28057e0e05e34632536eecc514a98403a22ea5c4dd1bd6a7a58640284f765a7106f491482245a
data/.gitignore CHANGED
@@ -5,3 +5,5 @@ pkg
5
5
  bin
6
6
  .tmp
7
7
  Gemfile.lock
8
+ coverage
9
+ npm-debug.log
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  language: ruby
2
2
  rvm:
3
+ - 2.1.1
3
4
  - 2.1.0
4
- - 2.0.0
5
+ - 2.0
5
6
  - 1.9.3
6
- - jruby-19mode
7
+ - rbx
data/Gemfile CHANGED
@@ -1,6 +1,13 @@
1
1
  source 'https://rubygems.org'
2
2
 
3
+ gem 'simplecov', require: false
3
4
  gem 'coveralls', require: false
4
5
 
6
+ platforms :rbx do
7
+ gem 'racc'
8
+ gem 'rubysl', '~> 2.0'
9
+ gem 'psych'
10
+ end
11
+
5
12
  # Specify your gem's dependencies in cliutils.gemspec
6
13
  gemspec
data/HISTORY.md CHANGED
@@ -1,3 +1,10 @@
1
+ # 2.2.0 (2014-04-26)
2
+
3
+ * More robust `Messaging` that allows for logging of prompts
4
+ * Complete RSpec tests (moving on from Test Unit)
5
+ * Removed useless `info_block` method
6
+ * Several bugfixes
7
+
1
8
  # 2.1.4 (2014-04-26)
2
9
 
3
10
  * Slightly more robust Time regex in TimeValidator
data/README.md CHANGED
@@ -2,6 +2,7 @@ CLIUtils
2
2
  ====
3
3
  [![Build Status](https://travis-ci.org/bachya/cliutils.svg?branch=master)](https://travis-ci.org/bachya/cliutils)
4
4
  [![Gem Version](https://badge.fury.io/rb/cliutils.svg)](http://badge.fury.io/rb/cliutils)
5
+ [![Coverage Status](http://img.shields.io/coveralls/bachya/cliutils/master.svg)](https://coveralls.io/r/bachya/cliutils?branch=master)
5
6
 
6
7
  CLIUtils is a library of functionality designed to alleviate common tasks and headaches when developing command-line (CLI) apps in Ruby.
7
8
 
@@ -35,10 +36,11 @@ CLIUtils offers:
35
36
 
36
37
  CLIUtils is certified against the following:
37
38
 
39
+ * Ruby 2.1.1
38
40
  * Ruby 2.1.0
39
41
  * Ruby 2.0.0
40
42
  * Ruby 1.9.3
41
- * jruby-19mode
43
+ * Latest RBX
42
44
 
43
45
  # Installation
44
46
 
data/Rakefile CHANGED
@@ -20,6 +20,13 @@ Rake::TestTask.new do |t|
20
20
  t.test_files = FileList['test/*_test.rb', 'test/**/*_test.rb']
21
21
  end
22
22
 
23
+ require 'rspec/core/rake_task'
24
+ desc "Run the specs."
25
+ RSpec::Core::RakeTask.new do |t|
26
+ t.pattern = "spec/**/*_spec.rb"
27
+ t.verbose = false
28
+ end
29
+
23
30
  desc "Release CLIUtils version #{version}"
24
31
  task :release => :build do
25
32
  unless `git branch` =~ /^\* master$/
@@ -41,4 +48,4 @@ task :build do
41
48
  FileUtils.mv("./cliutils-#{version}.gem", "pkg")
42
49
  end
43
50
 
44
- task :default => :test
51
+ task :default => [:spec] #:yard
data/cliutils.gemspec CHANGED
@@ -20,6 +20,7 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_development_dependency('bundler', '~> 1.5')
22
22
  spec.add_development_dependency('rake', '~> 0')
23
+ spec.add_development_dependency('rspec', '2.14.1')
23
24
  spec.add_development_dependency('yard', '0.8.7.4')
24
25
  spec.add_runtime_dependency('launchy', '2.4.2')
25
26
  end
@@ -27,7 +27,7 @@ module CLIUtils
27
27
  # a Configurator.
28
28
  # @return [Configurator]
29
29
  def configuration
30
- if !@@configuration.nil?
30
+ if @@configuration
31
31
  @@configuration
32
32
  else
33
33
  fail 'Attempted to access `configuration` before ' \
@@ -8,5 +8,5 @@ module CLIUtils
8
8
  SUMMARY = 'Sugary goodness for Ruby CLI apps.'
9
9
 
10
10
  # The current version of the gem
11
- VERSION = '2.1.4'
11
+ VERSION = '2.2.0'
12
12
  end
@@ -2,20 +2,24 @@
2
2
  # Contains many convenient methods borrowed from Rails
3
3
  # http://api.rubyonrails.org/classes/Hash.html
4
4
  class Hash
5
- # Deep merges a hash into the current one.
5
+ # Deep merges a hash into the current one. Returns
6
+ # a new copy of the hash.
6
7
  # @param [Hash] other_hash The hash to merge in
7
- # @yield &block
8
8
  # @return [Hash] The original Hash
9
- def deep_merge!(other_hash, &block)
10
- other_hash.each_pair do |k, v|
11
- tv = self[k]
12
- if tv.is_a?(Hash) && v.is_a?(Hash)
13
- self[k] = tv.deep_merge(v, &block)
14
- else
15
- self[k] = block && tv ? block.call(k, tv, v) : v
16
- end
9
+ def deep_merge(other_hash)
10
+ self.merge(other_hash) do |key, oldval, newval|
11
+ oldval = oldval.to_hash if oldval.respond_to?(:to_hash)
12
+ newval = newval.to_hash if newval.respond_to?(:to_hash)
13
+ oldval.class.to_s == 'Hash' && newval.class.to_s == 'Hash' ? oldval.deep_merge(newval) : newval
17
14
  end
18
- self
15
+ end
16
+
17
+ # Deep merges a hash into the current one. Does
18
+ # the replacement inline.
19
+ # @param [Hash] other_hash The hash to merge in
20
+ # @return [Hash] The original Hash
21
+ def deep_merge!(other_hash)
22
+ replace(deep_merge(other_hash))
19
23
  end
20
24
 
21
25
  # Recursively turns all Hash keys into strings and
@@ -66,6 +70,7 @@ class Hash
66
70
  # key and returns the value (if there is one).
67
71
  # http://stackoverflow.com/a/2239847/327179
68
72
  # @param [<Symbol, String>] key The key to search for
73
+ # @yield
69
74
  # @return [Multiple]
70
75
  def recursive_find_by_key(key)
71
76
  # Create a stack of hashes to search through for the needle which
@@ -86,7 +91,6 @@ class Hash
86
91
  end
87
92
  end
88
93
  end
89
- yield if block_given?
90
94
  end
91
95
 
92
96
  private
@@ -1,128 +1,14 @@
1
- require 'cliutils/pretty_io'
1
+ require 'cliutils/messenger'
2
2
 
3
3
  module CLIUtils
4
4
  # CLIMessenger Module
5
5
  # Outputs coordinated messages to a variety of targets.
6
6
  module Messaging
7
- include PrettyIO
8
-
9
- # Hook that triggers when this module is included.
10
- # @param [Object] k The includer object
11
- # @return [void]
12
- def self.included(k)
13
- k.extend(self)
14
- end
15
-
16
- # Empty method so that Messaging doesn't freak out when passed a debug
17
- # message.
18
- # @return [void]
19
- def debug(m); end
20
-
21
- # Returns a default instance of LoggerDelegator that delegates to STDOUT
22
- # only.
23
- # @return [LoggerDelegator]
24
- def default_instance
25
- stdout_logger = Logger.new(STDOUT)
26
- stdout_logger.formatter = proc do |severity, datetime, progname, msg|
27
- send(severity.downcase, msg)
28
- end
29
-
30
- LoggerDelegator.new(STDOUT: stdout_logger)
31
- end
32
-
33
- # Outputs a formatted-red error message.
34
- # @param [String] m The message to output
35
- # @return [void]
36
- def error(m)
37
- puts _word_wrap(m, '# ').red
38
- end
39
-
40
- # Outputs a formatted-blue informational message.
41
- # @param [String] m The message to output
42
- # @return [void]
43
- def info(m)
44
- puts _word_wrap(m, '# ').blue
45
- end
46
-
47
- # Wraps a block in an opening and closing info message.
48
- # @param [String] m1 The opening message to output
49
- # @param [String] m2 The closing message to output
50
- # @param [Boolean] multiline Whether the message should be multiline
51
- # @yield
52
- # @return [void]
53
- def info_block(m1, m2 = 'Done.', multiline = false)
54
- if block_given?
55
- if multiline
56
- info(m1)
57
- else
58
- print _word_wrap(m1, '# ').blue
59
- end
60
-
61
- yield
62
-
63
- if multiline
64
- info(m2)
65
- else
66
- puts _word_wrap(m2, '# ').blue
67
- end
68
- else
69
- fail 'Did not specify a valid block'
70
- end
71
- end
72
-
73
- # Empty method so that Messaging doesn't freak
74
- # out when passed a log message.
75
- # @return [void]
76
- def log(m); end
77
-
78
7
  # Singleton method to return (or initialize, if needed)
79
8
  # a LoggerDelegator.
80
9
  # @return [LoggerDelegator]
81
10
  def messenger
82
- @@messenger ||= default_instance
83
- end
84
-
85
- # Outputs a prompt, collects the user's response, and
86
- # returns it.
87
- # @param [String] prompt The prompt to output
88
- # @param [String] default The default option
89
- # @param [String] start_dir The directory to start from for autocompletion
90
- # @return [String]
91
- def prompt(prompt, default = nil, start_dir = '')
92
- Readline.completion_append_character = nil
93
- Readline.completion_proc = lambda do |prefix|
94
- files = Dir["#{start_dir}#{prefix}*"]
95
- files.map { |f| File.expand_path(f) }
96
- .map { |f| File.directory?(f) ? "#{ f }/" : f }
97
- end
98
- p = "# #{ prompt }#{ default.nil? ? ':' : " [default: #{ default }]:" } "
99
- choice = Readline.readline(p.cyan)
100
- if choice.empty?
101
- default
102
- else
103
- choice
104
- end
105
- end
106
-
107
- # Outputs a formatted-purple section message.
108
- # @param [String] m The message to output
109
- # @return [void]
110
- def section(m)
111
- puts _word_wrap(m, '---> ').purple
112
- end
113
-
114
- # Outputs a formatted-green success message.
115
- # @param [String] m The message to output
116
- # @return [void]
117
- def success(m)
118
- puts _word_wrap(m, '# ').green
119
- end
120
-
121
- # Outputs a formatted-yellow warning message.
122
- # @param [String] m The message to output
123
- # @return [void]
124
- def warn(m)
125
- puts _word_wrap(m, '# ').yellow
11
+ @@messenger ||= CLIUtils::Messenger.new
126
12
  end
127
13
  end
128
14
  end
@@ -0,0 +1,109 @@
1
+ require 'cliutils/pretty_io'
2
+ require 'readline'
3
+
4
+ module CLIUtils
5
+ class Messenger
6
+ include PrettyIO
7
+
8
+ # The endpoints to which delegation occurs.
9
+ # @return [Array]
10
+ attr_reader :targets
11
+
12
+ # Attaches a new target to delegate to.
13
+ # @param [Hash] target A hash describing a reference key and a Logger
14
+ # @return [void]
15
+ def attach(target)
16
+ fail "Cannot add invalid target: #{ target }" unless target.is_a?(Hash)
17
+ @targets.merge!(target)
18
+ end
19
+
20
+ # Empty method so that Messaging doesn't freak out when passed a debug
21
+ # message.
22
+ # @return [void]
23
+ def debug(m)
24
+ @targets.each { |_, t| t.debug(m) }
25
+ end
26
+
27
+ # Detaches a delegation target.
28
+ # @param [<String, Symbol>] target_name The target to remove
29
+ # @return [void]
30
+ def detach(target_name)
31
+ unless @targets.key?(target_name)
32
+ fail "Cannot detach invalid target: #{ target_name }"
33
+ end
34
+ @targets.delete(target_name)
35
+ end
36
+
37
+ # Outputs a formatted-red error message.
38
+ # @param [String] m The message to output
39
+ # @return [void]
40
+ def error(m)
41
+ puts _word_wrap(m, '# ').red
42
+ @targets.each { |_, t| t.error(m) }
43
+ end
44
+
45
+ # Outputs a formatted-blue informational message.
46
+ # @param [String] m The message to output
47
+ # @return [void]
48
+ def info(m)
49
+ puts _word_wrap(m, '# ').blue
50
+ @targets.each { |_, t| t.info(m) }
51
+ end
52
+
53
+ def info_block(*params)
54
+ warn('As of 2.2.0, `info_block` is deprecated and nonfunctioning.')
55
+ end
56
+
57
+ def initialize(targets = {})
58
+ @targets = targets
59
+ end
60
+
61
+ # Outputs a prompt, collects the user's response, and
62
+ # returns it.
63
+ # @param [String] prompt The prompt to output
64
+ # @param [String] default The default option
65
+ # @return [String]
66
+ def prompt(prompt, default = nil)
67
+ Readline.completion_append_character = nil
68
+ Readline.completion_proc = Proc.new do |str|
69
+ Dir[str + '*'].grep( /^#{ Regexp.escape(str) }/ )
70
+ end
71
+
72
+ p = "# #{ prompt }#{ default.nil? ? ':' : " [default: #{ default }]:" } "
73
+ r = ''
74
+ choice = Readline.readline(p.cyan)
75
+ if choice.nil? || choice.empty?
76
+ r = default
77
+ else
78
+ r = choice
79
+ end
80
+
81
+ @targets.each { |_, t| t.prompt("Answer to '#{ prompt }': #{ r }") }
82
+ r
83
+ end
84
+
85
+ # Outputs a formatted-purple section message.
86
+ # @param [String] m The message to output
87
+ # @return [void]
88
+ def section(m)
89
+ puts _word_wrap(m, '---> ').purple
90
+ @targets.each { |_, t| t.section(m) }
91
+ end
92
+
93
+ # Outputs a formatted-green success message.
94
+ # @param [String] m The message to output
95
+ # @return [void]
96
+ def success(m)
97
+ puts _word_wrap(m, '# ').green
98
+ @targets.each { |_, t| t.success(m) }
99
+ end
100
+
101
+ # Outputs a formatted-yellow warning message.
102
+ # @param [String] m The message to output
103
+ # @return [void]
104
+ def warn(m)
105
+ puts _word_wrap(m, '# ').yellow
106
+ @targets.each { |_, t| t.warn(m) }
107
+ end
108
+ end
109
+ end
@@ -120,7 +120,7 @@ module CLIUtils
120
120
 
121
121
  valid_option_chosen = false
122
122
  until valid_option_chosen
123
- response = prompt(@prompt_text, default)
123
+ response = messenger.prompt(@prompt_text, default)
124
124
  if validate(response)
125
125
  valid_option_chosen = true
126
126
  @answer = evaluate_behaviors(response)
@@ -190,11 +190,11 @@ module CLIUtils
190
190
  # Evaluates the pre-prompt Hash and does the right thing. :)
191
191
  # @return [void]
192
192
  def _eval_pre
193
- info(@pre[:message])
194
- prompt('Press enter to continue')
193
+ messenger.info(@pre[:message])
194
+ messenger.prompt('Press enter to continue')
195
195
 
196
196
  if (@pre[:action])
197
- action_obj = _init_action(@pre[:action][:name])
197
+ action_obj = _init_action(@pre[:action])
198
198
  action_obj.run if action_obj
199
199
  end
200
200
  end
@@ -202,11 +202,11 @@ module CLIUtils
202
202
  # Evaluates the post-prompt Hash and does the right thing. :)
203
203
  # @return [void]
204
204
  def _eval_post
205
- info(@post[:message])
206
- prompt('Press enter to continue')
205
+ messenger.info(@post[:message])
206
+ messenger.prompt('Press enter to continue')
207
207
 
208
208
  if (@post[:action])
209
- action_obj = _init_action(@post[:action][:name])
209
+ action_obj = _init_action(@post[:action])
210
210
  action_obj.run if action_obj
211
211
  end
212
212
  end
@@ -217,8 +217,8 @@ module CLIUtils
217
217
  # @return [void]
218
218
  def _init_action(path_or_name)
219
219
  obj = _load_asset(ASSET_TYPE_ACTION, path_or_name)
220
- unless obj.nil?
221
- obj.parameters = @pre[:action][:parameters]
220
+ unless obj.nil? || @pre[:parameters].nil?
221
+ obj.parameters = @pre[:parameters]
222
222
  end
223
223
  obj
224
224
  end
@@ -258,7 +258,7 @@ module CLIUtils
258
258
  # If the file exists, we're assuming that the user
259
259
  # passed a filepath.
260
260
  asset_found = true
261
- asset_path = File.expand_path(path_or_name) if path_or_name.start_with?('~')
261
+ asset_path = File.expand_path(path_or_name)
262
262
  asset_name = File.basename(path_or_name, '.*').camelize
263
263
  end
264
264