shake 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.
data/README.md ADDED
@@ -0,0 +1,229 @@
1
+ Shake
2
+ =====
3
+
4
+ **Simple command runner.**
5
+
6
+ Goals:
7
+
8
+ * Shake lets you create extendable CLI runners very easily.
9
+
10
+ * Shake intends to replicate Thor/Rake's basic functionality in one very small package.
11
+
12
+ Why not Rake or Thor?
13
+
14
+ * Shake starts up way faster than Thor and Rake.
15
+ * Rake doesn't support arguments.
16
+ * Rake can't be extended easily to support anything more than `rake` + `Rakefile`.
17
+ * Thor can be too huge for your purposes.
18
+
19
+ Shake was made with the idea of being easily-embeddable into your projects
20
+ for your command line runners. It's a single ~4kb Ruby file.
21
+
22
+ # Shakefile
23
+ # Place this file in your project somewhere
24
+ class Shake
25
+ task(:start) {
26
+ puts "Starting '#{params.join(' ')}'..."
27
+ }
28
+
29
+ task.description = "Starts something"
30
+
31
+ task(:stop) {
32
+ puts "Stopping..."
33
+ }
34
+
35
+ task.description = "Stops it"
36
+ end
37
+
38
+ In your shell:
39
+
40
+ $ shake start server
41
+ Starting 'server'...
42
+
43
+ $ shake stop
44
+ Stopping...
45
+
46
+ $ shake help
47
+ Commands:
48
+ start Starts something
49
+ stop Stops it
50
+ help Shows a list of commands
51
+
52
+ Usage
53
+ =====
54
+
55
+ ### Using Shakefiles
56
+
57
+ Using the command `shake` will load your project's `Shakefile`.
58
+
59
+ # ~/project/Shakefile
60
+ class Shake
61
+ task(:deploy) do
62
+ puts "Deploying..."
63
+ system "ssh admin@server.com git pull && thin restart"
64
+ end
65
+ end
66
+
67
+ And in your shell:
68
+
69
+ $ cd ~/project
70
+ $ shake deploy
71
+ Deploying...
72
+
73
+ ### Parameters
74
+
75
+ Get the parameters with `params` (an array). Verify parameters with `wrong_usage`.
76
+
77
+ task(:init) do
78
+ wrong_usage if params.empty?
79
+ system "wget #{params.first}"
80
+ end
81
+
82
+ Example:
83
+
84
+ $ shake init
85
+ Invalid usage.
86
+ See `shake help` for more information.
87
+
88
+ ### Parameter options and flags
89
+
90
+ You may get params from it with `params.extract` or `params.delete`. Doing `extract`
91
+ will remove it from params.
92
+
93
+ task(:create) do
94
+ type = params.extract('-t') || 'default'
95
+ quiet = params.delete('-q')
96
+ file = params.shift
97
+ wrong_usage if params.any?
98
+
99
+ puts "Creating '#{file}' (quiet: #{!!quiet}, type: #{type})"
100
+ end
101
+
102
+ Example:
103
+
104
+ $ shake create #=> Invalid
105
+ $ shake create foobar #=> Creating 'foobar' (quiet: false, type: default)
106
+ $ shake create foobar -q #=> Creating 'foobar' (quiet: true, type: default)
107
+ $ shake create foobar -t xyz #=> Creating 'foobar' (quiet: false, type: xyz)
108
+
109
+ # Common commands
110
+
111
+ Use `err` to print something to STDERR. Use `pass` to halt execution.
112
+
113
+ task(:delete) do
114
+ unless File.exists?(params.first)
115
+ err 'You can't delete something that doesn't exist!'
116
+ pass
117
+ end
118
+
119
+ FileUtils.rm_rf params.first
120
+ end
121
+
122
+ You may also pass parameters to `pass` to have it printed out before halting.
123
+
124
+ pass 'The target already exists.' if File.exists?(target)
125
+
126
+ ### Default tasks
127
+
128
+ Use `default` to specify a default task. (The default task is usually `help`)
129
+
130
+ class Shake
131
+ task(:test) do
132
+ Dir['test/**/*_test.rb'].each { |f| load f }
133
+ end
134
+
135
+ default :test
136
+ end
137
+
138
+ # Typing `shake` will be the same as `shake test`
139
+
140
+ ### Invalid commands
141
+
142
+ Use `invalid` to define what happens when
143
+
144
+ class Shake
145
+ invalid {
146
+ err "Invalid command. What's wrong with you?"
147
+ }
148
+ end
149
+
150
+ In your shell:
151
+
152
+ $ shake foobar
153
+ Invalid command. What's wrong with you?
154
+
155
+ ### Defining helpers
156
+
157
+ Tasks are executed in the class's context, so just define your helpers like so:
158
+
159
+ module Helpers
160
+ def say_status(what, str)
161
+ puts "%15s %s" % [ what, str ]
162
+ end
163
+ end
164
+
165
+ class Shake
166
+ extend Helpers
167
+ end
168
+
169
+ Then use them in your tasks.
170
+
171
+ class Shake
172
+ task(:info) do
173
+ say_status :info, "It's a fine day"
174
+ end
175
+ end
176
+
177
+ ### Manual invocation
178
+
179
+ You can use shake in your projects without using the `shake` command. (recommended!)
180
+
181
+ require 'shake'
182
+
183
+ # If you want to load your own project file (optional)
184
+ file = Shake.find_in_project('Projectfile') and load file
185
+
186
+ # Now define some tasks, and then:
187
+ Shake.run!
188
+
189
+ ### Subclassing Shake
190
+
191
+ You may subclass shake for your own project.
192
+
193
+ By default, it will not have any of the default tasks (that is, `shake help`, and
194
+ the "invalid command" message). Use `include Defaults` if you want this behavior.
195
+
196
+ require 'shake'
197
+
198
+ class CLI < Shake
199
+ include Defaults # optional, see above
200
+
201
+ task(:flip) do
202
+ what = rand < 0.5 ? "heads" : "tails"
203
+ puts "The coin says #{what}"
204
+ end
205
+ end
206
+
207
+ CLI.run!
208
+
209
+ ### Defining tasks
210
+
211
+ # In your Shakefile or something
212
+ class Shake
213
+ task(:reset) do
214
+ # ...
215
+ end
216
+
217
+ task.description = "Resets passwords."
218
+ end
219
+
220
+ Alternatively:
221
+
222
+ class Shake
223
+ task(:reset) do
224
+ # ...
225
+ end
226
+
227
+ task(:reset).description = "Resets passwords."
228
+ end
229
+
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ task :test do
2
+ Dir['test/**/*_test.rb'].each { |f| load f }
3
+ end
4
+
5
+ task :default => :test
data/examples/example ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+ require 'shake'
3
+
4
+ ## Example
5
+ # This is a very simple example of using Shake with a subclass.
6
+
7
+ class Monk < Shake
8
+ # Include the Shake default commands (like `help` and stuff)
9
+ # This is optional, but you probably want this for most cases.
10
+ include Defaults
11
+
12
+ # Define a task like this.
13
+ # The other command line options will be accesible as the
14
+ # `params` array. Pass it onto Clap or something.
15
+ task(:start) {
16
+ puts "Running..."
17
+ puts "Your options: #{params.inspect}"
18
+ }
19
+
20
+ # Just `task` will return the last defined task.
21
+ # By the way, only `help` cares about the description (skip
22
+ # it if you wish), and you can add as much metadata to the
23
+ # tasks as you need.
24
+ task.description = "Starts the server"
25
+
26
+ # You may also define task metadata like this.
27
+ # Note that there are no namespaces, but feel free to use
28
+ # the colon anyway.
29
+ task(:'redis:start', :description => "Runs redis") {
30
+ puts "Starting redis..."
31
+ }
32
+
33
+ # Or this way:
34
+ task(:stop) {
35
+ puts "Stopping..."
36
+ }
37
+
38
+ task(:stop).description = "Stops the server"
39
+ end
40
+
41
+ # This gets ARGV and dispatches the appropriate task
42
+ Monk.run!
43
+
44
+ # Example output:
45
+ #
46
+ # $ ./monk
47
+ # Usage: monk <command>
48
+ #
49
+ # Commands:
50
+ # help Shows a list of commands
51
+ # start Starts the server
52
+ # redis:start Runs redis
53
+ # stop Stops the server
54
+ #
55
+ # $ ./monk start production
56
+ # Running...
57
+ # Your options: ['production']
58
+ #
59
+ # $ ./monk aaaa
60
+ # Unknown command: aaaa
61
+ # See `monk help` for a list of commands.
data/examples/example2 ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+ require 'shake'
3
+
4
+ ## Shake
5
+ # Shake is a very simple Thor/Rake replacement. It intends
6
+ # to replicate thor's basic functionality in <100loc.
7
+ #
8
+ # This is a very simple example.
9
+ #
10
+ Shake.task(:hello) {
11
+ puts "hi!"
12
+ }
13
+
14
+ Shake.run!
15
+
16
+ # Example output:
17
+ #
18
+ # $ ./example
19
+ # Usage: example <command>
20
+ #
21
+ # Commands:
22
+ # help
23
+ # hello
24
+ #
25
+ # $ ./example hello
26
+ # hi!
27
+ #
28
+ # $ ./example aaaa
29
+ # Unknown command: aaaa
30
+ # See `example help` for a list of commands.
data/lib/shake.rb ADDED
@@ -0,0 +1,186 @@
1
+ require 'ostruct'
2
+
3
+ class Shake
4
+ VERSION = "0.1.0"
5
+
6
+ Abort = Class.new(StandardError)
7
+
8
+ class << self
9
+ attr_reader :params
10
+ attr_reader :command
11
+
12
+ # By the way: every task gets evaluated in the context of the class.
13
+ # Just make more static methods (`def self.monkey`) if you need them
14
+ # in your tasks.
15
+ #
16
+ # This also means that all the methods here are available for use
17
+ # in your tasks.
18
+
19
+ # Returns a list of tasks.
20
+ # It gives out a hash with symbols as keys, and openstructs as values.
21
+ def tasks
22
+ @tasks ||= Hash.new
23
+ end
24
+
25
+ # Sets or retrieves a task.
26
+ # If no arguments are given, it returns the last task defined.
27
+ #
28
+ # Examples:
29
+ #
30
+ # task(:start) { ... }
31
+ # task(:start, description: 'Starts it') { ... }
32
+ # task(:start)
33
+ # task.description = "Starts it"
34
+ #
35
+ def task(what=nil, options={}, &blk)
36
+ return @last if what.nil?
37
+
38
+ key = what.downcase.to_sym
39
+ @last = tasks[key] = new_task(options, &blk) if block_given?
40
+ tasks[key]
41
+ end
42
+
43
+ # Sets or retrieves the default task.
44
+ #
45
+ # Examples:
46
+ #
47
+ # default :default_task
48
+ # default { ... }
49
+ #
50
+ def default(what=nil, &blk)
51
+ @default = what || blk || @default
52
+ end
53
+
54
+ # Sets or retrieves the 'invalid command' task.
55
+ # See `default` for examples.
56
+ def invalid(what=nil, &blk)
57
+ @invalid = what || blk || @invalid
58
+ end
59
+
60
+ # Invokes a task with the given arguments.
61
+ # You may even nest multiple task invocations.
62
+ #
63
+ # Examples:
64
+ #
65
+ # invoke(:start)
66
+ # invoke(:start, 'nginx', 'memcache')
67
+ #
68
+ def invoke(what, *args)
69
+ old, @params = @params, args.extend(Params)
70
+ return if what.nil?
71
+
72
+ begin
73
+ return instance_eval(&what) if what.is_a?(Proc)
74
+
75
+ task = task(what) or return nil
76
+ instance_eval &task.proc
77
+ true
78
+ rescue Abort
79
+ true
80
+ ensure
81
+ @params = old
82
+ end
83
+ end
84
+
85
+ # Stops the execution of a task.
86
+ def pass(msg=nil)
87
+ err msg unless msg.nil?
88
+ raise Abort
89
+ end
90
+
91
+ # Halts a task because of wrong usage.
92
+ #
93
+ # Example:
94
+ #
95
+ # wrong_usage if params.any?
96
+ #
97
+ def wrong_usage
98
+ invoke(invalid) and pass
99
+ end
100
+
101
+ # Runs with the given arguments and dispatches the appropriate task.
102
+ # Use `run!` if you want to go with command line arguments.
103
+ #
104
+ # Example:
105
+ #
106
+ # # This is equivalent to `invoke(:start, 'nginx')`
107
+ # run 'start', 'nginx'
108
+ #
109
+ def run(*argv)
110
+ return invoke(default) if argv.empty?
111
+
112
+ @command = argv.shift
113
+ invoke(@command, *argv) or invoke(invalid, *argv)
114
+ end
115
+
116
+ def run!
117
+ run *ARGV
118
+ end
119
+
120
+ def executable
121
+ File.basename($0)
122
+ end
123
+
124
+ def err(str="")
125
+ $stderr.write "#{str}\n"
126
+ end
127
+
128
+ # Traverse back it's parents and find the file called `file`.
129
+ def find_in_project(file)
130
+ dir = Dir.pwd
131
+ while true
132
+ path = File.join(dir, file)
133
+ return path if File.exists?(path)
134
+
135
+ parent = File.expand_path(File.join(dir, '..'))
136
+ return nil if parent == dir
137
+
138
+ dir = parent
139
+ end
140
+ end
141
+
142
+ protected
143
+ def new_task(options={}, &blk)
144
+ t = OpenStruct.new(options)
145
+ t.proc = blk
146
+ t
147
+ end
148
+ end
149
+
150
+ module Params
151
+ def extract(what)
152
+ i = index(what) and slice!(i, 2)[1]
153
+ end
154
+ end
155
+
156
+ # This is a list of default commands.
157
+ # The class Shake itself uses this, but if you subclass it,
158
+ # you will have to do `include Defaults` yourself.
159
+ module Defaults
160
+ def self.included(to)
161
+ to.default :help
162
+
163
+ to.task(:help) {
164
+ err "Usage: #{executable} <command>"
165
+ err
166
+ err "Commands:"
167
+ tasks.each { |name, task| err " %-20s %s" % [ name, task.description ] }
168
+ }
169
+
170
+ to.task(:help).description = "Shows a list of commands"
171
+
172
+ to.invalid {
173
+ if task(command)
174
+ err "Invalid usage."
175
+ err "See `#{executable} help` for more info."
176
+ else
177
+ err "Unknown command: #{command}"
178
+ err "See `#{executable} help` for a list of commands."
179
+ end
180
+ }
181
+ end
182
+ end
183
+
184
+ include Defaults
185
+ end
186
+
data/test/mock.rb ADDED
@@ -0,0 +1,9 @@
1
+ class Shake
2
+ def self.puts(str='')
3
+ $out << "#{str}\n"
4
+ end
5
+
6
+ def self.err(str='')
7
+ $err << "#{str}\n"
8
+ end
9
+ end
@@ -0,0 +1,47 @@
1
+ $: << File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'shake'
4
+ require 'contest'
5
+ require 'fileutils'
6
+
7
+ require File.expand_path('../mock', __FILE__)
8
+
9
+ class Test::Unit::TestCase
10
+ def temp_path(*a)
11
+ root %w(test tmp) + a
12
+ end
13
+
14
+ def root(*a)
15
+ File.join File.expand_path('../../', __FILE__), *a
16
+ end
17
+
18
+ setup do
19
+ $out, $err = '', ''
20
+ FileUtils.rm_rf temp_path if File.directory?(temp_path)
21
+ FileUtils.mkdir_p temp_path
22
+ Dir.chdir temp_path
23
+ end
24
+
25
+ teardown do
26
+ FileUtils.rm_rf temp_path
27
+ end
28
+
29
+ def cout
30
+ $out
31
+ end
32
+
33
+ def cerr
34
+ $err
35
+ end
36
+
37
+ # Simulate bin/shake
38
+ def shake(*args)
39
+ ARGV.slice! 0, ARGV.length
40
+ args.each { |a| ARGV << a }
41
+ load File.expand_path('../../bin/shake', __FILE__)
42
+ end
43
+
44
+ def shakefile(str)
45
+ File.open('Shakefile', 'w') { |f| f.write str.strip }
46
+ end
47
+ end
@@ -0,0 +1,72 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class ParamsTest < Test::Unit::TestCase
4
+ class Bake < ::Shake
5
+ task :heat do
6
+ wrong_usage if params.size != 1
7
+ puts "OK"
8
+ end
9
+
10
+ task :cool do
11
+ quiet = params.delete('-q')
12
+ type = params.extract('-t') || params.extract('--type') || 'default'
13
+ wrong_usage unless params.empty?
14
+
15
+ puts "Quiet #{!!quiet}, type #{type}"
16
+ end
17
+
18
+ invalid do
19
+ err "What?"
20
+ end
21
+ end
22
+
23
+ test 'wrong usage' do
24
+ Bake.run *%w{heat}
25
+ assert cerr == "What?\n"
26
+ end
27
+
28
+ test 'wrong usage 2' do
29
+ Bake.run *%w{heat x y}
30
+ assert cerr == "What?\n"
31
+ end
32
+
33
+ test 'proper usage' do
34
+ Bake.run *%w{heat x}
35
+ assert cout == "OK\n"
36
+ end
37
+
38
+ test 'cool' do
39
+ Bake.run 'cool'
40
+ assert cout == "Quiet false, type default\n"
41
+ end
42
+
43
+ test 'wrong usage' do
44
+ Bake.run *%w{cool down}
45
+ assert cerr == "What?\n"
46
+ end
47
+
48
+ test 'cool 2' do
49
+ Bake.run *%w{cool -q}
50
+ assert cout == "Quiet true, type default\n"
51
+ end
52
+
53
+ test 'cool 3' do
54
+ Bake.run *%w{cool -t special}
55
+ assert cout == "Quiet false, type special\n"
56
+ end
57
+
58
+ test 'cool 4' do
59
+ Bake.run *%w{cool -t special -q}
60
+ assert cout == "Quiet true, type special\n"
61
+ end
62
+
63
+ test 'cool 5' do
64
+ Bake.run *%w{cool -q -t}
65
+ assert cout == "Quiet true, type default\n"
66
+ end
67
+
68
+ test 'wrong usage again' do
69
+ Bake.run *%w{cool -q xyz}
70
+ assert cerr == "What?\n"
71
+ end
72
+ end
@@ -0,0 +1,44 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class ShakefileTest < Test::Unit::TestCase
4
+ test 'no shakefile' do
5
+ shake
6
+ assert cout.empty?
7
+ assert cerr.include?('No Shakefile')
8
+ end
9
+
10
+ context 'basics' do
11
+ setup do
12
+ shakefile %{
13
+ class Shake
14
+ task :start do
15
+ puts "Starting..."
16
+ puts params.inspect if params.any?
17
+ end
18
+ task.description = 'Starts the server'
19
+ end
20
+ }
21
+ end
22
+
23
+ test 'help' do
24
+ shake 'help'
25
+ assert cout.empty?
26
+ assert cerr =~ /^ start *Starts the server/
27
+ end
28
+
29
+ test 'invalid command' do
30
+ shake 'stop'
31
+ assert cerr.include?('Unknown command: stop')
32
+ end
33
+
34
+ test 'run' do
35
+ shake 'start'
36
+ assert_equal "Starting...\n", cout
37
+ end
38
+
39
+ test 'run with args' do
40
+ shake *%w(start x)
41
+ assert_equal "Starting...\n[\"x\"]\n", cout
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class SubclassTest < Test::Unit::TestCase
4
+ class Bake < ::Shake
5
+ task :melt do
6
+ puts "Working..."
7
+ end
8
+
9
+ invalid do
10
+ err "What?"
11
+ end
12
+ end
13
+
14
+ test 'subclassing' do
15
+ Bake.run 'melt'
16
+ assert cout == "Working...\n"
17
+ end
18
+
19
+ test 'nothing to do' do
20
+ Bake.run
21
+ assert cerr.empty?
22
+ assert cout.empty?
23
+ end
24
+
25
+ test 'invalid command' do
26
+ Bake.run 'foobar'
27
+ assert cerr == "What?\n"
28
+ end
29
+ end
@@ -0,0 +1,15 @@
1
+ require File.expand_path('../../test_helper', __FILE__)
2
+
3
+ class TraverseTest < Test::Unit::TestCase
4
+ test 'traverse backwards' do
5
+ shakefile %{
6
+ Shake.task(:create) { puts 'Creating...' }
7
+ }
8
+
9
+ Dir.mkdir 'xxx'
10
+ Dir.chdir 'xxx'
11
+
12
+ shake 'create'
13
+ assert_equal "Creating...\n", cout
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: shake
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.1.0
6
+ platform: ruby
7
+ authors:
8
+ - Rico Sta. Cruz
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-02-15 00:00:00 +08:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: Shake is a simple replacement for Thor/Rake.
18
+ email:
19
+ - rico@sinefunc.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files: []
25
+
26
+ files:
27
+ - lib/shake.rb
28
+ - test/mock.rb
29
+ - test/test_helper.rb
30
+ - test/unit/params_test.rb
31
+ - test/unit/shakefile_test.rb
32
+ - test/unit/subclass_test.rb
33
+ - test/unit/traverse_test.rb
34
+ - examples/example
35
+ - examples/example2
36
+ - README.md
37
+ - Rakefile
38
+ has_rdoc: true
39
+ homepage: http://github.com/rstacruz/shake
40
+ licenses: []
41
+
42
+ post_install_message:
43
+ rdoc_options: []
44
+
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ none: false
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: "0"
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ version: "0"
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.5.0
63
+ signing_key:
64
+ specification_version: 3
65
+ summary: Simple command line runner.
66
+ test_files: []
67
+