prick 0.19.0 → 0.20.1

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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +10 -4
  3. data/README.md +7 -7
  4. data/Rakefile +3 -1
  5. data/TODO +13 -11
  6. data/bin/console +2 -1
  7. data/doc/build-yml.txt +14 -0
  8. data/exe/prick +237 -19
  9. data/lib/builder/batch.rb +147 -0
  10. data/lib/builder/builder.rb +122 -0
  11. data/lib/builder/node.rb +189 -0
  12. data/lib/builder/node_pool.rb +105 -0
  13. data/lib/builder/parser.rb +120 -0
  14. data/lib/local/command.rb +193 -0
  15. data/lib/{prick → local}/git.rb +148 -22
  16. data/lib/local/timer.rb +98 -0
  17. data/lib/prick/constants.rb +54 -66
  18. data/lib/prick/diff.rb +28 -18
  19. data/lib/prick/prick_version.rb +161 -0
  20. data/lib/prick/state.rb +80 -165
  21. data/lib/prick/version.rb +2 -163
  22. data/lib/prick.rb +38 -24
  23. data/lib/share/init/.gitignore +10 -0
  24. data/lib/share/init/.prick-context +2 -0
  25. data/lib/share/init/.rspec +3 -0
  26. data/{share/schema/schema/public → lib/share/init/migration}/.keep +0 -0
  27. data/lib/share/init/prick.yml +6 -0
  28. data/lib/share/init/schema/.keep +0 -0
  29. data/lib/share/init/schema/build.yml +2 -0
  30. data/lib/share/init/schema/prick/.keep +0 -0
  31. data/lib/share/init/schema/prick/build.yml +5 -0
  32. data/lib/share/init/schema/prick/data.sql +6 -0
  33. data/{share/schema → lib/share/init}/schema/prick/tables.sql +2 -3
  34. data/lib/share/init/schema/public/.keep +0 -0
  35. data/lib/share/init/spec/prick_helper.rb +1 -0
  36. data/lib/share/init/spec/prick_spec.rb +6 -0
  37. data/lib/share/init/spec/spec_helper.rb +50 -0
  38. data/lib/share/migrate/migration/build.yml +4 -0
  39. data/lib/share/migrate/migration/diff.after-tables.sql +0 -0
  40. data/lib/share/migrate/migration/diff.before-tables.sql +0 -0
  41. data/lib/share/migrate/migration/diff.tables.sql +0 -0
  42. data/lib/subcommand/prick-build.rb +55 -0
  43. data/lib/subcommand/prick-create.rb +78 -0
  44. data/lib/subcommand/prick-drop.rb +25 -0
  45. data/lib/subcommand/prick-fox.rb +62 -0
  46. data/lib/subcommand/prick-init.rb +46 -0
  47. data/lib/subcommand/prick-make.rb +202 -0
  48. data/lib/subcommand/prick-migrate.rb +37 -0
  49. data/lib/subcommand/prick-release.rb +23 -0
  50. data/lib/subcommand/prick-setup.rb +20 -0
  51. data/lib/subcommand/prick-teardown.rb +18 -0
  52. data/prick.gemspec +32 -21
  53. metadata +95 -76
  54. data/.gitignore +0 -29
  55. data/.travis.yml +0 -7
  56. data/doc/create_release.txt +0 -17
  57. data/doc/flow.txt +0 -98
  58. data/doc/migra +0 -1
  59. data/doc/migrations.txt +0 -172
  60. data/doc/notes.txt +0 -116
  61. data/doc/prick.txt +0 -114
  62. data/doc/sh.prick +0 -316
  63. data/lib/ext/algorithm.rb +0 -14
  64. data/lib/ext/fileutils.rb +0 -26
  65. data/lib/ext/forward_method.rb +0 -18
  66. data/lib/ext/pg.rb +0 -18
  67. data/lib/ext/shortest_path.rb +0 -44
  68. data/lib/prick/archive.rb +0 -124
  69. data/lib/prick/branch.rb +0 -265
  70. data/lib/prick/builder.rb +0 -246
  71. data/lib/prick/cache.rb +0 -34
  72. data/lib/prick/command.rb +0 -104
  73. data/lib/prick/database.rb +0 -82
  74. data/lib/prick/dsort.rb +0 -151
  75. data/lib/prick/ensure.rb +0 -119
  76. data/lib/prick/exceptions.rb +0 -25
  77. data/lib/prick/head.rb +0 -189
  78. data/lib/prick/migration.rb +0 -70
  79. data/lib/prick/program.rb +0 -287
  80. data/lib/prick/project.rb +0 -626
  81. data/lib/prick/rdbms.rb +0 -137
  82. data/lib/prick/schema.rb +0 -27
  83. data/lib/prick/share.rb +0 -64
  84. data/libexec/strip-comments +0 -33
  85. data/make_releases +0 -72
  86. data/make_schema +0 -10
  87. data/share/diff/diff.after-tables.sql +0 -4
  88. data/share/diff/diff.before-tables.sql +0 -4
  89. data/share/diff/diff.tables.sql +0 -8
  90. data/share/features/diff.sql +0 -2
  91. data/share/features/feature/diff.sql +0 -2
  92. data/share/features/feature/migrate.sql +0 -2
  93. data/share/features/features.sql +0 -2
  94. data/share/features/features.yml +0 -2
  95. data/share/features/migrations.sql +0 -4
  96. data/share/gitignore +0 -2
  97. data/share/migration/diff.tables.sql +0 -8
  98. data/share/migration/features.yml +0 -6
  99. data/share/migration/migrate.sql +0 -3
  100. data/share/migration/migrate.yml +0 -8
  101. data/share/migration/tables.sql +0 -3
  102. data/share/schema/build.yml +0 -14
  103. data/share/schema/schema/build.yml +0 -3
  104. data/share/schema/schema/prick/build.yml +0 -14
  105. data/share/schema/schema/prick/data.sql +0 -7
  106. data/share/schema/schema/prick/schema.sql +0 -3
  107. data/share/schema/schema/public/build.yml +0 -13
  108. data/share/schema/schema.sql +0 -3
  109. data/test_assorted +0 -192
  110. data/test_feature +0 -112
  111. data/test_refactor +0 -34
  112. data/test_single_dev +0 -83
@@ -1,35 +1,161 @@
1
1
 
2
- require "prick/command.rb"
3
-
4
2
  module Prick
5
- # Tags have a 'v' prefixed the version in the git repository but this is made
6
- # transparent to the application
7
3
  module Git
8
- def self.init
9
- Command.command "git init"
4
+ # Return the origin of the repository
5
+ def self.origin()
6
+ Command.command("git remote get-url origin").first
7
+ end
8
+
9
+ # Clone a repository
10
+ def self.clone(url, directory = nil, branch: nil)
11
+ branch_arg = branch ? "--branch #{branch}" : ""
12
+ Command.command("git clone --quiet #{branch_arg} '#{url}' #{directory}")
13
+ end
14
+
15
+ # Return the current commit id
16
+ def self.id() Command.command("git rev-parse HEAD").first end
17
+
18
+ # Return true if the repository has no modified files or unresolved
19
+ # conflicts. Requires the repository to have at least one commit
20
+ def self.clean?(file = nil)
21
+ re = file ? /^ #{file}(?: .*)?$/ : /^\?\?|!!/
22
+ !Command.command("git status --porcelain").any? { |l| l !~ re }
23
+ end
24
+
25
+ # Return true if the repo is synchronized with the remote
26
+ def self.synchronized?()
27
+ out = Command.command "git rev-list --count --left-right 'HEAD...@{upstream}'"
28
+ !(out.first =~ /^0\s+0$/).nil?
10
29
  end
11
30
 
12
- # Returns true if the repository has no modified files or unresolved
13
- # conflicts. Requires the repository to have at least one commit. Creating
14
- # the repository using Project::initialize_directory guarantees that
15
- def self.clean?()
16
- Command.command("git status").find { |l|
17
- (l =~ /^Changes to be committed:/) ||
18
- (l =~ /^Unmerged paths:/) ||
19
- (l =~ /^Changes not staged for commit:/)
20
- }.nil?
31
+ # Add files to the index
32
+ def self.add(*files)
33
+ files = Array(files).flatten
34
+ Command.command "git add #{files.join(" ")}"
35
+ end
36
+
37
+ # True if the file was added to the index
38
+ def self.added?(file = nil)
39
+ re = file ? /^[ACDMR]. #{file}$/ : /^[ACDMR]/
40
+ Command.command("git status --porcelain").any? { |l| l =~ re }
41
+ end
42
+
43
+ # Commit changes on the current branch"
44
+ def self.commit(msg)
45
+ out = Command.command "git commit -m '#{msg}'", fail: false
46
+ Command.status == 0 or raise Command.exception.exception(out.join("\n"))
21
47
  end
22
48
 
23
- # Returns true if the repository is on a detached branch
24
- def self.detached?
25
- Command.command? "git symbolic-ref -q HEAD", expect: 1
49
+ # Pull changes from repository
50
+ def self.pull
51
+ Command.command "git pull"
26
52
  end
27
53
 
28
- # The current tag. This is only defined if the repository is in "detached
29
- # head" mode
30
- def self.current_tag()
31
- self.detached? ? Command.command("git describe --tags").first.sub(/^v/, "") : nil
54
+ # Push change to repository
55
+ def self.push
56
+ Command.command "git push --quiet --atomic"
32
57
  end
58
+
59
+ # Access to tag methods
60
+ def self.tag() Tag end
61
+
62
+ # Access to branch methods
63
+ def self.branch() Branch end
64
+
65
+
66
+ module Tag
67
+ # The associated commit ID of a tag
68
+ def self.id(tag)
69
+ tag && Command.command("git rev-list -n 1 #{tag}").first
70
+ end
71
+
72
+ # True if tag exists
73
+ def self.exist?(tag)
74
+ tag && Command.command?("git describe --tags #{tag}")
75
+ end
76
+
77
+ # Create tag
78
+ def self.create(tag, id: nil)
79
+ Command.command "git tag '#{tag}' #{id}"
80
+ end
81
+
82
+ # Drop a tag
83
+ def self.drop(tag)
84
+ Command.command "git tag -d '#{tag}'"
85
+ end
86
+
87
+ # Return list of all tags. Not in any particular order
88
+ def self.list()
89
+ Command.command("git tag")
90
+ end
91
+
92
+ # Return the most recent tag before the given commit (defaults to the
93
+ # last commit)
94
+ def self.current(id = nil)
95
+ describe_tag(id)&.first
96
+ end
97
+
98
+ private
99
+ # Return a [tag, number-of-commits, commit-id] tuple of the most recent
100
+ # tag. Return nil if no tag was found
101
+ def self.describe_tag(id = nil)
102
+ stdout, stderr = Command.command("git describe --tags #{id}", stderr: true, fail: false)
103
+ if Command.status != 0
104
+ return nil if stderr.first =~ /No names found/
105
+ raise Command.exception
106
+ end
107
+ if stdout.first =~ /^(.*)-(\d+)-.([0-9a-f]{7})$/
108
+ [$1, $2, $3]
109
+ else
110
+ [stdout.first, 0, nil]
111
+ end
112
+ end
113
+ end
114
+
115
+ module Branch
116
+ def self.exist?(branch)
117
+ Command.command? "git show-ref --verify --quiet refs/heads/#{branch}"
118
+ end
119
+
120
+ def self.create(branch, id = nil, set_upstream: true)
121
+ if set_upstream
122
+ current = Git.branch.current
123
+ Command.command %(
124
+ git checkout --quiet -b #{branch} #{id}
125
+ git push --quiet --set-upstream origin #{branch}
126
+ git checkout --quiet #{current}
127
+ )
128
+ else
129
+ Command.command "git branch #{branch} #{id}"
130
+ end
131
+ end
132
+
133
+ def self.drop(branch)
134
+ Command.command "git branch -D #{branch}"
135
+ end
136
+
137
+ def self.list()
138
+ Command.command "git for-each-ref --format='%(refname:short)' refs/heads/*"
139
+ end
140
+
141
+ def self.current()
142
+ Command.command("git branch --show-current").first
143
+ end
144
+
145
+ def self.checkout(branch)
146
+ Command.command "git checkout --quiet #{branch}"
147
+ end
148
+ end
149
+ end
150
+ end
151
+
152
+ __END__
153
+
154
+ def self.changed?(file)
155
+ Command.command("git status --porcelain").any? { |l| l =~ /^.M #{file}$/ }
156
+ end
157
+
158
+
33
159
 
34
160
  # Return true if `version` has an associated tag
35
161
  def self.tag?(version)
@@ -0,0 +1,98 @@
1
+
2
+ # TODO: Move class method implementations to Timer::Timer
3
+ module Timer
4
+ def self.on!() @on = true end
5
+ def self.off!() @on = false end
6
+ def self.on?() @on end
7
+ def self.off?() !@on end
8
+
9
+ # Output file used by #emit. Default is STDERR (this is set at load-time)
10
+ def self.file() @file end
11
+ def self.file=(file) @file = file end
12
+
13
+ # Currently only :s, or :ms. Default is :ms
14
+ def self.unit() @unit end
15
+ def self.unit=(unit) @unit = unit end
16
+
17
+ # Number of digits after the decimal point. Default 0
18
+ def self.scale() @scale end
19
+ def self.scale=(scale) @scale = scale end
20
+
21
+ def self.time(label, &block)
22
+ if on?
23
+ timer = Timer.new(label)
24
+ r = yield
25
+ timer.emit
26
+ r
27
+ else
28
+ yield
29
+ end
30
+ end
31
+
32
+ def time(label, &block) ::Timer.time(label, &block) end
33
+
34
+ def self.new(*args)
35
+ object = Timer.allocate
36
+ object.send(:initialize, *args)
37
+ object
38
+ end
39
+
40
+ class Timer
41
+ attr_reader :title
42
+
43
+ def file() ::Timer.file end
44
+
45
+ def self.on!() ::Timer.on! end
46
+ def self.on?() ::Time.on? end
47
+
48
+ def unit() ::Timer.unit end
49
+ def unit=(unit) ::Timer.unit = unit end
50
+
51
+ def scale() ::Timer.scale end
52
+ def scale=(scale) ::Timer.scale scale end
53
+
54
+ def initialize(title = nil)
55
+ @title = title
56
+ start
57
+ end
58
+
59
+ def start(title = self.title) @t0 = Time.now; @title = title end
60
+ def stop() @t1 ||= Time.now end
61
+ def time() @t1 - @t0 end
62
+
63
+ def to_s(title = self.title) "#{title}: #{Timer.format(time)}" end
64
+
65
+ def emit(title = self.title)
66
+ stop
67
+ ::Timer.file.puts to_s(title) if ::Timer.on?
68
+ end
69
+
70
+ def self.factor() { s: 1, ms: 1000 }[::Timer.unit] end
71
+
72
+ def self.format(time)
73
+ time ? sprintf("%.#{::Timer.scale}f#{::Timer.unit}", Timer.factor * time) : time.inspect
74
+ end
75
+ end
76
+
77
+ private
78
+ @on = false
79
+ @file = STDERR
80
+ @unit = :ms
81
+ @scale = 0
82
+ end
83
+
84
+
85
+
86
+
87
+
88
+
89
+
90
+ #def time(title, &block)
91
+ # t0 = Time.now
92
+ # result = yield
93
+ # t1 = Time.now
94
+ # dt = (1000 * (t1 - t0)).round(0)
95
+ # puts "#{title}: #{dt}ms" if defined?(TIME) ? TIME : true
96
+ # result
97
+ #end
98
+
@@ -1,10 +1,14 @@
1
1
 
2
2
  module Prick
3
+ ### TIME
4
+
5
+ EPOCH = Time.at(0).utc
6
+
3
7
  ### DIRECTORIES AND FILE NAMES
4
8
 
5
9
  # Shared files (part of the installation)
6
- SHARE_PATH = "#{File.dirname(File.dirname(__dir__))}/share"
7
- LIBEXEC_PATH = "#{File.dirname(File.dirname(__dir__))}/libexec"
10
+ SHARE_PATH = "#{File.dirname(File.dirname(__dir__))}/lib/share"
11
+ LIBEXEC_PATH = "#{File.dirname(File.dirname(__dir__))}/lib/libexec"
8
12
 
9
13
  # Project directories
10
14
  DIRS = [
@@ -16,22 +20,52 @@ module Prick
16
20
  CACHE_DIR = "#{VAR_DIR}/cache",
17
21
  SPOOL_DIR = "#{VAR_DIR}/spool",
18
22
  TMP_DIR = "tmp",
19
- CLONE_DIR = "tmp/clone",
23
+ CLONE_DIR = "tmp/clones",
20
24
  SPEC_DIR = "spec"
21
25
  ]
22
26
 
23
- # The project state file
24
- PROJECT_STATE_FILE = ".prick-project"
25
- PROJECT_STATE_PATH = PROJECT_STATE_FILE
27
+ # Project specification file
28
+ PRICK_PROJECT_FILE = "prick.yml"
29
+ PRICK_PROJECT_PATH = PRICK_PROJECT_FILE
30
+
31
+ # Context file
32
+ PRICK_CONTEXT_FILE = ".prick-context"
33
+ PRICK_CONTEXT_PATH = PRICK_CONTEXT_FILE
26
34
 
27
- # The .prick-version file
28
- PRICK_VERSION_FILE = ".prick-version"
29
- PRICK_VERSION_PATH = PRICK_VERSION_FILE
35
+ # Fox state file (contains anchors and table sizes)
36
+ FOX_STATE_FILE = ".fox-state.yml"
37
+ FOX_STATE_PATH = FOX_STATE_FILE
30
38
 
31
- # The prick.versions data file. This is where the Schema saves its version
39
+ # Reflections file
40
+ REFLECTIONS_FILE = "reflections.yml"
41
+ REFLECTIONS_PATH = File.join(SCHEMA_DIR, REFLECTIONS_FILE)
42
+
43
+ # Schema data file
32
44
  SCHEMA_VERSION_FILE = "data.sql"
33
45
  SCHEMA_VERSION_PATH = File.join(PRICK_DIR, SCHEMA_VERSION_FILE)
34
46
 
47
+ # Rspec temporary directory
48
+ SPEC_TMP_DIR = "spec"
49
+ SPEC_TMP_PATH = File.join(TMP_DIR, SPEC_TMP_DIR)
50
+
51
+
52
+ # Migration diff files
53
+ DIFF_FILE = "diff.sql"
54
+ DIFF_FILES = [
55
+ BEFORE_TABLES_DIFF_FILE = "diff.before-tables.sql",
56
+ TABLES_DIFF_FILE = "diff.tables.sql",
57
+ AFTER_TABLES_DIFF_FILE = "diff.after-tables.sql"
58
+ ]
59
+
60
+
61
+
62
+
63
+ # Not in use:
64
+
65
+ # The project state file
66
+ PROJECT_STATE_FILE = ".prick-project"
67
+ PROJECT_STATE_PATH = PROJECT_STATE_FILE
68
+
35
69
  # The the .prick-migration file
36
70
  PRICK_MIGRATION_FILE = ".prick-migration"
37
71
  PRICK_MIGRATION_PATH = File.join(MIGRATION_DIR, PRICK_MIGRATION_FILE)
@@ -45,16 +79,16 @@ module Prick
45
79
  STRIP_COMMENTS_PATH = File.join(LIBEXEC_PATH, "strip-comments")
46
80
 
47
81
  # Diff files
48
- DIFF_FILE = "diff.sql"
49
- BEFORE_TABLES_DIFF_NAME = "diff.before-tables.sql"
50
- TABLES_DIFF_NAME = "diff.tables.sql"
51
- AFTER_TABLES_DIFF_NAME = "diff.after-tables.sql"
82
+ # DIFF_FILE = "diff.sql"
83
+ # BEFORE_TABLES_DIFF_NAME = "diff.before-tables.sql"
84
+ # TABLES_DIFF_NAME = "diff.tables.sql"
85
+ # AFTER_TABLES_DIFF_NAME = "diff.after-tables.sql"
52
86
 
53
- BEFORE_TABLES_DIFF_PATH = File.join(MIGRATION_DIR, BEFORE_TABLES_DIFF_NAME)
54
- TABLES_DIFF_PATH = File.join(MIGRATION_DIR, TABLES_DIFF_NAME)
55
- AFTER_TABLES_DIFF_PATH = File.join(MIGRATION_DIR, AFTER_TABLES_DIFF_NAME)
56
-
57
- TABLES_DIFF_SHARE_PATH = File.join(SHARE_PATH, "diff", TABLES_DIFF_NAME)
87
+ # BEFORE_TABLES_DIFF_PATH = File.join(MIGRATION_DIR, BEFORE_TABLES_DIFF_NAME)
88
+ # TABLES_DIFF_PATH = File.join(MIGRATION_DIR, TABLES_DIFF_NAME)
89
+ # AFTER_TABLES_DIFF_PATH = File.join(MIGRATION_DIR, AFTER_TABLES_DIFF_NAME)
90
+ #
91
+ # TABLES_DIFF_SHARE_PATH = File.join(SHARE_PATH, "diff", TABLES_DIFF_NAME)
58
92
 
59
93
 
60
94
  # Dump files
@@ -174,53 +208,6 @@ module Prick
174
208
  /x
175
209
  ABSTRACT_RELEASE_RE = /^#{ABSTRACT_RELEASE_SUB_RE}$/
176
210
 
177
- # Matches a (proper) release
178
- #
179
- # The RE defines the following captures:
180
- # $1 - custom name, can be nil
181
- # $2 - semantic version
182
- #
183
- # RELEASE_SUB_RE = /
184
- # (?:(#{CUSTOM_NAME_SUB_RE})-)?
185
- # (#{MMP_SEMVER_SUB_RE})
186
- # /x
187
- # RELEASE_RE = /^#{RELEASE_SUB_RE}$/
188
-
189
- # Matches a prerelease branch
190
- #
191
- # The RE defines the following captures:
192
- # $1 - custom name, can be nil
193
- # $2 - semantic version
194
- #
195
- # PRERELEASE_SUB_RE = /
196
- # (?:(#{CUSTOM_NAME_SUB_RE})-)?
197
- # (#{MMP_SEMVER_SUB_RE}-#{PRE_SEMVER_SUB_RE})
198
- # /x
199
- # PRERELEASE_RE = /^#{PRERELEASE_SUB_RE}$/
200
-
201
- # Matches a feature branch
202
- #
203
- # The RE defines the following captures:
204
- # $1 - custom name, can be nil
205
- # $2 - semantic version
206
- # $3 - feature name
207
- #
208
- # FEATURE_SUB_RE = /#{ABSTRACT_RELEASE_SUB_RE}_(#{FEATURE_NAME_SUB_RE})/
209
- # FEATURE_RE = /^#{FEATURE_SUB_RE}$/
210
-
211
- # Migration RE. The syntax is <version>_<version>
212
- #
213
- # The RE defines the following captures
214
- # $1 - from version
215
- # $2 - from custom name, can be nil
216
- # $3 - from semantic version
217
- # $4 - to version
218
- # $5 - to custom name, can be nil
219
- # $6 - to semantic version
220
- #
221
- # MIGRATION_SUB_RE = /(#{RELEASE_SUB_RE})_(#{RELEASE_SUB_RE})/
222
- # MIGRATION_RE = /^#{MIGRATION_SUB_RE}$/
223
-
224
211
  # Project release RE. The general syntax is '<project>-<custom>-<version>'
225
212
  #
226
213
  # The RE defines the following captures:
@@ -271,3 +258,4 @@ module Prick
271
258
  def self.tmp_databases_re(project_name) /^#{tmp_databases_sub_re(project_name)}$/ end
272
259
  end
273
260
 
261
+
data/lib/prick/diff.rb CHANGED
@@ -1,14 +1,17 @@
1
1
  module Prick
2
+ # Database diff
2
3
  class Diff
3
4
  # Diff as a list of lines
4
5
  attr_reader :diff
5
6
 
6
- # Subject of the diff
7
+ # Diff split into before/after table changes
7
8
  attr_reader :before_table_changes
8
9
  attr_reader :table_changes
9
10
  attr_reader :after_table_changes
10
11
 
11
12
  def initialize(db1, db2)
13
+ constrain db1, String
14
+ constrain db2, String
12
15
  @db1, @db2 = db1, db2
13
16
  do_diff
14
17
  split_on_table_changes
@@ -17,40 +20,35 @@ module Prick
17
20
  def self.same?(db1, db2) Diff.new(db1, db2).same? end
18
21
 
19
22
  # Return true if the two databases are equal. Named #same? to avoid name
20
- # collision with the built in #equal?
23
+ # collision with the built-in #equal? method
21
24
  def same?() diff.empty? end
22
25
 
26
+ # :call-seq:
27
+ # write(file, mark: true)
28
+ # write(file1, file2, file3, mark: false)
29
+ #
23
30
  # Write the diff between the databases to the given file(s). Return true if
24
31
  # the databases are equal
25
- def write_segments(file1, file2 = nil, file3 = nil, mark: true)
32
+ def write(file1, file2 = nil, file3 = nil, mark: nil)
26
33
  if file2.nil? && file3.nil?
27
34
  file2 = file3 = file1
28
- elsif file2.nil? ^ !file3.nil?
29
- raise Internal, "Either none or both of `file2` and `file3` should be nil"
35
+ mark = mark.nil? ? true : mark
36
+ elsif file2.nil? != file3.nil?
37
+ raise Prick::Fail, "Either none or both of `file2` and `file3` should be nil"
38
+ else
39
+ mark = mark.nil? ? false : mark
30
40
  end
31
41
  write_segment(file1, :before_table_changes, mark: mark)
32
42
  write_segment(file2, :table_changes, mark: mark)
33
43
  write_segment(file3, :after_table_changes, mark: mark)
34
44
  end
35
45
 
36
- def write_segment(file, segment, mark: true)
37
- lines = self.send(segment)
38
- if lines.empty?
39
- FileUtils.rm_f(file) if file != "/dev/stdout"
40
- else
41
- File.open(file, "w") { |f|
42
- f.puts "--" + segment.to_s.gsub("_", " ").upcase if mark
43
- f.puts lines
44
- }
45
- end
46
- end
47
-
48
46
  private
49
47
  def do_diff(file = nil)
50
48
  command = "migra --unsafe --with-privileges postgres:///#{@db1} postgres:///#{@db2}"
51
49
  @diff = Command.command(command, fail: false)
52
50
  [0,2].include?(Command.status) or
53
- raise Internal, "migrate command failed with status #{Command.status}: #{command}"
51
+ raise Prick::Fail, "migrate command failed with status #{Command.status}: #{command}"
54
52
  end
55
53
 
56
54
  # Initialize table change variables
@@ -110,6 +108,18 @@ module Prick
110
108
  end
111
109
  }
112
110
  end
111
+
112
+ def write_segment(file, segment, mark: true)
113
+ lines = self.send(segment)
114
+ if lines.empty?
115
+ FileUtils.touch(file)
116
+ else
117
+ File.open(file, "w") { |f|
118
+ f.puts "-- " + segment.to_s.gsub("_", " ").upcase if mark
119
+ f.puts lines
120
+ }
121
+ end
122
+ end
113
123
  end
114
124
  end
115
125
 
@@ -0,0 +1,161 @@
1
+
2
+ require 'semantic' # https://github.com/jlindsey/semantic
3
+
4
+ # Project related code starts here
5
+ module Prick
6
+ class PrickVersion
7
+ class FormatError < RuntimeError; end
8
+
9
+ include Comparable
10
+
11
+ PRE_LABEL = "pre"
12
+ PRE_RE = /^#{PRE_LABEL}\.(\d+)$/
13
+
14
+ def self.zero() PrickVersion.new("0.0.0") end
15
+ def zero?() self == PrickVersion.zero end
16
+
17
+ # Return true if `string` is a version
18
+ def self.version?(string)
19
+ string.is_a?(String) or raise Internal, "String expected"
20
+ !(string =~ VERSION_RE).nil?
21
+ end
22
+
23
+ attr_accessor :fork
24
+ attr_accessor :semver
25
+ attr_accessor :feature
26
+
27
+ def major() @semver.major end
28
+ def major=(major) @semver.major = major end
29
+
30
+ def minor() @semver.minor end
31
+ def minor=(minor) @semver.minor = minor end
32
+
33
+ def patch() @semver.patch end
34
+ def patch=(patch) @semver.patch = patch end
35
+
36
+ # Return true if this is a fork release
37
+ def fork?() !@fork.nil? end
38
+
39
+ # Return true if this is a feature release
40
+ def feature?() !@feature.nil? end
41
+
42
+ # Return true if this is a release branch (and not a prerelease)
43
+ def release?() !feature? && !pre? end
44
+
45
+ # Return true if this is a pre-release
46
+ def pre?() !@semver.pre.nil? end
47
+ def prerelease?() pre? end
48
+
49
+ # The releases is stored as a String (eg. 'pre.1') in the semantic version
50
+ # but #pre returns only the Integer number
51
+ def pre() @semver.pre =~ PRE_RE ? $1.to_i : nil end
52
+ def prerelease() pre end
53
+
54
+ # #pre= expects an integer or nil argument
55
+ def pre=(pre) @semver.pre = (pre ? "#{PRE_LABEL}.#{pre}" : nil) end
56
+ def prerelease=(pre) self.pre = pre end
57
+
58
+ def dup() PrickVersion.new(self) end
59
+ def clone() PrickVersion.new(self) end
60
+
61
+ def eql?(other) self == other end
62
+ def hash() @semver.hash end
63
+
64
+ def initialize(version, fork: nil, feature: nil)
65
+ case version
66
+ when String
67
+ version =~ VERSION_RE or raise PrickVersion::FormatError, "Expected a version, got #{version.inspect}"
68
+ @fork = fork || $1
69
+ @semver = Semantic::Version.new($3)
70
+ @feature = feature || $4
71
+ when Semantic::Version
72
+ @fork = fork
73
+ @semver = version.dup
74
+ @feature = feature
75
+ when PrickVersion
76
+ @fork = fork || version.fork
77
+ @semver = version.semver.dup
78
+ @feature = feature || version.feature
79
+ else
80
+ raise Internal, "Expected a String, PrickVersion, or Semantic::Version, got #{version.class}"
81
+ end
82
+ end
83
+
84
+ # Try converting the string `version` into a PrickVersion object. Return nil if unsuccessful
85
+ def self.try(version)
86
+ version.is_a?(PrickVersion) ? version : (version?(version) ? new(version) : nil)
87
+ end
88
+
89
+ # Parse a branch or tag name into a PrickVersion object. Return a [version, tag]
90
+ # tuple where tag is true if name was a tag
91
+ def self.parse(name)
92
+ name =~ VERSION_RE or raise PrickVersion::FormatError, "Expected a version, got #{name.inspect}"
93
+ fork, tag, semver, feature = $1, $2, $3, $4
94
+ version = PrickVersion.new(semver, fork: fork, feature: feature)
95
+ [version, tag]
96
+ end
97
+
98
+ # `part` can be one of :major, :minor, :patch, or :pre. If pre is undefined, it
99
+ # is set to `pre_initial_value`
100
+ def increment(part, pre_initial_value = 1)
101
+ self.dup.increment!(part, pre_initial_value)
102
+ end
103
+
104
+ def increment!(part, pre_initial_value = 1)
105
+ if part == :pre
106
+ self.pre = (self.pre ? self.pre + 1 : pre_initial_value)
107
+ else
108
+ @semver = semver.increment!(part)
109
+ end
110
+ self
111
+ end
112
+
113
+ def truncate(part)
114
+ case part
115
+ when :pre
116
+ v = self.dup
117
+ v.feature = nil
118
+ v.pre = nil
119
+ v
120
+ when :feature
121
+ v = self.dup
122
+ v.feature = nil
123
+ v
124
+ else
125
+ raise NotYet
126
+ end
127
+ end
128
+
129
+ # def path
130
+ # parts = [FEATURE_DIR, truncate(:pre), feature].compact
131
+ # File.join(*parts)
132
+ # end
133
+
134
+ # def link
135
+ # !feature? or raise Internal, "PrickVersion #{to_s} is a feature, not a release"
136
+ # File.join(RELEASE_DIR, to_s)
137
+ # end
138
+
139
+ def <=>(other)
140
+ r = (fork || "") <=> (other.fork || "")
141
+ return r if r != 0
142
+ r = semver <=> other.semver
143
+ return r if r != 0
144
+ r = (feature || "") <=> (other.feature || "")
145
+ return r
146
+ end
147
+
148
+ # Render as branch
149
+ def to_s(tag: false)
150
+ (fork ? "#{fork}-" : "") + (tag ? "v" : "") + semver.to_s + (feature ? "_#{feature}" : "")
151
+ end
152
+
153
+ # Render as a tag
154
+ def to_tag() to_s(tag: true) end
155
+
156
+ # Render as string
157
+ def inspect() to_s end
158
+ end
159
+ end
160
+
161
+