prick 0.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.
@@ -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
+