shellopts 2.0.0.pre.14 → 2.0.2

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 (45) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +2 -1
  3. data/.ruby-version +1 -1
  4. data/README.md +201 -267
  5. data/TODO +37 -5
  6. data/doc/format.rb +95 -0
  7. data/doc/grammar.txt +27 -0
  8. data/doc/syntax.rb +110 -0
  9. data/doc/syntax.txt +10 -0
  10. data/lib/ext/array.rb +62 -0
  11. data/lib/ext/forward_to.rb +15 -0
  12. data/lib/ext/lcs.rb +34 -0
  13. data/lib/shellopts/analyzer.rb +130 -0
  14. data/lib/shellopts/ansi.rb +8 -0
  15. data/lib/shellopts/args.rb +29 -21
  16. data/lib/shellopts/argument_type.rb +139 -0
  17. data/lib/shellopts/dump.rb +158 -0
  18. data/lib/shellopts/formatter.rb +292 -92
  19. data/lib/shellopts/grammar.rb +375 -0
  20. data/lib/shellopts/interpreter.rb +103 -0
  21. data/lib/shellopts/lexer.rb +175 -0
  22. data/lib/shellopts/parser.rb +293 -0
  23. data/lib/shellopts/program.rb +279 -0
  24. data/lib/shellopts/renderer.rb +227 -0
  25. data/lib/shellopts/stack.rb +7 -0
  26. data/lib/shellopts/token.rb +44 -0
  27. data/lib/shellopts/version.rb +1 -1
  28. data/lib/shellopts.rb +360 -3
  29. data/main +1180 -0
  30. data/shellopts.gemspec +8 -14
  31. metadata +86 -41
  32. data/lib/ext/algorithm.rb +0 -14
  33. data/lib/ext/ruby_env.rb +0 -8
  34. data/lib/shellopts/ast/command.rb +0 -112
  35. data/lib/shellopts/ast/dump.rb +0 -28
  36. data/lib/shellopts/ast/option.rb +0 -15
  37. data/lib/shellopts/ast/parser.rb +0 -106
  38. data/lib/shellopts/constants.rb +0 -88
  39. data/lib/shellopts/exceptions.rb +0 -21
  40. data/lib/shellopts/grammar/analyzer.rb +0 -76
  41. data/lib/shellopts/grammar/command.rb +0 -87
  42. data/lib/shellopts/grammar/dump.rb +0 -56
  43. data/lib/shellopts/grammar/lexer.rb +0 -56
  44. data/lib/shellopts/grammar/option.rb +0 -55
  45. data/lib/shellopts/grammar/parser.rb +0 -78
data/main ADDED
@@ -0,0 +1,1180 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler'
4
+ Bundler.setup
5
+
6
+ require 'shellopts'
7
+
8
+ include ShellOpts
9
+
10
+
11
+ SPEC = %(
12
+ -a,alpha @ Brief comment for -a and --alpha options
13
+ Longer and more elaborate description of the --alpha option
14
+
15
+ -b,beta=ARG
16
+ @ Alternative style of brief comment
17
+
18
+ Longer and more elaborate description of the --beta option
19
+ )
20
+
21
+ opts, args = ShellOpts.process(SPEC, ARGV)
22
+ #puts "opts.alpha?: #{opts.alpha?.inspect}"
23
+ #puts "opts.alpha: #{opts.alpha.inspect}"
24
+ #puts "opts.beta?: #{opts.beta?.inspect}"
25
+ #puts "opts.beta: #{opts.beta.inspect}"
26
+ #exit
27
+ #ShellOpts::ShellOpts.brief
28
+ #exit
29
+
30
+
31
+
32
+
33
+
34
+
35
+
36
+ # Standard options
37
+ # -h,help
38
+ # --version
39
+ #
40
+ # Message options
41
+ # -q,quiet
42
+ # -v,verbose
43
+
44
+ # ShellOpts.parse(spec, argv)
45
+ # ShellOpts.stdopt(spec, argv)
46
+ # ShellOpts.msgopt(spec, argv)
47
+
48
+
49
+
50
+ SPEC = %(
51
+ # Comment
52
+
53
+ @ Program brief
54
+
55
+ This should end up in the DESCRIPTION section in the @help format. Bla bla
56
+ bla. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod
57
+ tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
58
+ quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
59
+ consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse
60
+ cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non
61
+ proident, sunt in culpa qui officia deserunt mollit anim id est laborum
62
+
63
+ Here comes some code
64
+
65
+ if this_is_printed_correctly?
66
+ puts "Success"
67
+ end
68
+
69
+ Here is a paragraph
70
+
71
+ Here is another paragraph
72
+
73
+ OPTIONS
74
+
75
+ This should be in the OPTIONS section (not supported for now - now it is!)
76
+
77
+ -a,all
78
+ -b,beta Brief inline comment
79
+ +v,verbose -h,help -version @ Multi option line. Option group.
80
+ -c
81
+ @ Alternative brief.
82
+ -f,file=FILE
83
+ Indented comment
84
+
85
+ Some free text not related to a single options. Eg. an introduction to the
86
+ next set of options and some more text to make this wrap
87
+
88
+ --multiple --options --on --one --line --with --brief
89
+ @ Brief for multi-option line - aka. option group. Lorem ipsum dolor sit amet, consectetur adipiscing elit
90
+
91
+ Common comment for previous multi-option line. Lorem ipsum dolor sit amet,
92
+ consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et
93
+ dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation
94
+ ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure
95
+ dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat
96
+ nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in
97
+ culpa qui officia deserunt mollit anim id est laborum
98
+
99
+ Here comes some more code:
100
+
101
+ if something
102
+ this
103
+ else
104
+ that
105
+ end
106
+
107
+ And some more text
108
+
109
+
110
+ # --multiple
111
+ # --options
112
+ # --on
113
+ # --multiple
114
+ # --lines
115
+ # Common comment for previous multi-option lines (not doable, but this is how
116
+ # the previous multi-option line will be rendered in one of the formats).
117
+ # However, the same is not true for commands that can't have a common comment
118
+
119
+
120
+ # A comment that should not be included in the source (useful to out-comment
121
+ # sections of source)
122
+ #
123
+ # The following blank line should be ignored
124
+
125
+ -l=MODE:short,long
126
+ Another indented comment. The following blank line should be included
127
+
128
+ But not if it is the last blank line.
129
+ \\hep! initiates a line but is not a command because it is escaped
130
+
131
+ a_code_example()
132
+ if something == 42
133
+ # Something that looks like a command
134
+ doit!
135
+
136
+ # Something that looks like a option
137
+ --i_miss_this
138
+ end
139
+
140
+ -- ARG1 ARG2
141
+
142
+ cmd! @ Brief description of command
143
+ Description of command.
144
+
145
+ Another paragraph.
146
+ Another line in paragraph
147
+
148
+ -c,copt @ Inline comment
149
+ -d,dopt @ Brief and nested comment
150
+ Even more nested comment
151
+ -a,all @ Duplicate
152
+
153
+ Here is some text where
154
+
155
+ the next line is indented (interpreted as code)
156
+
157
+ subcmd! -i,inc ++ SUB ARGS @ Brief description of sub-command
158
+ A description
159
+
160
+ This text should be included too
161
+
162
+ ++ CMD_ARG1 CMD_ARG2
163
+
164
+ Longer indented text is not related to the previous
165
+ description of arguments but is considered code
166
+
167
+ is this code
168
+
169
+ ++ CMD_ARG_A CMD_ARG_B
170
+
171
+ with something that is not a a description
172
+
173
+ # -- GLOBAL ARGS
174
+
175
+ --another-global-option
176
+
177
+ a regular paragraph
178
+ )
179
+
180
+ ONELINE = "-a,all -b,beta --verbose -h,help -v,version -c -f=FILE --multiple --options --on --one --line -l=MODE:short,long? cmd! -c,copt -d,dopt cmd.subcmd! -- GLOBAL ARGS"
181
+
182
+ SPEC2 = %(
183
+ @ Program brief
184
+
185
+ text text text
186
+
187
+ OPTIONS
188
+
189
+ Some option intro text
190
+
191
+ -a
192
+ Option brief 1. Help text
193
+
194
+ -b Option brief 2
195
+
196
+ -c @ Option brief 3
197
+
198
+ -d
199
+ @ Option brief 4
200
+
201
+ text text text
202
+
203
+ COMMANDS
204
+
205
+ Some command intro text
206
+
207
+ cmd1! -- ARG1 ARG2 Command brief 1
208
+ cmd2! @ Command brief 2
209
+ cmd3!
210
+ First-line option brief. After the first dot
211
+
212
+ )
213
+
214
+ # #{$stderr.puts "Oops"; "32"}
215
+
216
+ SPEC3 = %q(
217
+ Description
218
+
219
+ -- ARG1 ARG2
220
+ -- ARG3 ARG4
221
+
222
+ OPTIONS
223
+ Some text
224
+
225
+ -a An option and not code
226
+
227
+ Some code
228
+
229
+ \\-b A code line # Because we escaped it
230
+ -c # this is not an option
231
+ this_is_more_code()
232
+
233
+ Another option
234
+
235
+ -d Another
236
+
237
+ More code
238
+
239
+ # A command
240
+ -e # is not an option
241
+
242
+ COMMANDS
243
+ Some commands text
244
+
245
+ cmd1! A command
246
+ cmd1! A dup
247
+
248
+ Some code
249
+
250
+ \\cmd2! A code line
251
+ cmd3! # this is not a command
252
+
253
+ Final
254
+ )
255
+
256
+ SPEC4 = %(
257
+ cmd!
258
+ A command
259
+
260
+ cmd.nested!
261
+ A nested command
262
+
263
+ cmd.subcmd!
264
+ A subcommand
265
+ )
266
+
267
+ SPEC5 = "cmd! cmd!"
268
+
269
+ shellopts = ShellOpts::ShellOpts.new(exception: false)
270
+ #shellopts.compile("cmd! cmd!")
271
+ shellopts.compile(SPEC)
272
+ #shellopts.compile(SPEC2)
273
+ #shellopts.compile(SPEC3)
274
+ #shellopts.compile(SPEC4)
275
+ #shellopts.compile(SPEC5)
276
+
277
+ #shellopts.tokens.each(&:dump)
278
+ #exit
279
+
280
+ shellopts.usage
281
+ #shellopts.brief
282
+ #shellopts.help
283
+ #shellopts.help("cmd")
284
+ #shellopts.help(ARGV.first)
285
+
286
+ #p shellopts.tokens
287
+
288
+ #
289
+ #shellopts = ShellOpts::ShellOpts.new
290
+ #shellopts.compile(SPEC4)
291
+ #p shellopts.file
292
+ #ShellOpts.process(SPEC4, [])
293
+ #exit
294
+
295
+
296
+ #argv = ARGV.empty? ? %w(-a cmd -c) : ARGV
297
+ #prog, args = ShellOpts::ShellOpts.process SPEC2, argv
298
+
299
+ #tokens = ShellOpts::Lexer.lex("main", SPEC4)
300
+ #tokens.each(&:dump)
301
+ #exit
302
+
303
+ #ast = ShellOpts::Parser.parse(tokens)
304
+ #ast.dump_ast
305
+ #exit
306
+
307
+ #idr = ShellOpts::Analyzer.analyze(ast) # @idr and @ast refer to the same object
308
+ #idr.dump_idr
309
+ #exit
310
+
311
+ #puts "-" * 80
312
+ #ShellOpts::Formatter.usage(idr)
313
+ #puts "-" * 80
314
+ #ShellOpts::Formatter.brief(idr)
315
+ #puts "-" * 80
316
+ #ShellOpts::Formatter.help(idr)
317
+ #puts "-" * 80
318
+
319
+ #ShellOpts.process(SPEC, [])
320
+ #ShellOpts.error("Hej")
321
+
322
+
323
+
324
+
325
+
326
+
327
+
328
+
329
+ __END__
330
+ exit
331
+
332
+ puts "prog.verbose: #{prog.verbose.inspect}"
333
+ puts "prog.all?: #{prog.all?}"
334
+ puts "prog.file: #{prog.file.inspect}"
335
+ puts "prog.subcommand: #{prog.subcommand.inspect}"
336
+ puts "prog[:cmd!]: #{prog[:cmd!].__ident__}"
337
+ puts "prog[\"cmd\"]: #{prog["cmd"].__ident__}"
338
+ puts "prog.subcommand!.subcommand: #{prog.subcommand!.subcommand.inspect}"
339
+ #shellopts = ShellOpts::ShellOpts.new ONELINE, ARGV
340
+
341
+
342
+
343
+ puts "---------------------"
344
+ puts ShellOpts::Formatter.option_help(prog)
345
+
346
+ #spec = %(
347
+ #hej
348
+ #med dig
349
+ #)
350
+ #shellopts = ShellOpts::ShellOpts.new spec, ARGV
351
+
352
+ #shellopts.tokens.each(&:dump)
353
+
354
+
355
+ __END__
356
+
357
+
358
+ shellopts = ShellOpts::ShellOpts.new(SPEC, ARGV)
359
+ opts, args = shellopts.result
360
+
361
+ #opts, args = ShellOpts.make(SPEC, ARGV)
362
+
363
+ #opts.help? # True if present
364
+ #opts.file? # True if present
365
+ #opts.file # Not nil if argument was given
366
+
367
+ shellopts.mesg "This is a message"
368
+ shellopts.quiet!
369
+ shellopts.mesg "Not printed"
370
+
371
+ shellopts.verb "Not printed"
372
+ shellopts.verbose!
373
+ shellopts.verb "Printed"
374
+
375
+
376
+ #ShellOpts.failure "Something went wrong"
377
+ #
378
+ #
379
+ #ShellOpts.mesg "This is a message"
380
+ #ShellOpts.quiet!
381
+ #ShellOpts.mesg "Not printed"
382
+ #
383
+ #ShellOpts.verb "Not printed"
384
+ #ShellOpts.verbose!
385
+ #ShellOpts.verb "Printed"
386
+ #
387
+ #
388
+ #ShellOpts.failure "Something went wrong"
389
+
390
+ #include ShellOpts::Include
391
+ #
392
+ #mesg "This is a message"
393
+ #quiet!
394
+ #mesg "Not printed"
395
+ #
396
+ #verb "Not printed"
397
+ #verbose!
398
+ #verb "Printed"
399
+ #
400
+ #
401
+ #failure "Something went wrong"
402
+
403
+
404
+
405
+ __END__
406
+
407
+ opts, args = ShellOpts.process(OPTIONS, ARGV, exception: true)
408
+
409
+ if opts.version?
410
+ puts "pg_graph-#{PgGraph::VERSION}"
411
+ exit
412
+ end
413
+
414
+ if opts.help?
415
+ puts "Name"
416
+ puts " #{PROGRAM}"
417
+ puts
418
+ puts "Usage"
419
+ puts " #{PROGRAM} #{USAGE}"
420
+ puts
421
+ print "Options"
422
+ puts OPTIONS
423
+ exit
424
+ end
425
+
426
+ timing = opts.time?
427
+ timer = Timer::Timer.new
428
+
429
+ # Process options
430
+ meta = opts.meta
431
+ reflections = opts.reflections
432
+
433
+ !opts.kind? || %w(meta type data).include?(opts.kind) or
434
+ raise "Unknown argument for --kind option - '#{opts.kind}'"
435
+ kind = opts.kind? ? opts.kind : "type"
436
+
437
+ !opts.format? || %w(sql exec psql yaml).include?(opts.format) or
438
+ raise "Unknown argument for --format option - '#{opts.format}'"
439
+ format = opts.format? ? opts.format : "yaml"
440
+
441
+ case opts.subcommand || :dump
442
+ when :load
443
+ opts = opts.subcommand!
444
+ !opts.format? || %w(sql exec psql yaml).include?(opts.format) or
445
+ raise "Unknown argument for --format option - '#{opts.format}'"
446
+
447
+ database = args.expect(-1)
448
+ file = args.expect(0..1) || "/dev/stdin"
449
+
450
+ if opts.format?
451
+ format = opts.format
452
+ else
453
+ format =
454
+ case File.extname(file)
455
+ when ".sql"; "sql"
456
+ when ".yaml", ".yml"; "yaml"
457
+ else
458
+ "yaml"
459
+ end
460
+ end
461
+
462
+ case format
463
+ when "sql", "exec";
464
+ connection = timer.time("connect") { PgConn.new(database) }
465
+ timer.time("load file") {
466
+ connection.exec(IO.read(file))
467
+ }
468
+ when "psql"
469
+ timer.time("psql") {
470
+ system "psql -d #{database} < #{file} >/dev/null"
471
+ }
472
+ when "yaml"
473
+ connection, type = load_type(timer, opts, database)
474
+ tg = timer.group("read data")
475
+ data = tg.time("data") { PgGraph::Data.new(type, YAML.load(IO.read(file))) }
476
+ tg = timer.group("write data")
477
+ for label, sql in PgGraph::Data::SqlRender.new(data, :exec).to_h
478
+ tg.time(label) { connection.exec(sql.join) }
479
+ end
480
+ end
481
+
482
+ when :dump
483
+ if opts # When pg_graph is called without a subcommand
484
+ kind = "type"
485
+ format = "yaml"
486
+ else
487
+ !opts.kind? || %w(meta type data).include?(opts.kind) or
488
+ raise "Unknown argument for --kind option - '#{opts.kind}'"
489
+ kind = opts.kind? ? opts.kind : "type"
490
+
491
+ !opts.format? || %w(sql exec psql yaml).include?(opts.format) or
492
+ raise "Unknown argument for --format option - '#{opts.format}'"
493
+ format = opts.format? ? opts.format : "yaml"
494
+ end
495
+
496
+ database = args.expect(1)
497
+
498
+ case kind
499
+ when "meta"
500
+ connection = timer.time("connect") { PgConn.new(database) if !opts.meta? }
501
+ meta = timer.time("meta") { opts.meta? ? PgMeta.load_file(opts.meta) : PgMeta.new(connection) }
502
+ meta.dump
503
+ when "type"
504
+ connection, type = load_type(timer, opts, database)
505
+ type.dump
506
+ when "data"
507
+ connection, type = load_type(timer, opts, database)
508
+ data = timer.time("instantiate") { type.instantiate(connection) }
509
+ timer.time("dump") {
510
+ case format
511
+ when "sql"; puts data.to_sql
512
+ when "exec"; puts data.to_exec_sql
513
+ when "psql"; puts data.to_psql_sql
514
+ when "yaml"; puts data.to_yaml.to_yaml
515
+ end
516
+ }
517
+ end
518
+
519
+ when :clean
520
+ opts = opts.subcommand!
521
+ database = args.expect(1)
522
+
523
+ tg = timer.group("initialization")
524
+ connection = tg.time("connect") { PgConn.new(database) }
525
+ meta = tg.time("meta") { opts.meta? ? PgMeta.new(opts.meta) : PgMeta.new(connection) }
526
+ type = tg.time("type") { PgGraph::Type.new(meta) }
527
+ data = tg.time("data") { type.instantiate }
528
+
529
+ tg = timer.group("clean data")
530
+ for label, sql in PgGraph::Data::SqlRender.new(data, :exec).to_h
531
+ tg.time(label) { connection.exec(sql.join) }
532
+ end
533
+ else
534
+ puts "else"
535
+ end
536
+
537
+ timer.dump($stderr) if timing
538
+
539
+ rescue ShellOpts::Error => ex
540
+ $stderr.puts "#{PROGRAM}: #{ex.message}"
541
+ $stderr.puts "Usage: #{PROGRAM} #{USAGE}"
542
+ exit 1
543
+ end
544
+
545
+ include ShellOpts
546
+ include Prick
547
+
548
+ TIME = false
549
+
550
+ SPEC = %(
551
+ -h,help COMMAND...
552
+ Print this page
553
+
554
+ +v,verbose
555
+ Be verbose. Repeated -v options increase the verbosity level
556
+
557
+ -q,quiet
558
+ Be quiet
559
+
560
+ --version
561
+ Print prick version. Use 'prick version' to get the project version
562
+
563
+ -C,directory=EDIR
564
+ Change to directory DIR before doing anything else
565
+
566
+ -d,database=DATABASE
567
+ Override database name from prick.yml
568
+
569
+ -U,username=USERNAME
570
+ Override username from from prick.yml
571
+
572
+ !version
573
+ Print project version
574
+
575
+ !init -n,name=NAME -t,title=TITLE -- [DIRECTORY]
576
+ Initializes a prick project
577
+
578
+ !setup
579
+ Create the database user (if necessary) and an empty database
580
+
581
+ !teardown
582
+ Drop the database and the database user. TODO: Also run teardown scripts
583
+
584
+ !create.data
585
+ !create.schema
586
+ !create.database
587
+ !create.users
588
+ !create.all
589
+ Create an object. Fails if migration exist unless the --force flag is given
590
+
591
+ !create.migration -f,force -o,file=NFILE -- VERSION
592
+ Create a migration from VERSION to the current and write it to
593
+ migration/VERSION. Fails if migration exist unless the --force flag is
594
+ given. If --file is given, the migration is written to FILE instead of
595
+ the migration directory. This doesn't require you to be on a release
596
+ branch and can be used to create ad-hoc migration scripts
597
+
598
+ !drop -- [KIND]
599
+ Kind can be 'users', 'data', 'schema', 'database' (the default), or 'all'. It is
600
+ not an error if the object doesn't exist. TODO Only 'users' is currently defined
601
+
602
+ !build -t,time --dump=KIND? -- [SCHEMA]
603
+ Build the project. If SCHEMA is defined, later schemas are excluded.
604
+ KIND can be 'nodes', 'allnodes' or 'batches' (the default)
605
+
606
+ !make -t,time --dump=KIND? -- [SCHEMA]
607
+ Checks file timestamps against the time of the last build and only
608
+ rebuild affected parts of the project. KIND can be 'nodes', 'allnodes' or
609
+ 'batches'
610
+
611
+ !fox -- FILE...
612
+ Load fox file data. Data are reset to their initial state after build
613
+ before the fox data are loaded
614
+
615
+ !release -- KIND
616
+ Create a release of the given kind. KIND can be 'major', 'minor', or
617
+ 'patch'. Release checks that the current repo is clean and up to date
618
+ with the origin
619
+
620
+ !migrate -f,file=EFILE
621
+ Execute a migration
622
+
623
+ !dump.type
624
+ !dump.data
625
+ !dump.schema
626
+ !dump.database
627
+ TODO
628
+
629
+ dump.migration! --force VERSION
630
+ )
631
+
632
+ opts, args = ShellOpts.process(SPEC, ARGV)
633
+
634
+ # Handle --help
635
+ if opts.help?
636
+ puts "Name"
637
+ puts " prick - Postgres project management tool"
638
+ puts
639
+ puts "Usage"
640
+ puts " prick [GLOBAL-OPTIONS] command [COMMAND-OPTIONS] ARGUMENTS"
641
+ puts
642
+ puts "Options and commands"
643
+ puts SPEC.sub(/^\s*\n/, "")
644
+ exit
645
+ end
646
+
647
+ # Initial directory. Used to create relative paths in user messages
648
+ #rundir = Dir.getwd
649
+
650
+ begin
651
+ # Handle --version
652
+ if opts.version?
653
+ puts "prick-#{VERSION}"
654
+ exit
655
+ end
656
+
657
+ # Handle verbose and quiet
658
+ $verbose = opts.verbose
659
+ $quiet = opts.quiet?
660
+
661
+ # Honor -C option
662
+ if opts.directory?
663
+ if File.exist?(opts.directory)
664
+ begin
665
+ Dir.chdir(opts.directory)
666
+ rescue Errno::ENOENT
667
+ raise Prick::Error, "Can't cd to '#{opts.directory}'"
668
+ end
669
+ else
670
+ raise Prick::Error, "Can't find directory: #{opts.directory}"
671
+ end
672
+ end
673
+
674
+ # Get subcommand
675
+ cmd = opts.subcommand!
676
+
677
+ # Process init command
678
+ if opts.subcommand == :init
679
+ dir, state = Prick::SubCommand.init(args.expect(0..1), cmd.name, cmd.title, opts.database, opts.username)
680
+ puts "Initialized prick project '#{state.name}' in #{dir}"
681
+ if opts.database.nil? || opts.username.nil?
682
+ puts
683
+ puts "Please check database/username in #{PRICK_CONTEXT_FILE}"
684
+ end
685
+ exit
686
+ end
687
+
688
+ # Load state
689
+ Prick.state = State.load
690
+
691
+ # Handle -d and -U options
692
+ database = opts.database || Prick.state.database
693
+ username = opts.username || Prick.state.username
694
+
695
+ # Expect a sub-command
696
+ cmd = opts.subcommand! or raise Prick::Error, "Subcomand expected"
697
+
698
+ # Process subcommands
699
+ case opts.subcommand
700
+ when :version
701
+ puts "#{Prick.state.name}-#{Prick.state.version}"
702
+
703
+ when :setup
704
+ Prick::SubCommand.setup(database, username)
705
+
706
+ when :teardown
707
+ Prick::SubCommand.teardown(database, username)
708
+
709
+ when :create
710
+ create_command = opts.create!
711
+ case create_command.subcommand
712
+ when :migration
713
+ arg = args.expect(1)
714
+ version = PrickVersion.try(arg) or raise Prick::Error, "Illegal version: #{arg}"
715
+ Prick::SubCommand.create_migration(
716
+ username, version,
717
+ force: create_command.subcommand!.force?,
718
+ file: create_command.subcommand!.file)
719
+ else
720
+ raise NotImplementedError
721
+ end
722
+
723
+ when :build
724
+ dump = cmd.dump("batches")&.to_sym
725
+ Prick::SubCommand.build(database, username, args.expect(0..1), timer: cmd.time?, dump: dump)
726
+
727
+ when :make
728
+ dump = cmd.dump("batches")&.to_sym
729
+ Prick::SubCommand.make(database, username, args.expect(0..1), timer: cmd.time?, dump: dump)
730
+
731
+ when :fox
732
+ Prick::SubCommand.fox(database, username, args)
733
+
734
+ when :drop
735
+ case subject = args.expect(1).to_sym
736
+ when :all
737
+ Prick::SubCommand.drop_all(database)
738
+ when :users
739
+ Prick::SubCommand.drop_users(database)
740
+ when :database
741
+ Prick::SubCommand.drop_database(database)
742
+ when :data, :schema, :database, :all
743
+ raise NotImplementedError
744
+ else
745
+ raise Prick::Error, "Unknown subject: #{subject}"
746
+ end
747
+
748
+ when :release
749
+ kind = args.expect(1).to_sym
750
+ constrain? kind, :major, :minor, :patch or
751
+ raise Prick::Fail, "Expected 'major', 'minor', or 'patch' argument, got '#{kind}'"
752
+ Prick::SubCommand.release(kind)
753
+
754
+ when :migrate
755
+ args.expect(0)
756
+ Prick::SubCommand.migrate(database, username, file: cmd.file)
757
+
758
+ when :dump
759
+ subject = cmd.subcommand!
760
+ case cmd.subcommand
761
+ when :migration
762
+ arg = args.expect(1)
763
+ version = PrickVersion.try(arg) or raise "Illegal version number: #{arg}"
764
+ Prick::SubCommand.create_migration(username, version, force: subject.force?, file: "/dev/stdout")
765
+ when :data, :schema, :database
766
+ raise NotImplementedError
767
+ else
768
+ raise Prick::Error, "Unknown subject: #{subject}"
769
+ end
770
+
771
+ else
772
+ raise Prick::Fail, "Internal error: Unhandled command - #{opts.subcommand.inspect}"
773
+ end
774
+
775
+ rescue ShellOpts::Fail, Prick::Fail, Prick::Build::PostgresError => ex
776
+ ShellOpts.fail(ex.message)
777
+
778
+ rescue ShellOpts::Error, Prick::Error => ex
779
+ ShellOpts.error(ex.message)
780
+ end
781
+
782
+ __END__
783
+
784
+ -n,name=NAME
785
+ Name of project. Defauls to the environment variable `PRICK_PROJECT` if
786
+ set and else the name of the current directory
787
+
788
+ init! -u,user=USER [NAME]
789
+ Initialize a project in the given directory. The USER is the postgres
790
+ user and defaults to the project name
791
+
792
+ info!
793
+ Print project information
794
+
795
+ list.releases! -m,migrations -c,cancelled
796
+ List releases. Include migration releases if the --migration option is
797
+ present and also include cancelled releases if the --cancelled option is
798
+ present
799
+
800
+ list.migrations!
801
+ List migrations
802
+
803
+ list.upgrades! [FROM [TO]]
804
+ List available upgrades
805
+
806
+ list.cache!
807
+ List cache files
808
+
809
+ build! -d,database=DATABASE -s,state=FILE -C,no-cache [TAG]
810
+ Drop all users associated with the database before building the current
811
+ database from the content in the schemas/ directory. With a tag the
812
+ version is built into the associated versioned database and the result is
813
+ saved to cache unless the -C option is given. The -d option overrides the
814
+ default database and the -s option overrides the default state file
815
+ (fox.state)
816
+
817
+ make! -d,database=DATABASE -C,no-cache [TAG]
818
+ Build the current database from the content in the schemas/ directory.
819
+ With a tag the associated versioned database is loaded from cache if
820
+ present. The -C option ignores the cache and the -d option overrides
821
+ the default database
822
+
823
+ make.clean! -a,all
824
+ Drop versioned databases and remove cached and other temporary files.
825
+ Also drop the project database if the -a option is given
826
+
827
+ load! -d,database=DATABASE VERSION|FILE
828
+ Load the cached version or the file into the associated versioned
829
+ database. It is an error if the version hasn't been cached. The --database
830
+ argument overrides the database
831
+
832
+ save! VERSION [FILE]
833
+ Save the versioned database associated with version to the cache or the
834
+ given file
835
+
836
+ drop! -a,all [DATABASE]
837
+ Drop the given database or all versioned databases. Users with a username
838
+ on the form <database>__<username> are also dropped. The --all option
839
+ also drops the project database
840
+
841
+ drop.users! [DATABASE]
842
+ Drop users with a username on the form <database>__<username>
843
+
844
+ diff! -m,mark -t,tables -T,notables
845
+ diff [FROM-DATABASE|FROM-VERSION [TO-DATABASE|TO-VERSION]]
846
+ Create a schema diff between the given databases or versions. Default
847
+ to-version is the current schema and default from-version is the base
848
+ version of this branch/tag
849
+
850
+ migrate!
851
+ Not yet implemented
852
+
853
+ prepare!
854
+ Prepare a release. Just a shorthand for 'prick prepare release'
855
+
856
+ prepare.release! [FORK]
857
+ Populate the current migration directory with migration files
858
+
859
+ prepare.feature! NAME
860
+ Create and populate a feature as a subdirectory of the current directory.
861
+ Also prepares the current release directory
862
+
863
+ prepare.migration! [FROM]
864
+ Create and populate a migration directory
865
+
866
+ prepare.schema! NAME
867
+ Create and populate a new schema directory. Existing files and
868
+ directories are kept
869
+
870
+ prepare.diff! [VERSION]
871
+ Not yet implemented
872
+
873
+ include.feature! FEATURE
874
+ Include the given feature in the current pre-release
875
+
876
+ check!
877
+ Check that the current migration applied to the base version yields the
878
+ same result as loading the current schema
879
+
880
+ create.release! [RELEASE]
881
+ Prepare a release and create release directory and migration file before
882
+ tagging and branching to a release branch. The RELEASE argument can be
883
+ left out if the current branch is a prerelease branch
884
+
885
+ create.prerelease! RELEASE
886
+ Prepare a release and create release directory and migration file before
887
+ branching to a prerelease branch
888
+
889
+ create.feature! NAME
890
+ Prepare a feature before branching to a feature branch
891
+
892
+ cancel!
893
+ Cancel a release. Just a shorthand for 'prick cancel release'
894
+
895
+ cancel.release!
896
+ Cancel a release. Since tags are immutable, the release is cancelled by
897
+ added a special cancel-tag to the release that makes prick ignore it
898
+
899
+ generate.migration!
900
+ Create a script to migrate the database
901
+
902
+ generate.schema!
903
+ Create a script to create the database
904
+
905
+ upgrade!
906
+ Migrate the database to match the current schema
907
+
908
+ backup! [FILE]
909
+ Saves a backup of the database to the given file or to the var/spool
910
+ directory
911
+
912
+ restore! [FILE]
913
+ Restore the database from the given backup file or from the latest backup
914
+ in the var/spool directory
915
+ )
916
+
917
+ __END__
918
+
919
+
920
+
921
+
922
+
923
+
924
+ DEFAULT_STATE_FILE = "fox.state"
925
+
926
+ opts, args = ShellOpts.process(SPEC, ARGV)
927
+
928
+ # Handle --help
929
+ if opts.help?
930
+ ShellOpts.help
931
+ exit
932
+ end
933
+
934
+ # Handle --version
935
+ if opts.version?
936
+ puts "prick-#{VERSION}"
937
+ exit
938
+ end
939
+
940
+ begin
941
+ # Honor -C option
942
+ if opts.directory?
943
+ if File.exist?(opts.directory)
944
+ begin
945
+ Dir.chdir(opts.directory)
946
+ rescue Errno::ENOENT
947
+ raise Prick::Error, "Can't cd to '#{opts.directory}'"
948
+ end
949
+ else
950
+ raise Prick::Error, "Can't find directory: #{opts.directory}"
951
+ end
952
+ end
953
+
954
+ # Create program object
955
+ program = Program.new(quiet: opts.quiet?, verbose: opts.verbose?)
956
+ $verbose = opts.verbose? ? opts.verbose : nil
957
+
958
+ # Handle init command
959
+ if opts.subcommand == :init
960
+ directory = args.expect(0..1)
961
+ name = opts.name || (directory && File.basename(directory)) || File.basename(Dir.getwd)
962
+ user = opts.init!.user || name
963
+ program.init(name, user, directory || ".")
964
+ exit 0
965
+ end
966
+
967
+ # Change to parent directory containing the Prick version file if not found
968
+ # in the current directory
969
+ program.current_directory = Dir.getwd
970
+ while Dir.getwd != "/" && !File.exist?(PRICK_VERSION_FILE)
971
+ Dir.chdir("..")
972
+ end
973
+
974
+ # Check prick version
975
+ file = PrickVersion.new
976
+ file.exist? or raise Prick::Error, "Can't find prick version file '#{file.path}'"
977
+ VERSION == file.read.to_s or
978
+ raise Prick::Fail, ".prick-version required prick-#{file.read} but you're using prick-#{VERSION}"
979
+
980
+ # TODO: Check for dirty detached head
981
+
982
+ # Expect a sub-command
983
+ opts.subcommand or raise Prick::Error, "Subcomand expected"
984
+
985
+ case opts.subcommand
986
+ when :info
987
+ args.expect(0)
988
+ program.info
989
+
990
+ when :list
991
+ command = opts.list!
992
+ case command.subcommand
993
+ when :releases;
994
+ obj = command.releases!
995
+ program.list_releases(migrations: obj.migrations?, cancelled: obj.cancelled?)
996
+ when :migrations; program.list_migrations
997
+ when :upgrades; program.list_upgrades(*args.expect(0..2).compact)
998
+ when :cache;
999
+ args.expect(0)
1000
+ program.list_cache
1001
+ when NilClass; raise Prick::Error, "list requires a releases|migrations|upgrades sub-command"
1002
+ else
1003
+ raise Prick::Internal, "Subcommand #{opts.subcommand}.#{command.subcommand} is not matched"
1004
+ end
1005
+
1006
+ when :build
1007
+ version = args.expect(0..1)
1008
+ state_file = File.expand_path(opts.build!.state || DEFAULT_STATE_FILE)
1009
+ FileUtils.rm_f(state_file)
1010
+ program.build(opts.build!.database, version, state_file, opts.build!.no_cache?)
1011
+
1012
+ when :make
1013
+ command = opts.make!
1014
+ case command.subcommand
1015
+ when :clean
1016
+ args.expect(0)
1017
+ program.make_clean(command.clean!.all?)
1018
+ else
1019
+ version = args.expect(0..1)
1020
+ program.make(opts.make!.database, version, opts.make!.no_cache?)
1021
+ end
1022
+
1023
+ when :load
1024
+ version_or_file = args.expect(1)
1025
+ program.load(opts.load!.database, version_or_file)
1026
+
1027
+ when :save
1028
+ version, file = args.expect(1..2)
1029
+ program.save(version, file)
1030
+
1031
+ when :drop
1032
+ command = opts.drop!
1033
+ case command.subcommand
1034
+ when :users
1035
+ database = args.extract(0..1) || program.project.database.name
1036
+ args.expect(0)
1037
+ program.drop_users(database)
1038
+ else
1039
+ program.drop(args.expect(0..1), opts.drop!.all?)
1040
+ end
1041
+
1042
+ when :diff
1043
+ mark = opts.diff!.mark
1044
+ tables = opts.diff!.tables
1045
+ no_tables = opts.diff!.notables
1046
+ tables.nil? && no_tables.nil? || tables ^ no_tables or
1047
+ raise Error, "--tables and --no-tables options are exclusive"
1048
+ select = tables ? :tables : (no_tables ? :no_tables : :all)
1049
+ from, to = args.expect(0..2)
1050
+ program.diff(from, to, mark, select)
1051
+
1052
+ when :migrate
1053
+ raise NotYet
1054
+
1055
+ when :prepare
1056
+ cmd = opts.prepare!.subcommand || :release
1057
+ case cmd
1058
+ when :release; program.prepare_release(args.expect(0..1))
1059
+ when :feature; program.prepare_feature(args.expect(1))
1060
+ when :migration; program.prepare_migration(args.expect(0..1))
1061
+ when :schema; program.prepare_schema(args.expect(1))
1062
+ when :diff; program.prepare_diff(args.expect(0..1))
1063
+ else
1064
+ raise Prick::Internal, "Subcommand #{opts.subcommand}.#{cmd} is not matched"
1065
+ end
1066
+
1067
+ when :include
1068
+ cmd = opts.include!.subcommand || :feature
1069
+ case cmd
1070
+ when :feature; program.include_feature(args.expect(1))
1071
+ else
1072
+ raise Prick::Internal, "Subcommand #{opts.subcommand}.#{cmd} is not matched"
1073
+ end
1074
+
1075
+ when :check
1076
+ args.expect(0)
1077
+ program.check
1078
+
1079
+ when :create
1080
+ cmd = opts.create!.subcommand || :release
1081
+ case cmd
1082
+ when :release; program.create_release(args.expect(0..1))
1083
+ when :prerelease; program.create_prerelease(args.expect(0..1))
1084
+ when :feature; program.create_feature(args.expect(1))
1085
+ else
1086
+ raise Prick::Internal, "Subcommand #{opts.subcommand}.#{cmd} is not matched"
1087
+ end
1088
+
1089
+ when :cancel
1090
+ cmd = opts.cancel!.subcommand
1091
+ case cmd
1092
+ when :release; program.cancel_release(args.expect(1))
1093
+ when nil; raise Prick::Error, "'cancel' subcommand requires a release argument"
1094
+ else
1095
+ raise Prick::Internal, "Subcommand #{opts.subcommand}.#{cmd} is not matched"
1096
+ end
1097
+
1098
+ when :generate
1099
+ cmd = opts.generate!.subcommand
1100
+ case cmd
1101
+ when :schema; program.generate_schema
1102
+ when :migration; program.generate_migration
1103
+ when nil; raise Prick::Error, "'generate' subcommand requires a 'schema' or 'migration' argument"
1104
+ else
1105
+ raise Prick::Internal, "Subcommand #{opts.subcommand}.#{cmd} is not matched"
1106
+ end
1107
+
1108
+ when :upgrade
1109
+ args.expect(0)
1110
+ program.upgrade
1111
+
1112
+ when :backup
1113
+ program.backup(args.expect(0..1))
1114
+
1115
+ when :restore
1116
+ program.restore(args.expect(0..1))
1117
+ else
1118
+ raise Prick::Internal, "Subcommand #{opts.subcommand} is not matched"
1119
+ end
1120
+
1121
+ rescue Prick::Fail => ex # Handling of Fail has to come first because Fail < Error
1122
+ ShellOpts.fail(ex.message)
1123
+ rescue Prick::Error => ex
1124
+ ShellOpts.error(ex.message)
1125
+ end
1126
+
1127
+ __END__
1128
+
1129
+ # Awaits support for sections in ShellOpts
1130
+ HELP = %(
1131
+ OPTIONS
1132
+ -n, --name=NAME
1133
+ -C, --directory=DIR
1134
+ -h, --help
1135
+ -v, --verbose
1136
+ --version
1137
+
1138
+ COMMANDS
1139
+ INITIALIZATION
1140
+ init --user=USER [DIR]
1141
+
1142
+ INFO COMMANDS
1143
+ info
1144
+ list releases --migrations --cancelled
1145
+ list migrations
1146
+ list upgrades --all
1147
+
1148
+ BUILDING
1149
+ build -d DATABASE -C --nocache [TAG]
1150
+ make -d DATABASE -C --nocache [TAG]
1151
+ make clean -a
1152
+ load -d DATABASE VERSION|FILE
1153
+ save VERSION [FILE]
1154
+ drop --all [DATABASE]
1155
+ diff [FROM-DATABASE|FROM-VERSION [TO-DATABASE|TO-VERSION]]
1156
+ migrate
1157
+
1158
+ PREPARING RELEASES
1159
+ prepare release [FORK]
1160
+ prepare feature NAME
1161
+ prepare migration FROM
1162
+ prepare schema NAME
1163
+ prepare diff [VERSION]
1164
+ include feature FEATURE
1165
+ check
1166
+
1167
+ CREATING RELEASES
1168
+ create release [RELEASE]
1169
+ create prerelease RELEASE
1170
+ create feature NAME
1171
+ cancel release RELEASE
1172
+
1173
+ DEPLOYING RELEASES
1174
+ generate migration
1175
+ generate schema
1176
+ upgrade
1177
+ backup [FILE]
1178
+ restore [FILE]
1179
+ )
1180
+