prick 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,316 @@
1
+ #!/usr/bin/bash
2
+
3
+ # Name
4
+ # prick - Database project tool
5
+ #
6
+ # Usage
7
+ # prick prepare|migrate|release
8
+ #
9
+ # Commands
10
+ # prepare NEW-RELEASE
11
+ # Stamps NEW-RELEASE into the database
12
+ # migrate [OLD-RELEASE]
13
+ # Create a migration from OLD-RELEASE to the prepared release.
14
+ # OLD-RELEASE defaults to the last registered release
15
+ # release
16
+ # Release the prepared release
17
+ #
18
+ # Files
19
+ # prick use the following sub-directories:
20
+ #
21
+ # releases/
22
+ # <database>-<release>.dump.gz
23
+ # ...
24
+ # migrations/
25
+ # migrate-<from>-<to>[.sql]
26
+ # ...
27
+ #
28
+ # The releases directory contains schema backups that makes it easy to
29
+ # restore a earlier release used to generated migrations. The migrations directory
30
+ # contains migrations from one release to another. 'prick migrate' generates
31
+ # a SQL migration file but you can replace it with an executable if needed
32
+ #
33
+
34
+ # TODO: Schema-only dump of database
35
+ # pg_dump --no-owner --no-privileges --schema-only name_of_database -f schema.dump.sql
36
+
37
+
38
+ set -e
39
+
40
+ . bash.include
41
+
42
+ USAGE="prepare NEW-RELEASE
43
+ migrate OLD-RELEASE
44
+ release"
45
+
46
+ DB=mikras
47
+
48
+ function inoa() {
49
+ error "Illegal number of arguments"
50
+ }
51
+
52
+ [ $# -ge 1 ] || inoa
53
+ CMD=$1
54
+ shift
55
+
56
+ trap 'eval rm -rf $ERRFILE $TMPFILE $TMPDIR' EXIT
57
+ ERRFILE=$(mktemp --tmpdir $PROGRAM.XXXXXXXXXX)
58
+ TMPFILE=$(mktemp --tmpdir $PROGRAM.XXXXXXXXXX)
59
+ TMPDIR=$(mktemp --tmpdir --directory $PROGRAM.XXXXXXXXXX)
60
+ #echo $TMPFILE
61
+ #echo $TMPDIR
62
+
63
+ function last_release() {
64
+ local path=$(ls releases/$DB-*.*.*.dump.gz 2>/dev/null | tail -1)
65
+ local release=$(echo $path | sed -e "s/releases\/$DB-//" -e 's/\.dump\.gz$//')
66
+ echo ${release:-0.0.0}
67
+ }
68
+
69
+ function current_release() {
70
+ psql -qtAX -d $DB -c "select major || '.' || minor || '.' || patch from meta.versions"
71
+ }
72
+
73
+ function set_current_release() {
74
+ set -- $(echo $1 | sed 's/\./ /g')
75
+ local major=$1
76
+ local minor=$2
77
+ local patch=$3
78
+
79
+ psql -qtAX -d $DB -c "update meta.versions set major = $major, minor = $minor, patch = $patch"
80
+ }
81
+
82
+ function has_release() {
83
+ local release=$1
84
+ local db=$DB-$release
85
+ psql -qtAX -d $DB -l | grep -q "^$db|" &>/dev/null
86
+ }
87
+
88
+ function database_load() {
89
+ local release=$1
90
+ local db=$DB-$release
91
+ local dump=releases/$db.dump.gz
92
+ [ -f $dump ] || error "Can't find release dump $dump"
93
+ [ ! has_release ] || error "Release $release is already loaded"
94
+ createdb $db
95
+ gzip --to-stdout $dump | psql -d $db
96
+ }
97
+
98
+ function redatabase_load() {
99
+ local release=$1
100
+ local db=$DB-$release
101
+ local dump=releases/$db.dump.gz
102
+ [ -f $dump ] || error "Can't find release dump $dump"
103
+ [ ! has_release ] || dropdb $db
104
+ createdb $db
105
+ gzip --to-stdout $dump | psql -d $db
106
+ }
107
+
108
+
109
+ function prepare() {
110
+ local new_release=$1
111
+ mesg "Preparing $new_release"
112
+
113
+ [ ! -f $new_release ] || error "New release already exists: $new_release_dump"
114
+ set_current_release $new_release
115
+ }
116
+
117
+ function migrate() {
118
+ local old_release=$1
119
+ local new_release=$(current_release)
120
+ local old_release_dump=releases/$DB-$old_release.dump.gz
121
+ local new_release_dump=releases/$DB-$new_release_dump.gz
122
+ local migration=migrations/migrate-$old_release-$new_release.sql
123
+
124
+ mesg "Migrating $old_release to $new_release"
125
+
126
+ [ -f $old_release_dump ] || error "Can't find old release: $old_release_dump"
127
+ [ ! -f $new_release_dump ] || error "New release already exists: $new_release_dump"
128
+ [ "$old_release" != "$new_release" ] ||
129
+ error "Can't migrate $old_release to itself (did you forget to prepare the release?)"
130
+ [ ! -f $migration ] || error "Won't overwrite migration $migration"
131
+
132
+ has_release $old_release || database_load $old_release
133
+ # migra --unsafe postgresql:///$DB-$old_release postgresql:///$DB || true #>$migration
134
+ migra --unsafe postgresql:///$DB-$old_release postgresql:///$DB >$migration || {
135
+ case $? in
136
+ 0)
137
+ mesg "No changes"
138
+ ;;
139
+ 2)
140
+ mesg "Generated $migration"
141
+ ;;
142
+ *)
143
+ rm -f $migration
144
+ fail "migra failed"
145
+ ;;
146
+ esac
147
+ }
148
+ }
149
+
150
+ function database() {
151
+ local release=$1
152
+ echo "$DB-$release"
153
+ }
154
+
155
+ function dumpfile() {
156
+ local release=$1
157
+ echo "releases/$DB-$release.dump.gz"
158
+ }
159
+
160
+ ###
161
+ ### R E L E A S E
162
+ ###
163
+
164
+ function release_default() {
165
+ psql -qtAX -d $DB -c "select major || '.' || minor || '.' || patch from meta.versions"
166
+ }
167
+
168
+ function release_exist() {
169
+ local release=$1
170
+ local dump=$(dumpfile $release)
171
+ [ -f $dump ]
172
+ }
173
+
174
+ ###
175
+ ### M I G R A T I O N S
176
+ ###
177
+
178
+ function migration_create() {
179
+ local old_release=$1
180
+ local new_release=$(release_default)
181
+ }
182
+
183
+ function migration_apply() {
184
+ echo niy
185
+ }
186
+
187
+
188
+ ###
189
+ ### D A T A B A S E
190
+ ###
191
+
192
+ function database_loaded() {
193
+ local release=$1
194
+ local db=$(database $release)
195
+ psql -qtAX -d $DB -l | grep -q "^$db|" &>/dev/null
196
+ }
197
+
198
+ function database_build() {
199
+ local release=$1
200
+ local db=$(database $release)
201
+ ! database_loaded $release || fail "Database $db is already loaded"
202
+ mesg "Checking out release $release"
203
+ git archive --format=tar $release >$TMPFILE
204
+ (
205
+ cd $TMPDIR
206
+ tar xf $TMPFILE
207
+
208
+ # No git access so set spec.files to []
209
+ sed -i -e '/^ *spec.files/,+2d' mikras_db.gemspec -e 's/spec.files/[]/' mikras_db.gemspec
210
+
211
+ mesg "Running bundle"
212
+ bundle &>$ERRFILE || {
213
+ cat $ERRFILE >&2
214
+ fail "bundle failed"
215
+ }
216
+
217
+ # Set databse name
218
+ sed -i "s/^DB=.*/DB=$(database $release)/" exe/build
219
+
220
+ # Quote variables properly
221
+ perl -pi -e 's/:(\w+)/:"\1"/g' schemas/db.sql
222
+
223
+ # Replace schemas/skuffejern/users.sql - it is not possible to drop users because we
224
+ # run several versions of the database
225
+ for file in $(ls schemas/*/users.sql 2>/dev/null); do
226
+ {
227
+ psql -qtAX -d template1 \
228
+ -c "select 1 from pg_user where pg_user.usename = 'anonymous'" \
229
+ | grep -q 1 || psql -qtAX -d template1 -c "create user anonymous"
230
+
231
+ psql -qtAX -d template1 \
232
+ -c "select 1 from pg_roles where pg_roles.rolname = 'skuffejern_access'" \
233
+ | grep -q 1 || psql -qtAX -d template1 -c "create role skuffejern_access"
234
+ } >$file
235
+ done
236
+
237
+ mesg "Running rebuild"
238
+ ./rebuild &>$ERRFILE || {
239
+ cat $ERRFILE >&2
240
+ fail "rebuild failed"
241
+ }
242
+ )
243
+ }
244
+
245
+ function database_save() {
246
+ local release=$1
247
+ local db=$(database $release)
248
+ local dump=releases/$db.dump.gz
249
+ [ ! -f $dump ] || fail "Won't overwrite release file $dump"
250
+ # pg_dump --no-owner --no-privileges --schema-only name_of_database -f schema.dump.sql
251
+ pg_dump --no-owner --schema-only $db | gzip --to-stdout >$dump
252
+ }
253
+
254
+ function database_load() {
255
+ local release=$1
256
+ local db=$(database $release)
257
+ local dump=releases/$db.dump.gz
258
+ ! database_loaded $db || fail "Database $db is already loaded"
259
+ [ -f $dump ] || fail "Can't find release file $dump"
260
+ createdb $db
261
+ gunzip --to-stdout $dump | psql -d $db &>$ERRFILE || {
262
+ cat $ERRFILE
263
+ fail "psql failed"
264
+ }
265
+ }
266
+
267
+ function database_drop() {
268
+ local release=$1
269
+ local db=$(database $release)
270
+ dropdb $db
271
+ }
272
+
273
+
274
+ case $CMD in
275
+ list)
276
+ [ $# = 0 ] || inoa
277
+ psql -qtAX -d template1 -l | sed -n "/mikras[-0-9.]*|/s/|.*//p"
278
+ ;;
279
+ build)
280
+ [ $# = 1 ] || inoa
281
+ database_build $1
282
+ ;;
283
+ save)
284
+ [ $# = 1 ] || inoa
285
+ database_save $1
286
+ ;;
287
+ load)
288
+ [ $# = 1 ] || inoa
289
+ database_load $1
290
+ ;;
291
+ reload)
292
+ [ $# = 1 ] || inoa
293
+ ! database_loaded $1 || database_drop $1
294
+ database_load $1
295
+ ;;
296
+ drop)
297
+ [ $# = 1 ] || inoa
298
+ database_drop $1
299
+ ;;
300
+ prepare)
301
+ [ $# = 1 ] || inoa
302
+ prepare $1
303
+ ;;
304
+ migrate) # TODO: prick migrate from-release to-release
305
+ [ $# -le 1 ] || inoa
306
+ migrate ${1:-$(last_release)}
307
+ ;;
308
+ release)
309
+ mesg "Releasing"
310
+ ;;
311
+ *)
312
+ error "Illegal command: '$CMD'"
313
+ ;;
314
+ esac
315
+
316
+
@@ -0,0 +1,467 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'prick.rb'
4
+
5
+ require 'shellopts'
6
+ require 'indented_io'
7
+ require 'tempfile'
8
+ require 'ext/algorithm.rb'
9
+
10
+ include ShellOpts
11
+
12
+ # TODO: Backup & restore
13
+ SPEC = %(
14
+ n,name=NAME
15
+ C,directory=DIR
16
+ h,help
17
+ v,verbose
18
+ q,quiet
19
+ version
20
+
21
+ init!
22
+ info!
23
+ status!
24
+ history!
25
+
26
+ checkout!
27
+
28
+ feature!
29
+ rebase!
30
+
31
+ create! create.release! create.prerelease! create.feature! create.migration!
32
+ increment! increment.prerelease!
33
+
34
+ prepare! prepare.release! prepare.prerelease! prepare.feature! prepare.migration!
35
+ include!
36
+ commit!
37
+ prerelease!
38
+ release!
39
+
40
+ list! list.databases! list.releases! list.prereleases!
41
+ build!
42
+ use! f,file=FILE
43
+ load! f,file=FILE
44
+ reload! f,file=FILE
45
+ unload!
46
+ clean!
47
+
48
+ migrate!
49
+ )
50
+
51
+ USAGE = "-h -v -n NAME -C DIR COMMAND"
52
+
53
+ HELP = %(
54
+ NAME
55
+ prick - Database version management
56
+
57
+ USAGE
58
+ prick -v -n NAME -C DIR -h <command>
59
+
60
+ OPTIONS
61
+ -n, --name=NAME
62
+ Name of project. Defauls to the environment variable `PRICK_PROJECT` if
63
+ set and else the name of the current directory
64
+
65
+ -C, --directory=DIR
66
+ Change to directory DIR before anything else
67
+
68
+ -h, --help
69
+ Print this page
70
+
71
+ -v, --verbose
72
+ Be verbose
73
+
74
+ --version
75
+ Print prick version
76
+
77
+ COMMANDS
78
+ INFO COMMANDS
79
+ init [DIR]
80
+ Initialize a project directory
81
+
82
+ info
83
+ Print project information
84
+
85
+ status
86
+ Short status
87
+
88
+ GENERAL COMMANDS
89
+ checkout
90
+ As git checkout but does some extra work behind the scenes
91
+
92
+ migrate
93
+ Migrate from the current branch in the database to the current branch
94
+
95
+
96
+ DEVELOPER COMMANDS
97
+ create feature FEATURE
98
+ Create a new feature branch and a feature directory under
99
+ features/<base-version>. Note that the base version is the base version
100
+ of the pre-release
101
+
102
+ Features are named <feature>-<base-version>
103
+
104
+ prepare migration
105
+ Create migration files from the base release to the current schema. It
106
+ does not overwrite existing migration files
107
+
108
+ rebase VERSION
109
+ Rebase the feature to HEAD of the release. The rebased feature lives on
110
+ a branch of its own and have a symlink from the new base release to the
111
+ feature implementation in the original release. Rebased features are
112
+ named <feature>-<rebase-version>
113
+
114
+
115
+ RELEASE MANAGER COMMANDS
116
+ migrate VERSION|patch|minor|major
117
+ Prepare migration from current release to the given version. The branch
118
+ will be named <old-version>_<new-version>. Note that the generated
119
+ files don't include data migrations so it may be easier to first create a
120
+ prerelease and include missing features and then use that release as base
121
+ for an empty migration release that just changes the version number
122
+
123
+ (need some thought)
124
+
125
+ prepare VERSION|patch|minor|major
126
+ Prepare a new release by branching to <version>.pre.0 and creating a
127
+ features directory for the _base_ version. If version is one of the
128
+ names patch, minor, or major then the version is constructed by
129
+ incrementing the given part of the base release's version
130
+
131
+ include FEATURE
132
+ Merge a feature branch into the prepared release
133
+
134
+ prerelease
135
+ Bumps the pre-release serial and create a new branch
136
+
137
+ release [VERSION]
138
+ Create a new release. The version is not used if the release was prepared
139
+
140
+
141
+ MANIPULATING DATABASES AND RELEASES
142
+ list [databases|releases|prereleases]
143
+ Output a list of databases, releases, or prereleases. List databases by
144
+ default
145
+
146
+ build [VERSION]
147
+ Build the schema in the project database. If a version is given, the
148
+ version is built into the versioned database and the release cached
149
+ on disk
150
+
151
+ load
152
+ Load the current cache into the project database
153
+
154
+ load -f FILE [VERSION]
155
+ Load the cached release into its versioned database. It is an error if
156
+ the release isn't cached - use build to create it
157
+
158
+ reload [VERSION]
159
+ Clear the versioned database and reload the release from the cache
160
+
161
+ unload [VERSION]
162
+ Delete the versioned database. Default is to delete all project related
163
+ databases
164
+
165
+ clean [VERSION]
166
+ Delete the cached release. Default is to delete all cached releases
167
+
168
+
169
+ )
170
+
171
+ INFO_PARAMS = {
172
+ "Releases": :releases,
173
+ "Pre-releases": :prereleases,
174
+ "Features": :features,
175
+ "Ignored releases": :ignored_release_nodes,
176
+ "Ignored features": :ignored_feature_nodes,
177
+ "Orphan disk features": :orphan_feature_nodes,
178
+ "Orphan disk releases": :orphan_release_nodes,
179
+ "Orphan git branches": :orphan_git_branches
180
+ }
181
+
182
+ def info(project)
183
+ for header, member in INFO_PARAMS
184
+ elements = project.send(member)
185
+ puts header
186
+ if elements.empty?
187
+ puts " [none]"
188
+ else
189
+ if member == :features
190
+ project.features.each { |release, features|
191
+ puts " #{release}"
192
+ features.each { |feature| puts " #{feature.feature}" }
193
+ }
194
+ else
195
+ elements.sort.each { |element| puts " #{element}" }
196
+ end
197
+ end
198
+ end
199
+ end
200
+
201
+ def compute_version(project, arg)
202
+ if arg.nil?
203
+ nil
204
+ elsif %w(major minor patch).include?(arg)
205
+ project.release.version.increment(arg.to_sym)
206
+ else
207
+ Prick::Version.new(arg)
208
+ end
209
+ end
210
+
211
+ opts, args = ShellOpts.as_struct(SPEC, ARGV)
212
+
213
+ # Handle --help
214
+ if opts.help?
215
+ begin
216
+ file = Tempfile.new("prick")
217
+ file.puts HELP.split("\n").map { |l| l.sub(/^ /, "") }
218
+ file.flush
219
+ system "less #{file.path}"
220
+ ensure
221
+ file.close
222
+ end
223
+ exit
224
+ end
225
+
226
+ # Handle --version
227
+ if opts.version?
228
+ puts "prick-#{Prick::VERSION}"
229
+ exit
230
+ end
231
+
232
+ begin
233
+ # Honor -C option
234
+ if opts.directory
235
+ begin
236
+ Dir.chdir(opts.directory)
237
+ rescue Errno::ENOENT
238
+ raise Prick::Error, "Can't cd to '#{directory}'"
239
+ end
240
+ end
241
+
242
+ name = opts.name || ENV["PRICK_PROJECT"] || File.basename(Dir.getwd)
243
+ verbose = opts.verbose?
244
+
245
+ # :init! is processed separately because the project object can't be
246
+ # constructed before the directory has been initialized
247
+ if opts.subcommand == :init
248
+ dir = args.expect(1)
249
+ Prick::Project.initialize_directory(dir)
250
+ Dir.chdir(dir) {
251
+ Prick::Project.initialize_project(name)
252
+ }
253
+ if File.basename(dir) != name
254
+ puts "Initialized project #{name} in #{dir}" if !opts.quiet?
255
+ else
256
+ puts "Initialized project #{name}" if !opts.quiet?
257
+ end
258
+ else
259
+ project = Prick::Project.new(name)
260
+
261
+ case cmd = opts.subcommand
262
+
263
+ # INFO COMMANDS #########################################################
264
+ #
265
+ when :info
266
+ args.expect(0)
267
+
268
+ puts "Current branch"
269
+ puts " #{project.release&.version || 'nil'}"
270
+
271
+ puts "Project database"
272
+ db = project.database
273
+ if db.exist?
274
+ print " #{project.database.name}"
275
+ print " (present)" if project.database.exist? && !project.database.loaded?
276
+ print " (using #{project.database.version})" if project.database.loaded?
277
+ else
278
+ print " [missing]" if !project.database.exist?
279
+ end
280
+ puts
281
+
282
+ puts "Releases"
283
+ project.releases.each { |release|
284
+ print " #{release}"
285
+ if release.database.exist?
286
+ if release.database.version
287
+ print " loaded"
288
+ else
289
+ print " (empty database)"
290
+ end
291
+ end
292
+ if release.cached?
293
+ print ", cached"
294
+ end
295
+ print " <#{release.database.name}>"
296
+ print "\n"
297
+ }
298
+ puts
299
+ info(project)
300
+
301
+ when :status
302
+ args.expect(0)
303
+ puts "On branch #{project.release}" + (project.dirty? ? " (dirty)" : "")
304
+
305
+ when :history
306
+ args.expect(0)
307
+ history = project.release.history
308
+ history.each { |release, features|
309
+ puts release.to_s
310
+ indent {
311
+ features.map { |feature|
312
+ puts "#{feature}, base release: #{feature.base_release}"
313
+ }
314
+ }
315
+ }
316
+
317
+
318
+ # GENERAL COMMANDS ######################################################
319
+ #
320
+ when :checkout
321
+ version = args.expect(1)
322
+ project.checkout(version)
323
+ puts "Checked out #{version}"
324
+
325
+ when :migrate
326
+ project.migrate
327
+
328
+
329
+ # DEVELOPER COMMANDS ####################################################
330
+ #
331
+ when :feature # Shorthand for 'create feature'
332
+
333
+ when :rebase
334
+
335
+ when :prepare
336
+ args.expect(0)
337
+ if object = opts.prepare.subcommand!
338
+ case object
339
+ when :migration
340
+ project.prepare_migration
341
+ puts "Prepared migration"
342
+ else
343
+ raise "Oops: subcommand #{opts.subcommand.inspect} not matched"
344
+ end
345
+ else
346
+ args.expect(0)
347
+ project.prepare_release
348
+ puts "Prepared new release" if !opts.quiet?
349
+ end
350
+
351
+
352
+ # RELEASE MANAGER COMMANDS ##############################################
353
+ #
354
+ when :create
355
+ object = opts.create.subcommand!
356
+ release =
357
+ case object
358
+ when :release
359
+ version = compute_version(project, args.expect(0..1))
360
+ project.create_release(version)
361
+ when :prerelease
362
+ version = compute_version(project, args.expect(1))
363
+ project.create_prerelease(version)
364
+ when :feature
365
+ project.create_feature(args.expect(1))
366
+ else
367
+ raise "Oops: subcommand #{opts.subcommand.inspect} not matched"
368
+ end
369
+ puts "Created #{object} #{release.version}" if !opts.quiet?
370
+
371
+ when :increment # and 'increment prerelease'
372
+ args.expect(0)
373
+ release = project.increment_prerelease
374
+ puts "Created prerelease #{release.version}" if !opts.quiet?
375
+
376
+ when :release # Shorthand for 'create release'
377
+ version = compute_version(project, args.expect(0..1))
378
+ release = project.create_release(version)
379
+ puts "Created release #{release.version}" if !opts.quiet?
380
+
381
+ when :prerelease # Shorthand for 'create prerelease' and 'increment'
382
+ release =
383
+ if project.release?
384
+ version = compute_version(project, args.expect(1))
385
+ project.create_prerelease(version)
386
+ elsif project.prerelease?
387
+ args.expect(0)
388
+ project.increment_prerelease
389
+ end
390
+ puts "Created prerelease #{release.version}" if !opts.quiet?
391
+
392
+ when :include
393
+ name = args.expect(1)
394
+ project.include_feature(name)
395
+ puts "Included feature #{name}" if !opts.quiet?
396
+ puts "Please check changed files and then commit using 'prick commit'"
397
+
398
+ when :commit
399
+ msg = project.commit_feature
400
+ puts msg if !opts.quiet?
401
+ puts "Committed changes" if !opts.quiet?
402
+
403
+
404
+ # MANIPULATING DATABASES AND RELEASES ###################################
405
+ #
406
+ when :list
407
+ # TODO: Mark cached releases as such
408
+ args.expect(0)
409
+ case opts.list.subcommand
410
+ when :databases, nil; project.databases.map(&:name)
411
+ when :releases; project.releases.map(&:name)
412
+ when :prereleases; project.prereleases.map(&:version)
413
+ else
414
+ ShellOpts.error "Illegal list type: '#{opts.list.subcommand}"
415
+ end.each { |e| puts e }
416
+
417
+ when :build
418
+ version = args.expect(0..1)
419
+ project.build(version)
420
+ if version
421
+ puts "Built #{version}"
422
+ else
423
+ puts "Built current version"
424
+ end
425
+
426
+ when :use
427
+ file = opts.use.file
428
+ version = args.expect(0..1)
429
+ file || version or raise Error, "Need either a version or a -f FILE argument"
430
+ file.nil? != version.nil? or raise Error, "Not both version and -f FILE arguments can be specified"
431
+ file ||= project[version].archive.path
432
+ project.load_database(nil, file)
433
+
434
+ when :load
435
+ file = opts.load.file
436
+ version = args.expect(1)
437
+ project.load_database(version, file)
438
+
439
+ when :reload
440
+ file = opts.reload.file
441
+ version = args.expect(1)
442
+ project.reload(version, file)
443
+
444
+ when :unload
445
+ version = args.expect(0..1)
446
+ project.unload(version)
447
+
448
+ when :clean
449
+ version = args.expect(0..1)
450
+ project.clean(version, project: true)
451
+
452
+ when NilClass
453
+ if args.size > 0
454
+ ShellOpts.error "#{args.first} is not a command"
455
+ end
456
+ else
457
+ raise "Oops: subcommand #{opts.subcommand.inspect} not matched"
458
+ end
459
+ end
460
+
461
+ rescue Prick::Fail => ex # Handling of Fail has to come first because Fail < Error
462
+ ShellOpts.fail(ex.message)
463
+ rescue Prick::Error => ex
464
+ ShellOpts.error(ex.message)
465
+ end
466
+
467
+