prick 0.2.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +6 -5
  3. data/Gemfile +4 -1
  4. data/TODO +10 -0
  5. data/doc/prick.txt +114 -0
  6. data/exe/prick +328 -402
  7. data/lib/ext/fileutils.rb +18 -0
  8. data/lib/ext/forward_method.rb +18 -0
  9. data/lib/ext/shortest_path.rb +44 -0
  10. data/lib/prick.rb +20 -10
  11. data/lib/prick/branch.rb +254 -0
  12. data/lib/prick/builder.rb +164 -0
  13. data/lib/prick/cache.rb +34 -0
  14. data/lib/prick/command.rb +19 -11
  15. data/lib/prick/constants.rb +122 -48
  16. data/lib/prick/database.rb +28 -20
  17. data/lib/prick/diff.rb +125 -0
  18. data/lib/prick/exceptions.rb +15 -3
  19. data/lib/prick/git.rb +77 -30
  20. data/lib/prick/head.rb +183 -0
  21. data/lib/prick/migration.rb +40 -200
  22. data/lib/prick/program.rb +493 -0
  23. data/lib/prick/project.rb +523 -351
  24. data/lib/prick/rdbms.rb +4 -13
  25. data/lib/prick/schema.rb +16 -90
  26. data/lib/prick/share.rb +64 -0
  27. data/lib/prick/state.rb +192 -0
  28. data/lib/prick/version.rb +62 -29
  29. data/libexec/strip-comments +33 -0
  30. data/make_releases +48 -345
  31. data/make_schema +10 -0
  32. data/prick.gemspec +14 -23
  33. data/share/diff/diff.after-tables.sql +4 -0
  34. data/share/diff/diff.before-tables.sql +4 -0
  35. data/share/diff/diff.tables.sql +8 -0
  36. data/share/migration/diff.tables.sql +8 -0
  37. data/share/migration/features.yml +6 -0
  38. data/share/migration/migrate.sql +3 -0
  39. data/share/migration/migrate.yml +8 -0
  40. data/share/migration/tables.sql +3 -0
  41. data/share/schema/build.yml +14 -0
  42. data/share/schema/schema.sql +5 -0
  43. data/share/schema/schema/build.yml +3 -0
  44. data/share/schema/schema/prick/build.yml +14 -0
  45. data/share/schema/schema/prick/data.sql +7 -0
  46. data/share/schema/schema/prick/schema.sql +5 -0
  47. data/share/{schemas/prick/schema.sql → schema/schema/prick/tables.sql} +2 -5
  48. data/{file → share/schema/schema/public/.keep} +0 -0
  49. data/share/schema/schema/public/build.yml +14 -0
  50. data/share/schema/schema/public/schema.sql +3 -0
  51. data/test_assorted +192 -0
  52. data/test_feature +112 -0
  53. data/test_refactor +34 -0
  54. data/test_single_dev +83 -0
  55. metadata +43 -68
  56. data/lib/prick/build.rb +0 -376
  57. data/lib/prick/migra.rb +0 -22
  58. data/share/schemas/prick/data.sql +0 -8
@@ -0,0 +1,34 @@
1
+
2
+ module Prick
3
+ class Cache
4
+ def initialize
5
+ end
6
+
7
+ def exist?(version)
8
+ File.exist?(file(version))
9
+ end
10
+
11
+ def list
12
+ Dir.glob(File.join(CACHE_DIR, "*.sql.gz"))
13
+ end
14
+
15
+ def load(database, version)
16
+ Rdbms.load(database, file(version))
17
+ end
18
+
19
+ def save(database, version = database.version)
20
+ Rdbms.save(database, file(version))
21
+ end
22
+
23
+ # Returns list of removed files
24
+ def clean
25
+ l = list
26
+ FileUtils.rm(l)
27
+ l
28
+ end
29
+
30
+ def file(version)
31
+ File.join(CACHE_DIR, "#{version}.sql.gz")
32
+ end
33
+ end
34
+ end
data/lib/prick/command.rb CHANGED
@@ -14,14 +14,13 @@ module Command
14
14
  end
15
15
  end
16
16
 
17
- # Execute the shell command 'cmd' and return the output as an array of
18
- # strings
19
- #
20
- # If :stderr is true, it returns return a tuple of [stdout, stderr] instead
21
- #
22
- # The shell command is executed with the `errexit` and `pipefail` bash
23
- # options. It raises a Command::Error exception if the command fails unless
24
- # :fail is true. The exit status of the last command is stored in ::status
17
+ # Execute the shell command 'cmd' and return standard output as an array of
18
+ # strings while stderr is passed through unless stderr: is false. If stderr:
19
+ # is true, it returns a tuple of [stdout, stderr] instead. The shell command
20
+ # is executed with the `errexit` and `pipefail` bash options
21
+ #
22
+ # It raises a Command::Error exception if the command fails unless :fail is
23
+ # true. The exit status of the last command is stored in ::status
25
24
  #
26
25
  def command(cmd, stderr: false, fail: true)
27
26
  cmd = "set -o errexit\nset -o pipefail\n#{cmd}"
@@ -62,10 +61,19 @@ module Command
62
61
  pr[0].close
63
62
  pe[0].close
64
63
 
64
+ result =
65
+ case stderr
66
+ when true; [out, err]
67
+ when false; out
68
+ when NilClass; $stderr.puts err
69
+ else
70
+ raise Internal, "Unexpected value for :stderr - #{stderr.inspect}"
71
+ end
72
+
65
73
  if @status == 0 || fail == false
66
- stderr ? [out, err] : out
67
- else
68
- raise Command::Error.new((out + err).join("\n") + "\n", status, out, err)
74
+ result
75
+ elsif fail
76
+ raise Command::Error.new("\n" + cmd + "\n" + (out + err).join("\n") + "\n", status, out, err)
69
77
  end
70
78
  end
71
79
 
@@ -1,62 +1,96 @@
1
1
 
2
2
  module Prick
3
+ ### DIRECTORIES AND FILE NAMES
4
+
3
5
  # Shared files (part of the installation)
4
6
  SHARE_PATH = "#{File.dirname(File.dirname(__dir__))}/share"
7
+ LIBEXEC_PATH = "#{File.dirname(File.dirname(__dir__))}/libexec"
5
8
 
6
9
  # Project directories
7
10
  DIRS = [
8
- RELEASE_DIR = "releases",
9
- MIGRATION_DIR = "migrations",
10
- FEATURE_DIR = "features",
11
- SCHEMA_DIR = "schemas",
11
+ MIGRATION_DIR = "migration",
12
+ SCHEMA_DIR = "schema",
12
13
  PRICK_DIR = "#{SCHEMA_DIR}/prick",
13
14
  PUBLIC_DIR = "#{SCHEMA_DIR}/public",
14
15
  VAR_DIR = "var",
15
16
  CACHE_DIR = "#{VAR_DIR}/cache",
17
+ SPOOL_DIR = "#{VAR_DIR}/spool",
16
18
  TMP_DIR = "tmp",
17
19
  CLONE_DIR = "tmp/clone",
18
20
  SPEC_DIR = "spec"
19
21
  ]
20
22
 
23
+ # The project state file
24
+ PROJECT_STATE_FILE = ".prick-project"
25
+ PROJECT_STATE_PATH = PROJECT_STATE_FILE
26
+
27
+ # The .prick-version file
28
+ PRICK_VERSION_FILE = ".prick-version"
29
+ PRICK_VERSION_PATH = PRICK_VERSION_FILE
30
+
31
+ # The prick.versions data file. This is where the Schema saves its version
32
+ SCHEMA_VERSION_FILE = "data.sql"
33
+ SCHEMA_VERSION_PATH = File.join(PRICK_DIR, SCHEMA_VERSION_FILE)
34
+
35
+ # The the .prick-migration file
36
+ PRICK_MIGRATION_FILE = ".prick-migration"
37
+ PRICK_MIGRATION_PATH = File.join(MIGRATION_DIR, PRICK_MIGRATION_FILE)
38
+
39
+ # The the .prick-feature file
40
+ PRICK_FEATURE_FILE = ".prick-feature"
41
+ PRICK_FEATURE_PATH = File.join(MIGRATION_DIR, PRICK_FEATURE_FILE)
42
+
43
+ # The strip-comments executable
44
+ STRIP_COMMENTS_NAME = "strip-comments"
45
+ STRIP_COMMENTS_PATH = File.join(LIBEXEC_PATH, "strip-comments")
46
+
47
+ # 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"
52
+
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)
58
+
59
+
21
60
  # Dump files
22
61
  DUMP_EXT = "dump.gz"
23
62
  DUMP_GLOB = "*-[0-9]*.#{DUMP_EXT}"
24
63
  def self.dump_glob(project_name) "#{project_name}-*.#{DUMP_EXT}" end
25
64
 
65
+ ### REGULAR EXPRESSIONS
66
+
67
+ # Matches a system name. System names are used for objects that are external to prick
68
+ # (like usernames)
69
+ NAME_SUB_RE = /[a-z][a-z0-9_-]*/
70
+ NAME_RE = /^#{NAME_SUB_RE}$/
26
71
 
27
72
  # Matches an identifier. Identifiers consist of lower case letters, digits
28
73
  # and underscores but not dashes because they're used as separators
29
74
  IDENT_SUB_RE = /[a-z][a-z0-9_]*/
30
75
  IDENT_RE = /^#{IDENT_SUB_RE}$/
31
76
 
32
- # Matches an uppercase identifier
33
- # UPCASE_IDENT_SUB_RE = /[A-Z][A-Z0-9_]*/
34
- # UPCASE_IDENT_RE = /#{UPCASE_IDENT_SUB_RE}/
35
-
36
- # A (system) name. Names are used for projects and usernames that are
37
- # external to prick and can include both dashes and underscores but dashes
38
- # have to be followed by a character and not a digit so 'ident-1234' is not
39
- # allowed but 'ident_1234' and 'ident1234' are
40
- NAME_SUB_RE = /#{IDENT_SUB_RE}/
41
- NAME_RE = /^#{NAME_SUB_RE}$/
42
-
43
77
  # Matches a project name
44
- PROJECT_NAME_SUB_RE = NAME_SUB_RE
45
- PROJECT_NAME_RE = NAME_RE
78
+ PROJECT_NAME_SUB_RE = IDENT_SUB_RE
79
+ PROJECT_NAME_RE = IDENT_RE
46
80
 
47
81
  # Matches a custom name
48
- CUSTOM_NAME_SUB_RE = NAME_SUB_RE
49
- CUSTOM_NAME_RE = NAME_RE
82
+ CUSTOM_NAME_SUB_RE = IDENT_SUB_RE
83
+ CUSTOM_NAME_RE = IDENT_RE
50
84
 
51
85
  # Matches a feature name
52
- FEATURE_NAME_SUB_RE = NAME_SUB_RE
53
- FEATURE_NAME_RE = NAME_RE
86
+ FEATURE_NAME_SUB_RE = /(?!initial)#{IDENT_SUB_RE}/
87
+ FEATURE_NAME_RE = /^#{FEATURE_NAME_SUB_RE}$/
54
88
 
55
89
  # Matches a postgres user name
56
90
  USER_NAME_SUB_RE = NAME_SUB_RE
57
91
  USER_NAME_RE = NAME_RE
58
92
 
59
- # Matches a major.minor.patch version
93
+ # Matches a major.minor.patch ('MMP') version
60
94
  #
61
95
  # The *_SEMVER REs are derived from the canonical RE
62
96
  #
@@ -81,15 +115,48 @@ module Prick
81
115
  SEMVER_SUB_RE = /#{MMP_SEMVER_SUB_RE}(?:-#{PRE_SEMVER_SUB_RE})?/
82
116
  SEMVER_RE = /^#{SEMVER_SUB_RE}$/
83
117
 
84
- # Version RE. The general syntax for a version is '<custom>-<version>_<feature>'
118
+ # Tag RE. The general syntax for a tag is '<custom>-v<version>_<feature>'
85
119
  #
86
120
  # The RE defines the following captures:
87
121
  # $1 - custom name, can be nil
88
122
  # $2 - semantic version
89
123
  # $3 - feature name, can be nil
90
124
  #
125
+ TAG_SUB_RE = /
126
+ (?:(#{CUSTOM_NAME_SUB_RE})-)?
127
+ v
128
+ (#{SEMVER_SUB_RE})
129
+ (?:_(#{FEATURE_NAME_SUB_RE}))?
130
+ /x
131
+ TAG_RE = /^#{TAG_SUB_RE}$/
132
+
133
+ # TODO: Handle initial branches (0.0.0-initial)
134
+
135
+ # Branch RE. The general syntax for a branch is '<custom>-<version>_<feature>'
136
+ #
137
+ # The RE defines the following captures:
138
+ # $1 - custom name, can be nil
139
+ # $2 - semantic version
140
+ # $3 - feature name, can be nil
141
+ #
142
+ BRANCH_SUB_RE = /
143
+ (?:(#{CUSTOM_NAME_SUB_RE})-)?
144
+ (#{SEMVER_SUB_RE})
145
+ (?:_(#{FEATURE_NAME_SUB_RE}))?
146
+ /x
147
+ BRANCH_RE = /^#{BRANCH_SUB_RE}$/
148
+
149
+ # Tag or branch RE. The general syntax for a branch is '<custom>-v?<version>_<feature>'
150
+ #
151
+ # The RE defines the following captures:
152
+ # $1 - custom name, can be nil
153
+ # $2 - tag, nil or 'v'
154
+ # $3 - semantic version
155
+ # $4 - feature name, can be nil
156
+ #
91
157
  VERSION_SUB_RE = /
92
158
  (?:(#{CUSTOM_NAME_SUB_RE})-)?
159
+ (v)?
93
160
  (#{SEMVER_SUB_RE})
94
161
  (?:_(#{FEATURE_NAME_SUB_RE}))?
95
162
  /x
@@ -113,24 +180,11 @@ module Prick
113
180
  # $1 - custom name, can be nil
114
181
  # $2 - semantic version
115
182
  #
116
- RELEASE_SUB_RE = /
117
- (?:(#{CUSTOM_NAME_SUB_RE})-)?
118
- (#{MMP_SEMVER_SUB_RE})
119
- /x
120
- RELEASE_RE = /^#{RELEASE_SUB_RE}$/
121
-
122
- # Migration RE. The syntax is <version>_<version>
123
- #
124
- # The RE defines the following captures
125
- # $1 - from version
126
- # $2 - from custom name, can be nil
127
- # $3 - from semantic version
128
- # $4 - to version
129
- # $5 - to custom name, can be nil
130
- # $6 - to semantic version
131
- #
132
- MIGRATION_SUB_RE = /(#{RELEASE_SUB_RE})_(#{RELEASE_SUB_RE})/
133
- MIGRATION_RE = /^#{MIGRATION_SUB_RE}$/
183
+ # RELEASE_SUB_RE = /
184
+ # (?:(#{CUSTOM_NAME_SUB_RE})-)?
185
+ # (#{MMP_SEMVER_SUB_RE})
186
+ # /x
187
+ # RELEASE_RE = /^#{RELEASE_SUB_RE}$/
134
188
 
135
189
  # Matches a prerelease branch
136
190
  #
@@ -138,11 +192,11 @@ module Prick
138
192
  # $1 - custom name, can be nil
139
193
  # $2 - semantic version
140
194
  #
141
- PRERELEASE_SUB_RE = /
142
- (?:(#{CUSTOM_NAME_SUB_RE})-)?
143
- (#{MMP_SEMVER_SUB_RE}-#{PRE_SEMVER_SUB_RE})
144
- /x
145
- PRERELEASE_RE = /^#{PRERELEASE_SUB_RE}$/
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}$/
146
200
 
147
201
  # Matches a feature branch
148
202
  #
@@ -151,8 +205,21 @@ module Prick
151
205
  # $2 - semantic version
152
206
  # $3 - feature name
153
207
  #
154
- FEATURE_SUB_RE = /#{ABSTRACT_RELEASE_SUB_RE}_(#{FEATURE_NAME_SUB_RE})/
155
- FEATURE_RE = /^#{FEATURE_SUB_RE}$/
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}$/
156
223
 
157
224
  # Project release RE. The general syntax is '<project>-<custom>-<version>'
158
225
  #
@@ -195,5 +262,12 @@ module Prick
195
262
  ALL_DATABASES_RE = /^#{ALL_DATABASES_SUB_RE}$/
196
263
  def self.all_databases_sub_re(project_name) /(#{project_name})(?:-(#{ABSTRACT_RELEASE_SUB_RE}))?/ end
197
264
  def self.all_databases_re(project_name) /^#{all_databases_sub_re(project_name)}$/ end
265
+
266
+ # Matches temporary databases. Mostly useful when debugging because temporary databases
267
+ # should be deleted on exit
268
+ TMP_DATABASES_SUB_RE = /#{PROJECT_NAME_SUB_RE}-(?:base|next)/
269
+ TMP_DATABASES_RE = /^#{TMP_DATABASES_SUB_RE}$/
270
+ def self.tmp_databases_sub_re(project_name) /#{project_name}-(?:base|next)/ end
271
+ def self.tmp_databases_re(project_name) /^#{tmp_databases_sub_re(project_name)}$/ end
198
272
  end
199
273
 
@@ -1,26 +1,29 @@
1
1
 
2
- require "prick/ensure.rb"
3
-
4
2
  module Prick
5
3
  class Database
6
4
  attr_reader :name
7
5
  attr_reader :user
8
6
 
9
7
  def initialize(name, user)
8
+ name != "" or raise "Illegal database name"
10
9
  @name = name
11
10
  @user = user
12
11
  @version = nil
13
12
  end
14
13
 
15
- def version
16
- @version ||= begin
17
- version =
18
- if exist? && Rdbms.exist_table?(name, "prick", "versions")
19
- Rdbms.select(name, "select version from prick.versions")&.first&.first
20
- else
21
- nil
22
- end
23
- version && Version.new(version)
14
+ def version(cache: true)
15
+ if cache && @version
16
+ @version
17
+ else
18
+ @version = begin
19
+ version =
20
+ if exist? && Rdbms.exist_table?(name, "prick", "versions")
21
+ Rdbms.select(name, "select version from prick.versions")&.first&.first
22
+ else
23
+ nil
24
+ end
25
+ version && Version.new(version)
26
+ end
24
27
  end
25
28
  end
26
29
 
@@ -42,17 +45,22 @@ module Prick
42
45
  def recreate() drop if exist?; create; @version = nil end
43
46
  def drop() Rdbms.drop_database(name, fail: false); @version = nil end
44
47
 
45
- def loaded?() exist? && !version.nil? end
46
- def load(file) Rdbms.load(name, file, user: user); @version = nil end
48
+ def loaded?() !version.nil? end
49
+
50
+ def load(file)
51
+ !loaded? or raise Internal, "Database #{name} is already loaded"
52
+ Rdbms.load(name, file, user: user)
53
+ version # Provoke load of version
54
+ end
47
55
 
48
- def save(file) Rdbms.save(name, file); @version = nil end
56
+ def save(file)
57
+ loaded? or raise Internal, "Database #{name} is not loaded"
58
+ Rdbms.save(name, file)
59
+ @version = nil
60
+ end
49
61
 
50
- include Ensure
62
+ def clean() recreate end # TODO: Find solution that doesn't involve dropping the database
51
63
 
52
- private
53
- @states = {
54
- exist: [:create, :drop],
55
- loaded: [:exist, :load, :recreate]
56
- }
64
+ def to_s() name end
57
65
  end
58
66
  end
data/lib/prick/diff.rb ADDED
@@ -0,0 +1,125 @@
1
+ module Prick
2
+ class Diff
3
+ # Diff as a list of lines
4
+ attr_reader :diff
5
+
6
+ # Subject of the diff
7
+ attr_reader :before_table_changes
8
+ attr_reader :table_changes
9
+ attr_reader :after_table_changes
10
+
11
+ def initialize(db1, db2)
12
+ @db1, @db2 = db1, db2
13
+ do_diff
14
+ split_on_table_changes
15
+ end
16
+
17
+ def self.same?(db1, db2) Diff.new(db1, db2).same? end
18
+
19
+ # Return true if the two databases are equal. Named #same? to avoid name
20
+ # collision with the built in #equal?
21
+ def same?() diff.empty? end
22
+
23
+ # Write the diff between the databases to the given file(s). Return true if
24
+ # the databases are equal
25
+ def write_segments(file1, file2 = nil, file3 = nil, mark: true)
26
+ if file2.nil? && file3.nil?
27
+ file2 = file3 = file1
28
+ elsif file2.nil? ^ !file3.nil?
29
+ raise Internal, "Either none or both of `file2` and `file3` should be nil"
30
+ end
31
+ write_segment(file1, :before_table_changes, mark: mark)
32
+ write_segment(file2, :table_changes, mark: mark)
33
+ write_segment(file3, :after_table_changes, mark: mark)
34
+ end
35
+
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
+ private
49
+ def do_diff(file = nil)
50
+ command = "migra --unsafe --with-privileges postgres:///#{@db1} postgres:///#{@db2}"
51
+ @diff = Command.command(command, fail: false)
52
+ [0,2].include?(Command.status) or
53
+ raise Internal, "migrate command failed with status #{Command.status}: #{command}"
54
+ end
55
+
56
+ # Initialize table change variables
57
+ def split_on_table_changes
58
+ @before_table_changes = []
59
+ @table_changes = []
60
+ @after_table_changes = []
61
+
62
+ before = true
63
+ tables = false
64
+ after = false
65
+
66
+ in_table = false
67
+
68
+ diff.each { |l|
69
+ if before
70
+ if l =~ /^alter table/
71
+ @table_changes << l
72
+ before = false
73
+ tables = true
74
+ elsif l =~ /^create table/
75
+ @table_changes << l
76
+ before = false
77
+ tables = true
78
+ in_table = true
79
+ elsif l =~ /^drop table/
80
+ @table_changes << l
81
+ before = false
82
+ tables = true
83
+ else
84
+ @before_table_changes << l
85
+ end
86
+ elsif tables
87
+ if in_table
88
+ if l =~ /^\)/
89
+ @table_changes << l
90
+ in_table = false
91
+ else
92
+ @table_changes << l
93
+ end
94
+ elsif l =~ /^alter table/
95
+ @table_changes << l
96
+ elsif l =~ /^create table/
97
+ @table_changes << l
98
+ in_table = true
99
+ elsif l =~ /^drop table/
100
+ @table_changes << l
101
+ elsif l =~ /^\s*$/
102
+ @table_changes << l
103
+ else
104
+ @after_table_changes << l
105
+ tables = false
106
+ after = true
107
+ end
108
+ else
109
+ @after_table_changes << l
110
+ end
111
+ }
112
+ end
113
+ end
114
+ end
115
+
116
+
117
+
118
+
119
+
120
+
121
+
122
+
123
+
124
+
125
+