prick 0.19.0 → 0.20.1

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