shaf 0.3.1 → 0.4.0

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