prick 0.4.0 → 0.5.0
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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/TODO +7 -0
- data/exe/prick +95 -33
- data/lib/ext/fileutils.rb +7 -0
- data/lib/prick.rb +5 -3
- data/lib/prick/builder.rb +31 -8
- data/lib/prick/cache.rb +34 -0
- data/lib/prick/constants.rb +106 -54
- data/lib/prick/database.rb +26 -18
- data/lib/prick/diff.rb +103 -25
- data/lib/prick/git.rb +31 -9
- data/lib/prick/head.rb +183 -0
- data/lib/prick/migration.rb +41 -181
- data/lib/prick/program.rb +199 -0
- data/lib/prick/project.rb +277 -0
- data/lib/prick/rdbms.rb +2 -1
- data/lib/prick/schema.rb +5 -10
- data/lib/prick/state.rb +129 -74
- data/lib/prick/version.rb +41 -28
- data/share/diff/diff.after-tables.sql +4 -0
- data/share/diff/diff.before-tables.sql +4 -0
- data/share/diff/diff.tables.sql +8 -0
- data/share/migration/diff.tables.sql +8 -0
- data/share/{release_migration → migration}/features.yml +0 -0
- data/share/migration/migrate.sql +3 -0
- data/share/{release_migration → migration}/migrate.yml +3 -0
- data/share/migration/tables.sql +3 -0
- data/share/{schemas → schema/schema}/build.yml +0 -0
- data/share/{schemas → schema/schema}/prick/build.yml +0 -0
- data/share/schema/schema/prick/data.sql +7 -0
- data/share/{schemas → schema/schema}/prick/schema.sql +0 -0
- data/share/{schemas → schema/schema}/prick/tables.sql +2 -2
- data/share/{schemas → schema/schema}/public/.keep +0 -0
- data/share/{schemas → schema/schema}/public/build.yml +0 -0
- data/share/{schemas → schema/schema}/public/schema.sql +0 -0
- data/test_refactor +34 -0
- metadata +22 -20
- data/file +0 -0
- data/lib/prick/build.rb +0 -376
- data/lib/prick/migra.rb +0 -22
- data/share/feature_migration/diff.sql +0 -2
- data/share/feature_migration/migrate.sql +0 -2
- data/share/release_migration/diff.sql +0 -3
- data/share/release_migration/migrate.sql +0 -5
- data/share/schemas/prick/data.sql +0 -7
    
        data/lib/prick/project.rb
    CHANGED
    
    | @@ -2,6 +2,283 @@ require "prick/state.rb" | |
| 2 2 |  | 
| 3 3 | 
             
            require "tmpdir"
         | 
| 4 4 |  | 
| 5 | 
            +
            module Prick
         | 
| 6 | 
            +
              class Project
         | 
| 7 | 
            +
                attr_reader :name
         | 
| 8 | 
            +
                attr_reader :user # Database user
         | 
| 9 | 
            +
                attr_reader :head # Current branch/tag
         | 
| 10 | 
            +
                attr_reader :schema
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                # Return the versioned database or the project database if `version` is nil
         | 
| 13 | 
            +
                def database(version = nil)
         | 
| 14 | 
            +
                  version ? Database.new("#{name}-#{version}", user) : @database
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                attr_reader :cache
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                def initialize(name, user, head)
         | 
| 20 | 
            +
                  @name = name
         | 
| 21 | 
            +
                  @user = user || ENV['USER']
         | 
| 22 | 
            +
                  @head = head
         | 
| 23 | 
            +
                  @schema = Schema.new
         | 
| 24 | 
            +
                  @database = Database.new(name, user)
         | 
| 25 | 
            +
                  @cache = Cache.new
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                forward_methods :version, :base_version, :@head
         | 
| 29 | 
            +
                forward_methods :clean?, :@head
         | 
| 30 | 
            +
                forward_methods :tag?, :release_tag?, :migration_tag?, :@head
         | 
| 31 | 
            +
                forward_methods :branch?, :release_branch?, :prerelease_branch?, :feature_branch?, :migration_branch?, :@head
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                def self.exist?(directory) 
         | 
| 34 | 
            +
                  File.directory?(directory) && Dir.chdir(directory) { ProjectState.new.exist? } 
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def self.create(name, user, directory)
         | 
| 38 | 
            +
                  user ||= ENV['USER']
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  FileUtils.mkdir(directory) if directory != "."
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                  Dir.chdir(directory) {
         | 
| 43 | 
            +
                    # Initialize git instance
         | 
| 44 | 
            +
                    Git.init
         | 
| 45 | 
            +
             | 
| 46 | 
            +
                    # Create prick version file
         | 
| 47 | 
            +
                    PrickVersion.new.write(VERSION)
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    # Create project state file
         | 
| 50 | 
            +
                    ProjectState.new(name: name, user: user).write
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                    # Directories
         | 
| 53 | 
            +
                    FileUtils.mkdir_p(DIRS)
         | 
| 54 | 
            +
                    DIRS.each { |dir| FileUtils.touch("#{dir}/.keep") }
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                    # Copy default gitignore and schema files
         | 
| 57 | 
            +
                    Share.cp("gitignore", ".gitignore")
         | 
| 58 | 
            +
                    Share.cp("schema/schema", ".")
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                    # Add .prick-migration file
         | 
| 61 | 
            +
                    MigrationState.new(version: Version.zero).create
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                    # Add everything so far
         | 
| 64 | 
            +
                    Git.add(".")
         | 
| 65 | 
            +
                    Git.commit("Initial import")
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                    # Rename branch "master"/"main" to "0.0.0_initial"
         | 
| 68 | 
            +
                    from_branch = Git.branch?("master") ? "master" : "main"
         | 
| 69 | 
            +
                    Git.rename_branch(from_branch, "0.0.0_initial")
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                    # Create schema
         | 
| 72 | 
            +
                    schema = Schema.new
         | 
| 73 | 
            +
                    schema.version = Version.zero
         | 
| 74 | 
            +
                    Git.add schema.version_file
         | 
| 75 | 
            +
             | 
| 76 | 
            +
                    # Create release
         | 
| 77 | 
            +
                    Git.commit "Created release v0.0.0"
         | 
| 78 | 
            +
                    Git.create_tag(Version.zero)
         | 
| 79 | 
            +
                    Git.checkout_tag(Version.zero)
         | 
| 80 | 
            +
                  }
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                # Initialize from disk
         | 
| 84 | 
            +
                #
         | 
| 85 | 
            +
                # TODO Handle migrations
         | 
| 86 | 
            +
                def self.load
         | 
| 87 | 
            +
                  if Git.detached?
         | 
| 88 | 
            +
                    name = "v#{Git.current_tag}"
         | 
| 89 | 
            +
                  else
         | 
| 90 | 
            +
                    name = Git.current_branch
         | 
| 91 | 
            +
                  end
         | 
| 92 | 
            +
                  begin
         | 
| 93 | 
            +
                    branch = Head.load(name)
         | 
| 94 | 
            +
                  rescue Version::FormatError
         | 
| 95 | 
            +
                    raise Fail, "Illegal branch name: #{name}"
         | 
| 96 | 
            +
                  end
         | 
| 97 | 
            +
                  state = ProjectState.new.read
         | 
| 98 | 
            +
                  Project.new(state.name, state.user, branch)
         | 
| 99 | 
            +
                end
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            #   def checkout(branch_or_tag) end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                def build(database = self.database, version: nil)
         | 
| 104 | 
            +
                  database.clean
         | 
| 105 | 
            +
                  if version
         | 
| 106 | 
            +
                    FileUtils.mkdir_p(TMP_DIR)
         | 
| 107 | 
            +
                    Dir.mktmpdir("clone-", TMP_DIR) { |dir|
         | 
| 108 | 
            +
                      Command.command "git clone . #{dir}"
         | 
| 109 | 
            +
                      Dir.chdir(dir) {
         | 
| 110 | 
            +
                        Git.checkout_tag(version)
         | 
| 111 | 
            +
                        project = Project.load
         | 
| 112 | 
            +
                        project.head.build(database)
         | 
| 113 | 
            +
                      }
         | 
| 114 | 
            +
                    }
         | 
| 115 | 
            +
                  else
         | 
| 116 | 
            +
                    head.build(database)
         | 
| 117 | 
            +
                  end
         | 
| 118 | 
            +
                  self
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 121 | 
            +
            #   def make(database = self.database, version: nil)
         | 
| 122 | 
            +
            #     database.clean
         | 
| 123 | 
            +
            #     if cache.exist?(version)
         | 
| 124 | 
            +
            #       load(database, version: version)
         | 
| 125 | 
            +
            #     else
         | 
| 126 | 
            +
            #       build(database, version: version)
         | 
| 127 | 
            +
            #       cache.save(database, version) if version
         | 
| 128 | 
            +
            #     end
         | 
| 129 | 
            +
            #     self
         | 
| 130 | 
            +
            #   end
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                def load(database = self.database, version: nil, file: nil)
         | 
| 133 | 
            +
                  version.nil? ^ file.nil? or raise Internal, "Need exactly one of :file and :version to be defined"
         | 
| 134 | 
            +
                  database.clean
         | 
| 135 | 
            +
                  if version
         | 
| 136 | 
            +
                    cache.exist?(version) or raise Internal, "Can't find cache file for database #{database}"
         | 
| 137 | 
            +
                    cache.load(database, version)
         | 
| 138 | 
            +
                  else
         | 
| 139 | 
            +
                    database.load(file)
         | 
| 140 | 
            +
                  end
         | 
| 141 | 
            +
                  self
         | 
| 142 | 
            +
                end
         | 
| 143 | 
            +
             | 
| 144 | 
            +
                def save(database = nil, file: nil)
         | 
| 145 | 
            +
                  !database.nil? || file or raise Internal, "Need a database when saving to file"
         | 
| 146 | 
            +
                  database ||= self.database
         | 
| 147 | 
            +
                  if file
         | 
| 148 | 
            +
                    database.save(file)
         | 
| 149 | 
            +
                  else
         | 
| 150 | 
            +
                    cache.save(database)
         | 
| 151 | 
            +
                  end
         | 
| 152 | 
            +
                  self
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                # Create a schema diff between two database versions. to_version defaults to the
         | 
| 156 | 
            +
                # current version. Returns the Diff object
         | 
| 157 | 
            +
                def diff(from_version, to_version)
         | 
| 158 | 
            +
                  begin
         | 
| 159 | 
            +
                    from_db = Database.new("#{name}-base", user)
         | 
| 160 | 
            +
                    to_db = Database.new("#{name}-next", user)
         | 
| 161 | 
            +
                    from_version ||= version
         | 
| 162 | 
            +
                    build(from_db, version: from_version)
         | 
| 163 | 
            +
                    build(to_db, version: to_version)
         | 
| 164 | 
            +
                    Diff.new(from_db, to_db)
         | 
| 165 | 
            +
                  ensure
         | 
| 166 | 
            +
                    from_db&.drop
         | 
| 167 | 
            +
                    to_db&.drop
         | 
| 168 | 
            +
                  end
         | 
| 169 | 
            +
                end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                def prepare_release(fork = nil)
         | 
| 172 | 
            +
                  check_clean(:release_tag)
         | 
| 173 | 
            +
                  @head = ReleaseBranch.new(fork, version).create
         | 
| 174 | 
            +
                  submit "Prepared new release based on v#{version}", true
         | 
| 175 | 
            +
                  self
         | 
| 176 | 
            +
                end
         | 
| 177 | 
            +
             | 
| 178 | 
            +
                def prepare_diff(from_version = version)
         | 
| 179 | 
            +
                  begin
         | 
| 180 | 
            +
                    from_name = "#{name}-base"
         | 
| 181 | 
            +
                    from_db = Database.new(from_name, user)
         | 
| 182 | 
            +
                    build(from_db, version: from_version)
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                    to_name = "#{name}-next"
         | 
| 185 | 
            +
                    to_db = Database.new(to_name, user)
         | 
| 186 | 
            +
                    build(to_db)
         | 
| 187 | 
            +
             | 
| 188 | 
            +
                    if prerelease_branch?
         | 
| 189 | 
            +
                      head.migrate_features(from_db)
         | 
| 190 | 
            +
                    end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                    diff = Diff.new(from_db, to_db)
         | 
| 193 | 
            +
                    for path, lines, tables in [
         | 
| 194 | 
            +
                        [BEFORE_TABLES_DIFF_PATH, diff.before_table_changes, false], 
         | 
| 195 | 
            +
                        [TABLES_DIFF_PATH, diff.table_changes, true], 
         | 
| 196 | 
            +
                        [AFTER_TABLES_DIFF_PATH, diff.after_table_changes, false]]
         | 
| 197 | 
            +
                      if lines.empty?
         | 
| 198 | 
            +
                        if File.exist?(path)
         | 
| 199 | 
            +
                          if tables
         | 
| 200 | 
            +
                            Share.cp(File.join("diff", File.basename(path)), path)
         | 
| 201 | 
            +
                            Git.add(path)
         | 
| 202 | 
            +
                          else
         | 
| 203 | 
            +
                            Git.rm(path)
         | 
| 204 | 
            +
                          end
         | 
| 205 | 
            +
                        end
         | 
| 206 | 
            +
                      else
         | 
| 207 | 
            +
                        Share.cp(File.join("diff", File.basename(path)), path)
         | 
| 208 | 
            +
                        File.open(path, "a") { |f| f.puts lines }
         | 
| 209 | 
            +
                        Git.add(path) if !tables
         | 
| 210 | 
            +
                      end
         | 
| 211 | 
            +
                    end
         | 
| 212 | 
            +
                    self
         | 
| 213 | 
            +
                  ensure
         | 
| 214 | 
            +
                    from_db&.drop
         | 
| 215 | 
            +
                    to_db&.drop
         | 
| 216 | 
            +
                  end
         | 
| 217 | 
            +
                end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                def create_release(new_version) 
         | 
| 220 | 
            +
                  check_clean(:release_branch)
         | 
| 221 | 
            +
                  head = ReleaseTag.new(new_version, version)
         | 
| 222 | 
            +
                  check_migration(head.migration)
         | 
| 223 | 
            +
                  (@head = head).create
         | 
| 224 | 
            +
                  self
         | 
| 225 | 
            +
                end
         | 
| 226 | 
            +
             | 
| 227 | 
            +
                def generate_schema
         | 
| 228 | 
            +
                  build = SchemaBuilder.new(database, SCHEMA_DIR).build(execute: false)
         | 
| 229 | 
            +
                  puts build.lines
         | 
| 230 | 
            +
                end
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                def generate_migration
         | 
| 233 | 
            +
                  build = MigrationBuilder.new(database, MIGRATION_DIR).build(execute: false)
         | 
| 234 | 
            +
                  puts build.lines
         | 
| 235 | 
            +
                end
         | 
| 236 | 
            +
             | 
| 237 | 
            +
              private
         | 
| 238 | 
            +
                def check(kind) self.send(:"#{kind}?") end
         | 
| 239 | 
            +
                def check_clean(kind = nil)
         | 
| 240 | 
            +
                  clean? or raise Internal, "Dirty repository" 
         | 
| 241 | 
            +
                  kind.nil? || check(kind) or raise Internal, "Not on a #{kind} tag/branh"
         | 
| 242 | 
            +
                end
         | 
| 243 | 
            +
             | 
| 244 | 
            +
                def check_branch() branch? or raise Internal, "Not on a branch" end
         | 
| 245 | 
            +
                def check_tag() tag? or raise Internal, "Not on a tag" end
         | 
| 246 | 
            +
             | 
| 247 | 
            +
                # FIXME: Use Cache::file
         | 
| 248 | 
            +
                def cache_file(version) File.join(CACHE_DIR, "#{database(version)}.sql.gz") end
         | 
| 249 | 
            +
             | 
| 250 | 
            +
                def submit(msg, commit = true)
         | 
| 251 | 
            +
                  Git.commit msg if commit
         | 
| 252 | 
            +
                  @message = msg
         | 
| 253 | 
            +
                end
         | 
| 254 | 
            +
             | 
| 255 | 
            +
                def clean(database) # FIXME: Use Database#clean
         | 
| 256 | 
            +
                  if database.exist?
         | 
| 257 | 
            +
                    database.recreate if database.loaded?
         | 
| 258 | 
            +
                  else
         | 
| 259 | 
            +
                    database.create
         | 
| 260 | 
            +
                  end
         | 
| 261 | 
            +
                end
         | 
| 262 | 
            +
             | 
| 263 | 
            +
                def check_migration(migration)
         | 
| 264 | 
            +
                  begin
         | 
| 265 | 
            +
                    from_version = migration.base_version
         | 
| 266 | 
            +
                    from_db = Database.new("#{name}-base", user)
         | 
| 267 | 
            +
                    to_db = Database.new("#{name}-next", user)
         | 
| 268 | 
            +
                    build(from_db, version: from_version)
         | 
| 269 | 
            +
                    build(to_db)
         | 
| 270 | 
            +
                    migration.migrate(from_db)
         | 
| 271 | 
            +
                    Diff.new(from_db, to_db).same? or raise Error, "Schema/migration mismatch"
         | 
| 272 | 
            +
                  ensure
         | 
| 273 | 
            +
                    from_db&.drop
         | 
| 274 | 
            +
                    to_db&.drop
         | 
| 275 | 
            +
                  end
         | 
| 276 | 
            +
                end
         | 
| 277 | 
            +
              end
         | 
| 278 | 
            +
            end
         | 
| 279 | 
            +
             | 
| 280 | 
            +
            __END__
         | 
| 281 | 
            +
             | 
| 5 282 | 
             
            module Prick
         | 
| 6 283 | 
             
              class Project
         | 
| 7 284 | 
             
                # Name of project. Persisted in the project state file
         | 
    
        data/lib/prick/rdbms.rb
    CHANGED
    
    | @@ -9,7 +9,8 @@ module Prick | |
| 9 9 |  | 
| 10 10 | 
             
                ### EXECUTE SQL
         | 
| 11 11 |  | 
| 12 | 
            -
                # Execute the SQL statement and return stdout as an array of tuples
         | 
| 12 | 
            +
                # Execute the SQL statement and return stdout as an array of tuples. FIXME:
         | 
| 13 | 
            +
                # SQL can't contain '"'
         | 
| 13 14 | 
             
                def self.exec_sql(db, sql, user: ENV['USER'])
         | 
| 14 15 | 
             
                  stdout = Command.command %(
         | 
| 15 16 | 
             
                    {
         | 
    
        data/lib/prick/schema.rb
    CHANGED
    
    | @@ -9,22 +9,17 @@ module Prick | |
| 9 9 |  | 
| 10 10 | 
             
              # Note this models the SCHEMAS_DIR directory, not a database schema
         | 
| 11 11 | 
             
              class Schema
         | 
| 12 | 
            -
                 | 
| 13 | 
            -
                def yml_file() SchemaBuilder.yml_file(directory) end
         | 
| 12 | 
            +
                def yml_file() SchemaBuilder.yml_file(SCHEMA_DIR) end
         | 
| 14 13 |  | 
| 15 | 
            -
                def version() SchemaVersion. | 
| 16 | 
            -
                def version=(version) SchemaVersion.new( | 
| 17 | 
            -
                def version_file() SchemaVersion.new | 
| 18 | 
            -
             | 
| 19 | 
            -
                def initialize(directory = SCHEMAS_DIR) 
         | 
| 20 | 
            -
                  @directory = directory
         | 
| 21 | 
            -
                end
         | 
| 14 | 
            +
                def version() SchemaVersion.load end
         | 
| 15 | 
            +
                def version=(version) SchemaVersion.new(version).write end
         | 
| 16 | 
            +
                def version_file() SchemaVersion.new.path end
         | 
| 22 17 |  | 
| 23 18 | 
             
                def built?(database) database.exist? && database.version == version end
         | 
| 24 19 |  | 
| 25 20 | 
             
                # `subject` can be a subpath of schema/ (ie. 'public/tables')
         | 
| 26 21 | 
             
                def build(database, subject = nil)
         | 
| 27 | 
            -
                  SchemaBuilder.new(database,  | 
| 22 | 
            +
                  SchemaBuilder.new(database, SCHEMA_DIR).build(subject)
         | 
| 28 23 | 
             
                end
         | 
| 29 24 | 
             
              end
         | 
| 30 25 | 
             
            end
         | 
    
        data/lib/prick/state.rb
    CHANGED
    
    | @@ -2,25 +2,119 @@ | |
| 2 2 | 
             
            require 'yaml'
         | 
| 3 3 |  | 
| 4 4 | 
             
            module Prick
         | 
| 5 | 
            +
              # General interface for prick state files. Comments are automatically removed
         | 
| 5 6 | 
             
              class PrickFile
         | 
| 6 7 | 
             
                attr_reader :path
         | 
| 7 8 | 
             
                def initialize(path) @path = path end
         | 
| 8 9 | 
             
                def exist?() File.exist?(path) end
         | 
| 10 | 
            +
                def self.exist?() self.new.exist? end # Require an #initializer with no arguments
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def self.load(*args) self.new(*args).read end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def self.read() raise NotThis end
         | 
| 9 15 |  | 
| 10 16 | 
             
              protected
         | 
| 17 | 
            +
                # Read file from disk or from the given branch or tag
         | 
| 11 18 | 
             
                def general_read(method, branch: nil, tag: nil)
         | 
| 19 | 
            +
                  !(branch && tag) or raise Internal, "Not both of `branch` and `tag` can be defined"
         | 
| 12 20 | 
             
                  branch || tag ? Git.send(method, path, branch: branch, tag: tag) : File.open(path, "r").send(method)
         | 
| 13 21 | 
             
                end
         | 
| 22 | 
            +
             | 
| 14 23 | 
             
                def do_read(**opts) general_read(:read, **opts) end
         | 
| 15 | 
            -
                def  | 
| 24 | 
            +
                def do_readline(**opts) do_readlines(**opts).first end
         | 
| 25 | 
            +
                def do_readlines(**opts) general_read(:readlines, **opts).reject { |l| l =~ /^\s*#/ } end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
              # Models the .prick-version file. It contains just one line with the version
         | 
| 29 | 
            +
              # of prick itself
         | 
| 30 | 
            +
              class PrickVersion < PrickFile
         | 
| 31 | 
            +
                def initialize() super PRICK_VERSION_FILE end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                # Return the version
         | 
| 34 | 
            +
                def read(branch: nil, tag: nil)
         | 
| 35 | 
            +
                  Version.new(do_readline(branch: branch, tag: tag).chomp.sub(/^prick-/, ""))
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                # Write prick version
         | 
| 39 | 
            +
                def write(version)
         | 
| 40 | 
            +
                  File.open(path, "w") { |file| file.puts "prick-#{version}" }
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
              end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
              # Models the schema version that is stored in the data.sql file in the prick
         | 
| 45 | 
            +
              # schema directory. Note that SchemaVersion caches the version. Use #clear to
         | 
| 46 | 
            +
              # reset the cache
         | 
| 47 | 
            +
              class SchemaVersion < PrickFile
         | 
| 48 | 
            +
                def initialize(version = nil)
         | 
| 49 | 
            +
                  @version = version
         | 
| 50 | 
            +
                  super SCHEMA_VERSION_PATH
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                def clear() @version = nil end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def create() raise Internal, "This should not happen" end
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                def read(**opts)
         | 
| 58 | 
            +
                  return @version if @version
         | 
| 59 | 
            +
                  lines = do_readlines
         | 
| 60 | 
            +
                  while !lines.empty?
         | 
| 61 | 
            +
                    line = lines.shift.chomp
         | 
| 62 | 
            +
                    next if line != COPY_STMT
         | 
| 63 | 
            +
                    data = lines.shift.chomp
         | 
| 64 | 
            +
                    a = data.split("\t").map { |val| parse_sql_literal(val) }
         | 
| 65 | 
            +
                    a.size == FIELDS.size + 1 or raise Fail, "Illegal data format in #{path}"
         | 
| 66 | 
            +
                    fork, major, minor, patch, pre, feature, version = *a[1..-1]
         | 
| 67 | 
            +
                    @version = Version.new("0.0.0", fork: fork, feature: feature)
         | 
| 68 | 
            +
                    @version.major = major
         | 
| 69 | 
            +
                    @version.minor = minor
         | 
| 70 | 
            +
                    @version.patch = patch
         | 
| 71 | 
            +
                    @version.pre = pre
         | 
| 72 | 
            +
                    return @version
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
                  raise Fail, "No COPY statement in #{path}"
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                def write(version = @version)
         | 
| 78 | 
            +
            #     puts "Writing #{path}"
         | 
| 79 | 
            +
                  version_string = version.truncate(:pre).to_s
         | 
| 80 | 
            +
                  File.open(path, "w") { |f|
         | 
| 81 | 
            +
                    f.puts "--"
         | 
| 82 | 
            +
                    f.puts "-- This file is auto-generated by prick(1). Please don't touch"
         | 
| 83 | 
            +
                    f.puts COPY_STMT
         | 
| 84 | 
            +
                    f.print \
         | 
| 85 | 
            +
                        "1\t",
         | 
| 86 | 
            +
                        FIELDS[..-2].map { |f| version.send(f.to_sym) || '\N' }.join("\t"),
         | 
| 87 | 
            +
                        "\t#{version_string}\n"
         | 
| 88 | 
            +
                    f.puts "\\."
         | 
| 89 | 
            +
                  }
         | 
| 90 | 
            +
                  Git.add(path)
         | 
| 91 | 
            +
                  version
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
              private
         | 
| 95 | 
            +
                FIELDS = %w(fork major minor patch pre feature version)
         | 
| 96 | 
            +
                COPY_STMT = "COPY prick.versions (id, #{FIELDS.join(', ')}) FROM stdin;"
         | 
| 97 | 
            +
             | 
| 98 | 
            +
                def parse_sql_literal(s)
         | 
| 99 | 
            +
                  case s
         | 
| 100 | 
            +
                    when '\N', 'null'; nil
         | 
| 101 | 
            +
                    when/^\d+$/; s.to_i
         | 
| 102 | 
            +
                  else
         | 
| 103 | 
            +
                    s
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                end
         | 
| 16 106 | 
             
              end
         | 
| 17 107 |  | 
| 108 | 
            +
              # General interface for prick state files in YAML format
         | 
| 18 109 | 
             
              class State < PrickFile
         | 
| 19 | 
            -
                # `fields` is a Hash from field name (Symbol) to field type (class) | 
| 110 | 
            +
                # `fields` is a Hash from field name (Symbol) to field type (class) where a
         | 
| 111 | 
            +
                # class can be a class known to YAML (Integer, String, etc.) or Version.
         | 
| 112 | 
            +
                # The #initialize method generates an accessor methods for each field
         | 
| 20 113 | 
             
                def initialize(path, **fields)
         | 
| 21 114 | 
             
                  super(path)
         | 
| 22 115 | 
             
                  @fields = fields
         | 
| 23 116 | 
             
                  @fields.each_key { |k| self.class.attr_accessor k }
         | 
| 117 | 
            +
                  @loaded = false
         | 
| 24 118 | 
             
                end
         | 
| 25 119 |  | 
| 26 120 | 
             
                def create() write end
         | 
| @@ -29,23 +123,23 @@ module Prick | |
| 29 123 | 
             
                  for field, value in fields
         | 
| 30 124 | 
             
                    self.send(:"#{field}=", value)
         | 
| 31 125 | 
             
                  end
         | 
| 126 | 
            +
                  self
         | 
| 32 127 | 
             
                end
         | 
| 33 128 |  | 
| 34 129 | 
             
                def read(branch: nil, tag: nil)
         | 
| 35 | 
            -
                  if  | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
                    end
         | 
| 42 | 
            -
                  else
         | 
| 43 | 
            -
                    raise NotYet
         | 
| 130 | 
            +
                  return self if @loaded
         | 
| 131 | 
            +
                  hash = YAML.load(do_read(branch: branch, tag: tag))
         | 
| 132 | 
            +
                  for field, klass in @fields
         | 
| 133 | 
            +
                    value = hash[field.to_s]
         | 
| 134 | 
            +
                    value = Version.new(value) if klass == Version && !value.nil?
         | 
| 135 | 
            +
                    self.instance_variable_set("@#{field}", value)
         | 
| 44 136 | 
             
                  end
         | 
| 137 | 
            +
                  @loaded = true
         | 
| 45 138 | 
             
                  self
         | 
| 46 139 | 
             
                end
         | 
| 47 140 |  | 
| 48 | 
            -
                def write
         | 
| 141 | 
            +
                def write(**fields)
         | 
| 142 | 
            +
                  set(**fields)
         | 
| 49 143 | 
             
                  hash = @fields.map { |field, klass|
         | 
| 50 144 | 
             
                    value = self.send(field)
         | 
| 51 145 | 
             
                    value = value.to_s if klass == Version && !value.nil?
         | 
| @@ -58,80 +152,41 @@ module Prick | |
| 58 152 | 
             
              end
         | 
| 59 153 |  | 
| 60 154 | 
             
              class MigrationState < State
         | 
| 61 | 
            -
                def initialize( | 
| 62 | 
            -
                  super( | 
| 155 | 
            +
                def initialize(version: nil, base_version: nil)
         | 
| 156 | 
            +
                  super(PRICK_MIGRATION_PATH, version: Version, base_version: Version)
         | 
| 63 157 | 
             
                  set(version: version, base_version: base_version)
         | 
| 64 158 | 
             
                end
         | 
| 65 159 | 
             
              end
         | 
| 66 160 |  | 
| 67 | 
            -
              class  | 
| 68 | 
            -
                def initialize( | 
| 69 | 
            -
                  super( | 
| 70 | 
            -
                  set( | 
| 161 | 
            +
              class HeadState < State
         | 
| 162 | 
            +
                def initialize(name: nil)
         | 
| 163 | 
            +
                  super(PRICK_HEAD_PATH, name: String)
         | 
| 164 | 
            +
                  set(name: name)
         | 
| 71 165 | 
             
                end
         | 
| 72 166 | 
             
              end
         | 
| 73 167 |  | 
| 74 | 
            -
              class  | 
| 75 | 
            -
                def initialize( | 
| 76 | 
            -
                  super( | 
| 77 | 
            -
                  set( | 
| 168 | 
            +
              class FeatureState < State
         | 
| 169 | 
            +
                def initialize(feature: nil)
         | 
| 170 | 
            +
                  super(PRICK_FEATURE_PATH, feature: String)
         | 
| 171 | 
            +
                  set(feature: feature)
         | 
| 78 172 | 
             
                end
         | 
| 79 173 | 
             
              end
         | 
| 80 174 |  | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 175 | 
            +
            # class FeaturesState < State
         | 
| 176 | 
            +
            #   def initialize(directory, features: [])
         | 
| 177 | 
            +
            #     super(File.join(directory, FEATURES_STATE_FILE), features: Array)
         | 
| 178 | 
            +
            #     set(features: features)
         | 
| 179 | 
            +
            #   end
         | 
| 180 | 
            +
            # end
         | 
| 181 | 
            +
            #
         | 
| 87 182 |  | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 183 | 
            +
              # Models the .prick-project file that contains the name of the project and
         | 
| 184 | 
            +
              # the database user
         | 
| 185 | 
            +
              class ProjectState < State
         | 
| 186 | 
            +
                def initialize(name: nil, user: nil)
         | 
| 187 | 
            +
                  super(PROJECT_STATE_FILE, name: String, user: String)
         | 
| 188 | 
            +
                  set(name: name, user: user)
         | 
| 90 189 | 
             
                end
         | 
| 91 190 | 
             
              end
         | 
| 92 191 |  | 
| 93 | 
            -
              class SchemaVersion < PrickFile
         | 
| 94 | 
            -
                def initialize(schema = SCHEMAS_DIR) super(File.join(schema, "prick", "data.sql")) end
         | 
| 95 | 
            -
             | 
| 96 | 
            -
                def read(**opts)
         | 
| 97 | 
            -
                  lines = do_readlines
         | 
| 98 | 
            -
                  while !lines.empty?
         | 
| 99 | 
            -
                    line = lines.shift.chomp
         | 
| 100 | 
            -
                    next if line != COPY_STMT
         | 
| 101 | 
            -
                    l = lines.shift.chomp
         | 
| 102 | 
            -
                    a = l.split("\t")[1..].map { |val| val == '\N' ? nil : val }
         | 
| 103 | 
            -
                    a.size == FIELDS.size or raise Fail, "Illegal data format in #{path}"
         | 
| 104 | 
            -
                    custom, major, minor, patch, pre = a[0], *a[1..-2].map { |val| val && val.to_i }
         | 
| 105 | 
            -
                    v = Version.new("0.0.0", custom: (custom == '\N' ? nil : custom))
         | 
| 106 | 
            -
                    v.major = major
         | 
| 107 | 
            -
                    v.minor = minor
         | 
| 108 | 
            -
                    v.patch = patch
         | 
| 109 | 
            -
                    v.pre = (pre == "null" ? nil : pre)
         | 
| 110 | 
            -
                    return v
         | 
| 111 | 
            -
                  end
         | 
| 112 | 
            -
                  raise Fail, "No COPY statement in #{path}"
         | 
| 113 | 
            -
                end
         | 
| 114 | 
            -
             | 
| 115 | 
            -
                def write(version)
         | 
| 116 | 
            -
                  version_string = version.truncate(:pre).to_s
         | 
| 117 | 
            -
                  File.open(path, "w") { |f|
         | 
| 118 | 
            -
                    f.puts "--"
         | 
| 119 | 
            -
                    f.puts "-- This file is auto-generated by prick(1). Please don't touch"
         | 
| 120 | 
            -
                    f.puts COPY_STMT
         | 
| 121 | 
            -
                    f.print \
         | 
| 122 | 
            -
                        "1\t",
         | 
| 123 | 
            -
                        FIELDS[..-2].map { |f| version.send(f.to_sym) || '\N' }.join("\t"),
         | 
| 124 | 
            -
                        "\t#{version_string}\n"
         | 
| 125 | 
            -
                    f.puts "\\."
         | 
| 126 | 
            -
                  }
         | 
| 127 | 
            -
                  Git.add(path)
         | 
| 128 | 
            -
                end
         | 
| 129 | 
            -
             | 
| 130 | 
            -
                def create() raise Internal, "This should not happen" end
         | 
| 131 | 
            -
             | 
| 132 | 
            -
              private
         | 
| 133 | 
            -
                FIELDS = %w(custom major minor patch pre feature version)
         | 
| 134 | 
            -
                COPY_STMT = "COPY prick.versions (id, #{FIELDS.join(', ')}) FROM stdin;"
         | 
| 135 | 
            -
              end
         | 
| 136 192 | 
             
            end
         | 
| 137 | 
            -
             |