clif 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.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,5 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 jameswilding
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,106 @@
1
+ # clif
2
+
3
+ clif is a ready-made interface between your library and the command-line. Write less code, get more done.
4
+
5
+ ## Why clif?
6
+
7
+ clif will ask questions, get answers, convert yes/no responses into true/false values, and more. Use clif to run an installation procedure, explain build steps, get user input for a command line script: anywhere your library needs to interact with the use on the command line, clif can help.
8
+
9
+ ## Some examples
10
+
11
+ In the following examples, the output of methods like <code>ask</code>, <code>say</code>, and <code>perform_action</code> is displayed on the command-line (and input from the command line is passed back to your code, where appropriate).
12
+
13
+ ### Setup
14
+
15
+ Clif respects the :quiet and :pretend options:
16
+
17
+ Clif.new # speaks messages, performs action
18
+ Clif.new(:quiet => true) # performs actions silently (will still ask questions)
19
+ Clif.new(:pretend => true) # speaks but doesn't act (useful for dry runs)
20
+
21
+ # Let's go
22
+ @clif = Clif.new
23
+
24
+ ### Speaking
25
+
26
+ # Clif can speak
27
+ @clif.say "Hello world!" # => 'Hello world!'
28
+
29
+ # Set :quiet to true to supress messages (questions will still be spoken)
30
+ Clif.new(:quiet => true).say('Hello world!') # => nil
31
+
32
+ ### Asking Questions
33
+
34
+ Ask a question on the command line and get a response from the user:
35
+
36
+ user_status = @clif.ask('How are you?')
37
+
38
+ Yes/no ('boolean') questions return true or false:
39
+
40
+ hungry = @clif.ask('Are you hungry?', :boolean => true) # "Are you hungry? [y/n]:"
41
+
42
+ And as a convenience, clif provide <code>yes?</code> and <code>no?</code> methods:
43
+
44
+ hungry = @clif.yes?('Are you hungry?')
45
+ thirsty = @clif.no?('Have you drunk?')
46
+
47
+ ### Prompt for responses
48
+
49
+ name = @clif.prompt('Your name, please') # "Your name, please: "
50
+ puts "Your name is #{name}"
51
+
52
+ ### Perform Actions
53
+
54
+ The <code>perform_action</code> and <code>explain</code> methods are intended to talk the user through a serious of related steps: an installation, a plugin generation process, etc.
55
+
56
+ The <code>perform_action</code> method explains an action to the user, and then performs the action. This next example outputs "install: some/file.rb" and runs <code>FileUtils.install</code>:
57
+
58
+ @clif.perform_action(:install, 'some/file.rb') do
59
+ FileUtils.install('some/file.rb', '/usr/local/lib')
60
+ end
61
+
62
+ When you use <code>:pretend => true</code>, clif will explain actions but won't actually perform them. This is useful for dry runs:
63
+
64
+ file = 'some/file.rb'
65
+
66
+ Clif.new(:pretend => true).perform_action(:create, file) do
67
+ FileUtils.touch(file) # won't run
68
+ end
69
+
70
+ File.file?(file) # => false
71
+
72
+ Use <code>explain</code> to comment on steps that don't require any actions:
73
+
74
+ @clif.explain(:exists, 'app/views') # => "exists: app/views"
75
+
76
+ ### Mixin!
77
+
78
+ Mix in the <code>Clif::Helper</code> module to use clif's methods in your own classes:
79
+
80
+ class Butler
81
+ include Clif::Helper
82
+ end
83
+
84
+ Butler.new.ask('May I take your coat?')
85
+
86
+ <code>Clif::Helper</code> assumes that you have an instance variable called <code>@options</code> available in your objects.
87
+
88
+ ## TODO
89
+
90
+ * Cleaner testing of output to $stdout and input from $stdin
91
+ * Persistently ask yes/no questions until the response is some variant of 'y', 'yes, 'n', or 'no'
92
+
93
+ ## Note on Patches/Pull Requests
94
+
95
+ * Fork the project.
96
+ * Make your feature addition or bug fix.
97
+ * Add tests for it. This is important so I don't break it in a
98
+ future version unintentionally.
99
+ * Commit: do not mess with rakefile, version, or history.
100
+ (if you want to have your own version, that is fine but
101
+ bump version in a commit by itself I can ignore when I pull)
102
+ * Send me a pull request. Bonus points for topic branches.
103
+
104
+ == Copyright
105
+
106
+ Copyright (c) 2009 James Wilding. See LICENSE for details.
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+
7
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "clif"
10
+ gem.summary = %Q{Ready-made command line interface for ruby libraries}
11
+ gem.description = %Q{clif (Command Line InterFace) acts as a bridge between your code and the command line}
12
+ gem.email = "james@jameswilding.net"
13
+ gem.homepage = "http://github.com/jameswilding/clif"
14
+ gem.authors = ["jameswilding"]
15
+ gem.add_development_dependency "thoughtbot-shoulda"
16
+ gem.add_development_dependency "redgreen"
17
+ end
18
+
19
+ # Releases go to gemcutter.org
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
23
+ end
24
+
25
+ require 'rake/testtask'
26
+ Rake::TestTask.new(:test) do |test|
27
+ test.libs << 'lib' << 'test'
28
+ test.pattern = 'test/**/*_test.rb'
29
+ test.verbose = true
30
+ end
31
+
32
+ begin
33
+ require 'rcov/rcovtask'
34
+ Rcov::RcovTask.new do |test|
35
+ test.libs << 'test'
36
+ test.pattern = 'test/**/*_test.rb'
37
+ test.rcov_opts = ['--exclude /Library'] # otherwise depandencies are analysed, too
38
+ test.verbose = true
39
+ end
40
+ rescue LoadError
41
+ task :rcov do
42
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
43
+ end
44
+ end
45
+
46
+ #task :test => :check_dependencies # => "don't know how to build task'check_dependencies'"
47
+
48
+ task :default => :test
49
+
50
+ require 'rake/rdoctask'
51
+ Rake::RDocTask.new do |rdoc|
52
+ if File.exist?('VERSION')
53
+ version = File.read('VERSION')
54
+ else
55
+ version = ""
56
+ end
57
+
58
+ rdoc.rdoc_dir = 'rdoc'
59
+ rdoc.title = "clif #{version}"
60
+ rdoc.rdoc_files.include('README*')
61
+ rdoc.rdoc_files.include('lib/**/*.rb')
62
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,9 @@
1
+ require 'clif/helper'
2
+
3
+ class Clif
4
+ include Clif::Helper
5
+
6
+ def initialize(options = {})
7
+ @options = options
8
+ end
9
+ end
@@ -0,0 +1,50 @@
1
+ require 'clif/questioner'
2
+
3
+ class Clif
4
+ module Helper
5
+
6
+ def say(message)
7
+ $stdout.puts(message) unless quiet?
8
+ end
9
+
10
+ def no?(question)
11
+ !yes?(question)
12
+ end
13
+
14
+ def yes?(question)
15
+ !!ask(question)
16
+ end
17
+
18
+ def ask(question, options = {})
19
+ ::Clif::Questioner.new(options).ask(question)
20
+ end
21
+ alias :prompt :ask
22
+
23
+ def perform_action(action, message, &block)
24
+ explain(action, message)
25
+ yield unless pretend?
26
+ end
27
+
28
+ def explain(action, message)
29
+ say "#{title_for_action(action)}: #{message}"
30
+ end
31
+
32
+ def quiet?
33
+ @options[:quiet] || false
34
+ end
35
+
36
+ def pretend?
37
+ @options[:pretend] || false
38
+ end
39
+
40
+ private
41
+ def title_for_action(action)
42
+ action.to_s.rjust(indent)
43
+ end
44
+
45
+ def indent
46
+ @options[:indent] || 8
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,45 @@
1
+ class Clif
2
+ class Questioner
3
+
4
+ def initialize(options = {})
5
+ @options = options
6
+ end
7
+
8
+ def ask(question)
9
+ @question = question
10
+ ask_question
11
+ get_response
12
+ end
13
+
14
+ private
15
+ def ask_question
16
+ $stdout.print(formatted_question + " ")
17
+ end
18
+
19
+ def get_response
20
+ response = ($stdin.gets || '').chomp
21
+
22
+ if boolean_question?
23
+ boolean_response(response)
24
+ else
25
+ response
26
+ end
27
+ end
28
+
29
+ def boolean_response(response)
30
+ %w(y yes).include?(response.downcase) ? true : false
31
+ end
32
+
33
+ def formatted_question
34
+ if boolean_question?
35
+ "#{@question} [y/n]:"
36
+ else
37
+ @question =~ /\?$/ ? "#{@question}" : "#{@question}:"
38
+ end
39
+ end
40
+
41
+ def boolean_question?
42
+ @options[:boolean] || false
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,185 @@
1
+ require 'test_helper'
2
+ require 'clif'
3
+
4
+ class ClifTest < Test::Unit::TestCase
5
+
6
+ def setup
7
+ @real_stdout, $stdout = $stdout, StringIO.new
8
+ @real_stdin, $stdin = $stdin, StringIO.new
9
+
10
+ def $stdin.gets
11
+ $stdin.string
12
+ end
13
+ end
14
+
15
+ def teardown
16
+ $stdout = @real_stdout
17
+ $stdin = @real_stdin
18
+ end
19
+
20
+ context 'a Clif::CLI instance' do
21
+
22
+ setup { @cli = new_clif }
23
+
24
+ should 'be able to say things' do
25
+ expected = "Hello world"
26
+
27
+ assert_stdout(expected) { @cli.say 'Hello world' }
28
+ end
29
+
30
+ context 'when initialized with :quiet => true' do
31
+
32
+ setup { @cli = new_clif(:quiet => true) }
33
+
34
+ should 'communicate whether it should be quiet' do
35
+ assert @cli.quiet?
36
+ end
37
+
38
+ should 'not say things when asked to' do
39
+ expected = ''
40
+
41
+ assert_stdout(expected) { @cli.say 'Nothing' }
42
+ end
43
+ end
44
+
45
+ context 'when initialized without :quiet' do
46
+
47
+ setup { @cli = new_clif }
48
+
49
+ should 'return false when asked if it should be quiet' do
50
+ assert_equal false, @cli.quiet?
51
+ end
52
+ end
53
+
54
+ context 'when initialized with :pretend => true' do
55
+
56
+ setup { @cli = new_clif(:pretend => true, :quiet => true) }
57
+
58
+ should 'not actually do things when asked to' do
59
+ expected = nil
60
+ actual = @cli.perform_action(:add, '1 plus 1') { destroy_the_city }
61
+
62
+ assert_equal expected, actual
63
+ end
64
+ end
65
+
66
+ should 'be able to do things' do
67
+ expected = 2
68
+ actual = new_clif(:quiet => true).perform_action(:add, '1 plus 1') { 1 + 1 }
69
+ end
70
+
71
+ should 'explain its action before performing them' do
72
+ expected = ' add: 1 plus 1'
73
+
74
+ assert_stdout(expected) do
75
+ new_clif.perform_action(:add, '1 plus 1') { 1 + 1 }
76
+ end
77
+ end
78
+
79
+ should 'just explain things with no action' do
80
+ expected = ' skip: something'
81
+
82
+ assert_stdout(expected) do
83
+ new_clif.explain(:skip, 'something')
84
+ end
85
+ end
86
+
87
+ should 'not explain things when :quiet => true' do
88
+ expected = ''
89
+
90
+ assert_stdout(expected) do
91
+ new_clif(:quiet => true).explain(:do, 'something')
92
+ end
93
+ end
94
+
95
+ should 'allow for setting of indent level in explanations' do
96
+ expected = 'do: something'
97
+
98
+ assert_stdout(expected) do
99
+ new_clif(:indent => 0).explain(:do, 'something')
100
+ end
101
+ end
102
+
103
+ should 'be able to ask nicely formatted questions, without indenting' do
104
+ expected = formatted_question_for("What's your name?")
105
+
106
+ assert_stdout expected do
107
+ new_clif.ask("What's your name?")
108
+ end
109
+ end
110
+
111
+ should 'alias :ask as :prompt' do
112
+ expected = formatted_question_for('Your name')
113
+
114
+ assert_stdout expected do
115
+ new_clif.prompt('Your name')
116
+ end
117
+ end
118
+
119
+ should 'ask nicely formatted yes/no questions, if :boolean => true' do
120
+ expected = formatted_question_for('Launch rockets?', :boolean => true)
121
+
122
+ assert_stdout expected do
123
+ new_clif.ask('Launch rockets?', :boolean => true)
124
+ end
125
+ end
126
+
127
+ should 'have a shortcut for getting a yes? answer' do
128
+ expected = formatted_question_for('Do something?')
129
+
130
+ assert_stdout expected do
131
+ assert_respond_to @cli, :yes?
132
+ @cli.yes?('Do something?')
133
+ end
134
+ end
135
+
136
+ should 'have a shortcut for getting a no? answer' do
137
+ expected = formatted_question_for('Do something?')
138
+
139
+ assert_stdout expected do
140
+ assert_respond_to @cli, :no?
141
+ @cli.no?('Do something?')
142
+ end
143
+ end
144
+
145
+ context 'asking boolean questions' do
146
+ should 'turn Y into true' do
147
+ expected = true
148
+
149
+ with_input('Y') do
150
+ actual = @cli.ask('Are you there?', :boolean => true)
151
+ assert_equal expected, actual, "Expected 'Y' to produce #{expected} (got #{actual})"
152
+ end
153
+ end
154
+
155
+ should 'turn y into true' do
156
+ expected = true
157
+
158
+ with_input('y') do
159
+ actual = @cli.ask('Are you there?', :boolean => true)
160
+ assert_equal expected, actual, "Expected 'y' to produce #{expected} (got #{actual})"
161
+ end
162
+ end
163
+
164
+ should 'turn N into false' do
165
+ expected = false
166
+
167
+ with_input('N') do
168
+ actual = @cli.ask('Are you there?', :boolean => true)
169
+ assert_equal expected, actual, "Expected 'N' to produce #{expected} (got #{actual})"
170
+ end
171
+ end
172
+
173
+ should 'turn n into false' do
174
+ expected = false
175
+
176
+ with_input('n') do
177
+ actual = @cli.ask('Are you there?', :boolean => true)
178
+ assert_equal expected, actual, "Expected 'n' to produce #{expected} (got #{actual})"
179
+ end
180
+ end
181
+ end
182
+
183
+ end
184
+
185
+ end
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'redgreen'
5
+
6
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
7
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
8
+ require 'clif'
9
+
10
+ class Test::Unit::TestCase
11
+
12
+ def new_clif(options = {})
13
+ Clif.new(options)
14
+ end
15
+
16
+ def formatted_question_for(message, options = {})
17
+ if options[:boolean]
18
+ "#{message} [y/n]: "
19
+ else
20
+ message =~ /\?$/ ? "#{message} " : "#{message}: "
21
+ end
22
+ end
23
+
24
+ def assert_stdout(expected, &block)
25
+ result = silently(&block)
26
+ assert_equal expected, result
27
+ end
28
+
29
+ def with_input(text, &block)
30
+ silently do
31
+ $stdin.write(text); yield
32
+ end
33
+ ensure
34
+ $stdin.string.replace('')
35
+ end
36
+
37
+ def silently(&block)
38
+ yield; $stdout.string.chomp
39
+ end
40
+
41
+ end
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: clif
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - jameswilding
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-21 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: thoughtbot-shoulda
17
+ type: :development
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: "0"
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: redgreen
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ description: clif (Command Line InterFace) acts as a bridge between your code and the command line
36
+ email: james@jameswilding.net
37
+ executables: []
38
+
39
+ extensions: []
40
+
41
+ extra_rdoc_files:
42
+ - LICENSE
43
+ - README.markdown
44
+ files:
45
+ - .document
46
+ - .gitignore
47
+ - LICENSE
48
+ - README.markdown
49
+ - Rakefile
50
+ - VERSION
51
+ - lib/clif.rb
52
+ - lib/clif/helper.rb
53
+ - lib/clif/questioner.rb
54
+ - test/clif_test.rb
55
+ - test/test_helper.rb
56
+ has_rdoc: true
57
+ homepage: http://github.com/jameswilding/clif
58
+ licenses: []
59
+
60
+ post_install_message:
61
+ rdoc_options:
62
+ - --charset=UTF-8
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: "0"
70
+ version:
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: "0"
76
+ version:
77
+ requirements: []
78
+
79
+ rubyforge_project:
80
+ rubygems_version: 1.3.5
81
+ signing_key:
82
+ specification_version: 3
83
+ summary: Ready-made command line interface for ruby libraries
84
+ test_files:
85
+ - test/clif_test.rb
86
+ - test/test_helper.rb