cliutils 2.1.4 → 2.2.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.
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