shellopts 2.0.0.pre.14 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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 +25 -15
  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 +359 -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
+