shaf 0.3.1 → 0.4.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/bin/shaf +3 -0
  5. data/lib/shaf.rb +1 -0
  6. data/lib/shaf/api_doc/document.rb +0 -1
  7. data/lib/shaf/app.rb +1 -1
  8. data/lib/shaf/command.rb +19 -2
  9. data/lib/shaf/command/generate.rb +11 -1
  10. data/lib/shaf/command/new.rb +1 -6
  11. data/lib/shaf/command/server.rb +7 -0
  12. data/lib/shaf/command/upgrade.rb +38 -0
  13. data/lib/shaf/extensions/authorize.rb +8 -6
  14. data/lib/shaf/extensions/resource_uris.rb +129 -71
  15. data/lib/shaf/generator.rb +4 -0
  16. data/lib/shaf/generator/controller.rb +2 -2
  17. data/lib/shaf/generator/migration.rb +33 -14
  18. data/lib/shaf/generator/migration/add_column.rb +1 -3
  19. data/lib/shaf/generator/migration/add_index.rb +42 -0
  20. data/lib/shaf/generator/model.rb +4 -4
  21. data/lib/shaf/generator/policy.rb +1 -1
  22. data/lib/shaf/generator/scaffold.rb +3 -3
  23. data/lib/shaf/generator/serializer.rb +5 -5
  24. data/lib/shaf/rake.rb +5 -0
  25. data/lib/shaf/rake/db.rb +79 -0
  26. data/lib/shaf/rake/test.rb +32 -0
  27. data/lib/shaf/registrable_factory.rb +8 -3
  28. data/lib/shaf/settings.rb +20 -4
  29. data/lib/shaf/tasks.rb +6 -3
  30. data/lib/shaf/{api_doc/task.rb → tasks/api_doc_task.rb} +6 -5
  31. data/lib/shaf/tasks/db_task.rb +42 -0
  32. data/lib/shaf/tasks/test_task.rb +16 -0
  33. data/lib/shaf/upgrade.rb +3 -0
  34. data/lib/shaf/upgrade/manifest.rb +31 -0
  35. data/lib/shaf/upgrade/package.rb +158 -0
  36. data/lib/shaf/upgrade/version.rb +52 -0
  37. data/lib/shaf/utils.rb +30 -0
  38. data/lib/shaf/version.rb +1 -1
  39. data/templates/Rakefile +2 -3
  40. data/templates/config/database.rb +3 -3
  41. metadata +33 -202
  42. metadata.gz.sig +0 -0
  43. data/lib/shaf/api_doc.rb +0 -3
  44. data/lib/shaf/tasks/db.rb +0 -81
  45. data/lib/shaf/tasks/test.rb +0 -48
@@ -1,8 +1,9 @@
1
1
  require 'shaf/api_doc/document'
2
+ require 'shaf/api_doc/comment'
2
3
 
3
4
  module Shaf
4
- module ApiDoc
5
- class Task
5
+ module Tasks
6
+ class ApiDocTask
6
7
  include Rake::DSL
7
8
 
8
9
  attr_accessor :document_class, :source_dir, :html_output_dir, :yaml_output_dir
@@ -10,7 +11,7 @@ module Shaf
10
11
  def initialize
11
12
  yield self if block_given?
12
13
  validate_attributes!
13
- @document_class ||= Document
14
+ @document_class ||= ApiDoc::Document
14
15
  define_tasks
15
16
  end
16
17
 
@@ -48,7 +49,7 @@ module Shaf
48
49
 
49
50
  def read_file(file)
50
51
  doc = document_class.new
51
- comment = Comment.new
52
+ comment = ApiDoc::Comment.new
52
53
 
53
54
  File.readlines(file).each do |line|
54
55
  next if empty_line?(line)
@@ -59,7 +60,7 @@ module Shaf
59
60
  end
60
61
 
61
62
  parse_line(line, doc, comment)
62
- comment = Comment.new
63
+ comment = ApiDoc::Comment.new
63
64
  end
64
65
 
65
66
  return doc unless block_given?
@@ -0,0 +1,42 @@
1
+ module Shaf
2
+ module Tasks
3
+ class DbTask
4
+ include Rake::DSL
5
+
6
+ def initialize(name, description:, args: [], &block)
7
+ @name = name
8
+ @desc = description
9
+ @args = args
10
+ @block = block
11
+ define_task
12
+ end
13
+
14
+ def define_task
15
+ namespace :db do
16
+ desc @desc
17
+ task @name, @args do |t, args|
18
+ require 'config/database'
19
+ Sequel.extension :migration
20
+ instance_exec(t, args, &@block)
21
+ end
22
+ end
23
+ end
24
+
25
+ def migrations
26
+ @migrations ||= DB[:schema_migrations].all
27
+ end
28
+
29
+ def last_migration
30
+ DB[:schema_migrations].order(:filename).last
31
+ end
32
+
33
+ def extract_version_and_filename(filename)
34
+ return [] unless filename
35
+ filename = filename[:filename] if filename.is_a? Hash
36
+ match = /(\d*)_(.*).rb/.match(filename)
37
+ return [] unless match
38
+ match[1..2]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ require 'rake/testtask'
2
+
3
+ module Shaf
4
+ module Tasks
5
+ class TestTask < Rake::TestTask
6
+ def initialize(*args)
7
+ super(*args) do |t|
8
+ t.libs = %w(. api spec)
9
+ t.verbose = true
10
+ t.warning = false
11
+ yield self if block_given?
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ require 'shaf/upgrade/version'
2
+ require 'shaf/upgrade/manifest'
3
+ require 'shaf/upgrade/package'
@@ -0,0 +1,31 @@
1
+ require 'yaml'
2
+
3
+ module Shaf
4
+ module Upgrade
5
+ class Manifest
6
+ attr_reader :target_version, :patches
7
+
8
+ def initialize(target_version:, patches: {})
9
+ @target_version = target_version
10
+ @patches = build_patterns(patches)
11
+ end
12
+
13
+ def build_patterns(patches)
14
+ patches.each_with_object({}) do |(chksum, pattern), hash|
15
+ hash[chksum] = /#{pattern}/
16
+ end
17
+ end
18
+
19
+ def patch_name_for(file)
20
+ @patches.select { |_, pattern| pattern =~ file }.keys.first
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ # Example of manifest:
27
+ # ---
28
+ # target_version: 0.4.0
29
+ # patches:
30
+ # cd5b0bf61070a9fd57e60c45e9aaf64a: config/database.rb
31
+ # 59783ecfa5f41b84c6fad734e7aa6a1d: Rakefile
@@ -0,0 +1,158 @@
1
+ require 'rubygems/package'
2
+ require 'zlib'
3
+ require 'set'
4
+ require 'digest'
5
+ require 'open3'
6
+
7
+ module Shaf
8
+ module Upgrade
9
+ class Package
10
+ include Comparable
11
+
12
+ class UpgradeError < StandardError; end
13
+ class VersionNotFoundError < UpgradeError; end
14
+ class VersionConflictError < UpgradeError; end
15
+ class ManifestNotFoundError < UpgradeError; end
16
+ class MissingPatchError < UpgradeError; end
17
+ class PatchNotInManifestError < UpgradeError; end
18
+ class BadChecksumError < UpgradeError; end
19
+
20
+ UPGRADE_FILES_PATH = File.join(Utils.gem_root, 'upgrades').freeze
21
+ MANIFEST_FILENAME = 'manifest'.freeze
22
+
23
+ attr_reader :version
24
+
25
+ class << self
26
+ def all
27
+ target_versions.map(&method(:new))
28
+ end
29
+
30
+ def load(version)
31
+ v = strip_suffix(version)
32
+ raise VersionNotFoundError unless target_versions.include? v
33
+ new(v).tap(&:load)
34
+ end
35
+
36
+ private
37
+
38
+ def target_versions
39
+ return @target_versions if @target_versions
40
+
41
+ files = Dir[File.join(UPGRADE_FILES_PATH, '*.tar.gz')]
42
+ @target_versions = files.each_with_object([]) do |file, versions|
43
+ str = File.basename(file, '.tar.gz')
44
+ versions << Version.new(str)
45
+ end
46
+ end
47
+
48
+ def strip_suffix(file)
49
+ file.sub('.tar.gz', '')
50
+ end
51
+ end
52
+
53
+ def initialize(version)
54
+ @version = Version.new(version)
55
+ @manifest = nil
56
+ @patches = {}
57
+ end
58
+
59
+ def load
60
+ File.open(tarball, 'rb') do |file|
61
+ Zlib::GzipReader.wrap(file) do |gz|
62
+ Gem::Package::TarReader.new(gz) do |tar|
63
+ tar.each(&method(:add_tar_entry))
64
+ end
65
+ end
66
+ end
67
+ validate
68
+ self
69
+ end
70
+
71
+ def <=>(other)
72
+ version = other.is_a?(String) ? other : other.version
73
+ @version <=> version
74
+ end
75
+
76
+ def apply(dir = nil)
77
+ files_in(dir).all? do |file|
78
+ name = @manifest.patch_name_for(file) # returns nil when file
79
+ next true unless name # shouldn't be patched
80
+ patch = @patches[name]
81
+ apply_patch(file, patch)
82
+ end
83
+ end
84
+
85
+ def to_s
86
+ if @manifest.nil?
87
+ "Shaf::Upgrade::Package for version #{@version}"
88
+ else
89
+ count = @patches.size
90
+ count_str = "#{count} patch#{count == 1 ? "" : "es"}"
91
+ "Shaf::Upgrade::Package for version #{@version}, containing #{count_str}"
92
+ end
93
+ end
94
+
95
+ private
96
+
97
+ def tarball
98
+ file = File.join(UPGRADE_FILES_PATH, "#{@version}.tar.gz")
99
+ return file if File.exist? file
100
+ raise VersionNotFoundError
101
+ end
102
+
103
+ def add_tar_entry(entry)
104
+ filename = entry.full_name
105
+ content = entry.read
106
+
107
+ if filename == MANIFEST_FILENAME
108
+ parse_manifest content
109
+ else
110
+ @patches[filename] = content
111
+ end
112
+ end
113
+
114
+ def parse_manifest(content)
115
+ h = YAML.safe_load(content)
116
+ @manifest = Manifest.new(
117
+ target_version: h['target_version'],
118
+ patches: h['patches']
119
+ )
120
+ end
121
+
122
+ def validate
123
+ raise ManifestNotFoundError unless @manifest
124
+ raise VersionConflictError unless @version == @manifest.target_version
125
+
126
+ from_manifest = @manifest.patches.keys.to_set
127
+ from_file = @patches.keys.to_set
128
+ raise MissingPatchError if from_file < from_manifest
129
+ raise PatchNotInManifestError if from_manifest < from_file
130
+
131
+ @patches.each do |md5, content|
132
+ raise BadChecksumError unless Digest::MD5.hexdigest(content) == md5
133
+ end
134
+
135
+ true
136
+ end
137
+
138
+ def files_in(dir)
139
+ dir += '/' if !dir.nil? && dir[-1] != '/'
140
+ Dir["#{dir}**/*"]
141
+ end
142
+
143
+ def apply_patch(file, patch)
144
+ success = nil
145
+ Open3.popen3('patch', file, '-r', '-') do |i, o, e, t|
146
+ i.write patch
147
+ i.close
148
+ puts o.read
149
+ err = e.read
150
+ puts err unless err.empty?
151
+ success = t.value.success?
152
+ end
153
+ success
154
+ end
155
+
156
+ end
157
+ end
158
+ end
@@ -0,0 +1,52 @@
1
+ module Shaf
2
+ module Upgrade
3
+ class Version
4
+ include Comparable
5
+
6
+ attr_reader :major, :minor, :patch
7
+
8
+ alias eql? ==
9
+
10
+ class UpgradeVersionError < StandardError
11
+ def initialize(message = "")
12
+ super("Bad upgrade version: #{message}")
13
+ end
14
+ end
15
+
16
+ def initialize(version)
17
+ if self.class === version
18
+ @major, @minor, @patch = [:major, :minor, :patch].map { |m| version.send m }
19
+ else
20
+ raise UpgradeVersionError.new(version) unless version =~ /\d+\.\d+\.\d+/
21
+ @major, @minor, @patch = split_version(version)
22
+ end
23
+ end
24
+
25
+ def <=>(other)
26
+ if other.is_a? String
27
+ compare_version(*split_version(other))
28
+ else
29
+ compare_version(other.major, other.minor, other.patch)
30
+ end
31
+ end
32
+
33
+ def to_s
34
+ [major, minor, patch].join('.')
35
+ end
36
+
37
+ private
38
+
39
+ def split_version(str)
40
+ str.split('.').map(&:to_i)
41
+ end
42
+
43
+ def compare_version(other_major, other_minor, other_patch)
44
+ [
45
+ major <=> other_major,
46
+ minor <=> other_minor,
47
+ patch <=> other_patch
48
+ ].find { |x| !x.zero? } || 0
49
+ end
50
+ end
51
+ end
52
+ end
data/lib/shaf/utils.rb CHANGED
@@ -2,6 +2,8 @@ module Shaf
2
2
  module Utils
3
3
  class ProjectRootNotFound < StandardError; end
4
4
 
5
+ SHAF_VERSION_FILE = '.shaf'.freeze
6
+
5
7
  # FIXME!!!
6
8
  def self.pluralize(noun)
7
9
  noun + 's' # FIXME!!
@@ -11,6 +13,10 @@ module Shaf
11
13
  name.capitalize.gsub(/[_-](\w)/) { $1.upcase }
12
14
  end
13
15
 
16
+ def self.gem_root
17
+ File.expand_path('../../..', __FILE__)
18
+ end
19
+
14
20
  # FIXME!!!
15
21
  def self.singularize(noun)
16
22
  return singularize(noun.to_s).to_sym if noun.is_a? Symbol
@@ -18,6 +24,10 @@ module Shaf
18
24
  noun[0..-2]
19
25
  end
20
26
 
27
+ def gem_root
28
+ self.class.gem_root
29
+ end
30
+
21
31
  def project_root
22
32
  dir = Dir.pwd
23
33
  20.times do
@@ -61,5 +71,25 @@ module Shaf
61
71
  def pluralize(noun)
62
72
  Utils::pluralize(noun)
63
73
  end
74
+
75
+ def read_shaf_file
76
+ return {} unless File.exist? SHAF_VERSION_FILE
77
+ str = File.read(SHAF_VERSION_FILE)
78
+ YAML.load(str) || {}
79
+ end
80
+
81
+ def write_shaf_file(data = {})
82
+ data = read_shaf_file.merge(data)
83
+ File.write SHAF_VERSION_FILE,
84
+ YAML.dump(data)
85
+ end
86
+
87
+ def read_shaf_version
88
+ read_shaf_file['version']
89
+ end
90
+
91
+ def write_shaf_version(version = nil)
92
+ write_shaf_file('version' => version || Shaf::VERSION)
93
+ end
64
94
  end
65
95
  end
data/lib/shaf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Shaf
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  end
data/templates/Rakefile CHANGED
@@ -2,11 +2,10 @@ require 'rubygems'
2
2
  require 'bundler'
3
3
  Bundler.setup(:default, :development)
4
4
  $:.unshift __dir__
5
- require 'shaf/tasks'
5
+ require 'shaf/rake'
6
6
  require 'shaf/settings'
7
- require 'shaf/api_doc'
8
7
 
9
- Shaf::ApiDoc::Task.new do |api_doc|
8
+ Shaf::ApiDocTask.new do |api_doc|
10
9
  api_doc.source_dir = File.join(%w(api serializers))
11
10
  api_doc.html_output_dir = File.join(Shaf::Settings.public_folder, "doc")
12
11
  api_doc.yaml_output_dir = Shaf::Settings.documents_dir || "doc/api"
@@ -3,7 +3,7 @@ require 'sequel'
3
3
  require 'config/constants'
4
4
  require 'fileutils'
5
5
 
6
- CONFIG = {
6
+ config = {
7
7
 
8
8
  production: {
9
9
  adapter: 'postgres',
@@ -28,7 +28,7 @@ CONFIG = {
28
28
 
29
29
  env = Sinatra::Application.settings.environment
30
30
 
31
- unless CONFIG[env]
31
+ unless config[env]
32
32
  STDERR.puts "No Database config for environment '#{env}'"
33
33
  exit 1
34
34
  end
@@ -36,5 +36,5 @@ end
36
36
  MIGRATIONS_DIR = File.join(APP_ROOT, Shaf::Settings.migrations_dir)
37
37
  FileUtils.mkdir_p(MIGRATIONS_DIR) unless Dir.exist?(MIGRATIONS_DIR)
38
38
 
39
- DB = Sequel.connect(CONFIG[env])
39
+ DB = Sequel.connect(config[env])
40
40