shaf 0.4.1 → 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.
Files changed (36) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/lib/shaf/command/base.rb +51 -0
  4. data/lib/shaf/command/templates/Gemfile.erb +0 -1
  5. data/lib/shaf/command.rb +1 -47
  6. data/lib/shaf/extensions/resource_uris.rb +113 -151
  7. data/lib/shaf/generator/base.rb +61 -0
  8. data/lib/shaf/generator/migration/base.rb +133 -0
  9. data/lib/shaf/generator/migration/create_table.rb +4 -4
  10. data/lib/shaf/generator/migration.rb +1 -111
  11. data/lib/shaf/generator/serializer.rb +3 -51
  12. data/lib/shaf/generator/templates/api/controller.rb.erb +2 -0
  13. data/lib/shaf/generator/templates/api/policy.rb.erb +3 -2
  14. data/lib/shaf/generator/templates/api/serializer.rb.erb +2 -6
  15. data/lib/shaf/generator/templates/spec/integration_spec.rb.erb +30 -30
  16. data/lib/shaf/generator.rb +1 -57
  17. data/lib/shaf/helpers/cache_control.rb +21 -0
  18. data/lib/shaf/helpers.rb +4 -2
  19. data/lib/shaf/rake/db.rb +1 -1
  20. data/lib/shaf/upgrade/manifest.rb +29 -8
  21. data/lib/shaf/upgrade/package.rb +51 -27
  22. data/lib/shaf/version.rb +1 -1
  23. data/templates/api/controllers/docs_controller.rb +2 -0
  24. data/templates/api/controllers/root_controller.rb +2 -3
  25. data/templates/api/policies/base_policy.rb +3 -0
  26. data/templates/api/serializers/base_serializer.rb +4 -0
  27. data/templates/api/serializers/error_serializer.rb +3 -2
  28. data/templates/api/serializers/form_serializer.rb +2 -2
  29. data/templates/api/serializers/root_serializer.rb +3 -3
  30. data/templates/config/initializers/db_migrations.rb +24 -11
  31. data/templates/config/initializers/hal_presenter.rb +0 -5
  32. data/templates/config/settings.yml +8 -3
  33. data/upgrades/0.5.0.tar.gz +0 -0
  34. data.tar.gz.sig +0 -0
  35. metadata +69 -6
  36. metadata.gz.sig +0 -0
@@ -1,5 +1,3 @@
1
- require 'date'
2
-
3
1
  module Shaf
4
2
  module Generator
5
3
  module Migration
@@ -20,119 +18,11 @@ module Shaf
20
18
  raise Command::ArgumentError, e.message
21
19
  end
22
20
  end
23
-
24
- class Base
25
- DB_COL_TYPES = {
26
- integer: ['Integer :%s', 'add_column :%s, Integer'],
27
- varchar: ['String %s', 'add_column :%s, String'],
28
- string: ['String :%s', 'add_column :%s, String'],
29
- text: ['String :%s, text: true', 'add_column :%s, String, text: true'],
30
- blob: ['File :%s', 'add_column :%s, File'],
31
- bigint: ['Bignum :%s', 'add_column :%s, Bignum'],
32
- double: ['Float :%s', 'add_column :%s, Float'],
33
- numeric: ['BigDecimal :%s', 'add_column :%s, BigDecimal'],
34
- date: ['Date :%s', 'add_column :%s, Date'],
35
- timestamp: ['DateTime :%s', 'add_column :%s, DateTime'],
36
- time: ['Time :%s', 'add_column :%s, Time'],
37
- bool: ['TrueClass :%s', 'add_column :%s, TrueClass'],
38
- boolean: ['TrueClass :%s', 'add_column :%s, TrueClass'],
39
- index: ['index :%s, unique: true', 'add_index :%s'],
40
- }
41
-
42
- REGEXP_DB_TYPES = {
43
- /\Aforeign_key\((\w+)\)/ => ['foreign_key :%s, :\1', 'add_foreign_key :%s, :\1'],
44
- }
45
-
46
- attr_reader :args
47
-
48
- class << self
49
- def inherited(child)
50
- Factory.register(child)
51
- end
52
-
53
- def identifier(*ids)
54
- @identifiers = ids.flatten.map(&:to_s)
55
- end
56
-
57
- def usage(str = nil, &block)
58
- @usage = str || block
59
- end
60
- end
61
-
62
- def initialize(*args)
63
- @args = args.dup
64
- end
65
-
66
- def call
67
- validate_args
68
- name = compile_migration_name
69
- compile_changes
70
- [target(name), render]
71
- rescue StandardError => e
72
- raise Command::ArgumentError, e.message
73
- end
74
-
75
- def add_change(change)
76
- @changes ||= []
77
- @changes << change if change
78
- end
79
-
80
- def db_type(type)
81
- type ||= :string
82
- result = DB_COL_TYPES[type.to_sym]
83
- result ||= REGEXP_DB_TYPES.each do |pattern, strings|
84
- m = pattern.match(type) or next
85
- break strings.map { |a| replace_backreferences(m, a) }
86
- end
87
- raise "Column type '#{type}' not supported" unless result
88
- result
89
- end
90
-
91
- def column_def(str, create: true)
92
- name, type = str.split(':')
93
- format db_type(type)[create ? 0 : 1], name.downcase
94
- end
95
-
96
- def target(name)
97
- raise "Migration filename is nil" unless name
98
- "db/migrations/#{timestamp}_#{name}.rb"
99
- end
100
-
101
- private
102
-
103
- def timestamp
104
- DateTime.now.strftime("%Y%m%d%H%M%S")
105
- end
106
-
107
- def replace_backreferences(match, str)
108
- groups = match.size
109
- (1...groups).inject(str) do |s, i|
110
- s.gsub("\\#{i}", match[i])
111
- end
112
- end
113
-
114
- def add_timestamp_columns?
115
- if File.exist? 'config/initializers/sequel.rb'
116
- require 'config/initializers/sequel'
117
- Sequel::Model.plugins.include? Sequel::Plugins::Timestamps
118
- end
119
- end
120
-
121
- def render
122
- <<~EOS
123
- Sequel.migration do
124
- change do
125
- #{@changes.flatten.join("\n ")}
126
- end
127
- end
128
- EOS
129
- end
130
- end
131
-
132
21
  end
133
22
  end
134
23
  end
135
24
 
25
+ require 'shaf/generator/migration/base'
136
26
  require 'shaf/generator/migration/add_column'
137
27
  require 'shaf/generator/migration/add_index'
138
28
  require 'shaf/generator/migration/create_table'
@@ -69,7 +69,7 @@ module Shaf
69
69
  end
70
70
 
71
71
  def links
72
- %w(doc:up self doc:create-form doc:edit-form doc:edit doc:delete)
72
+ %w(doc:up self doc:edit-form doc:delete)
73
73
  end
74
74
 
75
75
  def curies_with_doc
@@ -83,7 +83,7 @@ module Shaf
83
83
  # Example:
84
84
  # ```
85
85
  # curl -H "Accept: application/hal+json" \\
86
- # /doc/#{name}/rels/edit
86
+ # /doc/#{name}/rels/delete
87
87
  #```
88
88
  curie :doc do
89
89
  doc_curie_uri('#{name}')
@@ -96,9 +96,7 @@ module Shaf
96
96
  [
97
97
  collection_link,
98
98
  self_link,
99
- new_link,
100
99
  edit_link,
101
- update_link,
102
100
  delete_link,
103
101
  ]
104
102
  end
@@ -123,15 +121,6 @@ module Shaf
123
121
  )
124
122
  end
125
123
 
126
- def new_link
127
- link(
128
- rel: "doc:create-form",
129
- desc: "Link to a form to create a new #{name}",
130
- uri: "/#{plural_name}/form",
131
- uri_helper: "new_#{name}_uri"
132
- )
133
- end
134
-
135
124
  def edit_link
136
125
  link(
137
126
  rel: "doc:edit-form",
@@ -141,16 +130,6 @@ module Shaf
141
130
  )
142
131
  end
143
132
 
144
- def update_link
145
- link(
146
- rel: "doc:edit",
147
- desc: "Link to update this #{name}",
148
- method: "PUT",
149
- uri: "/#{plural_name}/5",
150
- uri_helper: "#{name}_uri(resource)"
151
- )
152
- end
153
-
154
133
  def delete_link
155
134
  link(
156
135
  rel: "doc:delete",
@@ -194,38 +173,13 @@ module Shaf
194
173
  EOS
195
174
  end
196
175
 
197
- def embeds
198
- [:'doc:edit-form']
199
- end
200
-
201
- def embeds_with_doc
202
- [
203
- <<~EOS.split("\n")
204
- # Auto generated doc:
205
- # A form to edit this #{name}
206
- embed :'doc:edit-form' do
207
- resource.edit_form.tap do |form|
208
- form.self_link = edit_#{name}_uri(resource)
209
- form.href = #{name}_uri(resource)
210
- end
211
- end
212
- EOS
213
- ]
214
- end
215
-
216
176
  def collection_with_doc
217
177
  <<~EOS.split("\n")
218
178
  collection of: '#{plural_name}' do
219
179
  link :self, #{plural_name}_uri
220
180
  link :up, root_uri
181
+ link :'doc:create-form', new_#{name}_uri
221
182
  curie(:doc) { doc_curie_uri('#{name}') }
222
-
223
- embed :'doc:create-form' do
224
- #{model_class_name}.create_form.tap do |form|
225
- form.self_link = new_#{name}_uri
226
- form.href = #{plural_name}_uri
227
- end
228
- end
229
183
  end
230
184
  EOS
231
185
  end
@@ -239,11 +193,9 @@ module Shaf
239
193
  policy_name: "#{name}_policy",
240
194
  attributes: attributes,
241
195
  links: links,
242
- embeds: embeds,
243
196
  attributes_with_doc: attributes_with_doc,
244
197
  curies_with_doc: curies_with_doc,
245
198
  links_with_doc: links_with_doc,
246
- embeds_with_doc: embeds_with_doc,
247
199
  collection_with_doc: collection_with_doc,
248
200
  }
249
201
  end
@@ -14,6 +14,7 @@ class <%= controller_class_name %> < BaseController
14
14
 
15
15
  get :new_<%= name %>_uri do
16
16
  authorize! :read
17
+ cache_control(:private, http_cache_max_age: :short)
17
18
  respond_with create_form
18
19
  end
19
20
 
@@ -31,6 +32,7 @@ class <%= controller_class_name %> < BaseController
31
32
 
32
33
  get :edit_<%= name %>_uri do
33
34
  authorize! :read
35
+ cache_control(:private, http_cache_max_age: :short)
34
36
  respond_with edit_form
35
37
  end
36
38
 
@@ -1,5 +1,6 @@
1
- class <%= policy_class_name %>
2
- include HALPresenter::Policy::DSL
1
+ require 'policies/base_policy'
2
+
3
+ class <%= policy_class_name %> < BasePolicy
3
4
 
4
5
  # Auto generated policy: Update this file to suite your API!
5
6
 
@@ -1,8 +1,7 @@
1
+ require 'serializers/base_serializer'
1
2
  require 'policies/<%= policy_name %>'
2
3
 
3
- class <%= class_name %>
4
- extend HALPresenter
5
- extend Shaf::UriHelper
4
+ class <%= class_name %> < BaseSerializer
6
5
 
7
6
  model <%= model_class_name %>
8
7
  policy <%= policy_class_name %>
@@ -15,9 +14,6 @@ class <%= class_name %>
15
14
  <% links_with_doc.each do |link| %>
16
15
  <%= link.join("\n ") %>
17
16
  <% end %>
18
- <% embeds_with_doc.each do |embed| %>
19
- <%= embed.join("\n ") %>
20
- <% end %>
21
17
  <% if collection_with_doc %>
22
18
  <%= collection_with_doc.join("\n ") %>
23
19
  <% end %>
@@ -34,21 +34,21 @@ describe <%= model_class_name %>, type: :integration do
34
34
  it "can create <%= plural_name %>" do
35
35
  get <%= plural_name %>_uri
36
36
 
37
- embedded :'doc:create-form' do
38
- links[:self][:href].must_equal new_<%= name %>_uri
39
- attributes[:href].must_equal <%= plural_name %>_uri
40
- attributes[:method].must_equal "POST"
41
- attributes[:name].must_equal "create-<%= name %>"
42
- attributes[:title].must_equal "Create <%= model_class_name %>"
43
- attributes[:type].must_equal "application/json"
44
- attributes[:fields].size.must_equal <%= params.size %>
45
-
46
- payload = fill_form attributes[:fields]
47
- post attributes[:href], payload
48
- status.must_equal 201
49
- link_rels.must_include(:self)
50
- headers["Location"].must_equal links[:self][:href]
51
- end
37
+ link_rels.must_include(:'doc:create-form')
38
+ follow_rel :'doc:create-form'
39
+ links[:self][:href].must_equal new_<%= name %>_uri
40
+ attributes[:href].must_equal <%= plural_name %>_uri
41
+ attributes[:method].must_equal "POST"
42
+ attributes[:name].must_equal "create-<%= name %>"
43
+ attributes[:title].must_equal "Create <%= model_class_name %>"
44
+ attributes[:type].must_equal "application/json"
45
+ attributes[:fields].size.must_equal <%= params.size %>
46
+
47
+ payload = fill_form attributes[:fields]
48
+ post attributes[:href], payload
49
+ status.must_equal 201
50
+ link_rels.must_include(:self)
51
+ headers["Location"].must_equal links[:self][:href]
52
52
 
53
53
  get <%= plural_name %>_uri
54
54
  status.must_equal 200
@@ -71,22 +71,22 @@ describe <%= model_class_name %>, type: :integration do
71
71
  <%= name %> = <%= model_class_name %>.create
72
72
  get <%= name %>_uri(<%= name %>)
73
73
  status.must_equal 200
74
- link_rels.must_include(:'doc:edit-form')
75
74
 
76
- embedded :'doc:edit-form' do
77
- links[:self][:href].must_equal edit_<%= name %>_uri(<%= name %>)
78
- attributes[:href].must_equal <%= name %>_uri(<%= name %>)
79
- attributes[:method].must_equal "PUT"
80
- attributes[:name].must_equal "update-<%= name %>"
81
- attributes[:title].must_equal "Update <%= model_class_name %>"
82
- attributes[:type].must_equal "application/json"
83
- attributes[:fields].size.must_equal <%= params.size %>
84
-
85
- payload = fill_form attributes[:fields]
86
- put attributes[:href], payload
87
- status.must_equal 200
88
- link_rels.must_include(:self)
89
- end
75
+ link_rels.must_include(:'doc:edit-form')
76
+ follow_rel :'doc:edit-form'
77
+
78
+ links[:self][:href].must_equal edit_<%= name %>_uri(<%= name %>)
79
+ attributes[:href].must_equal <%= name %>_uri(<%= name %>)
80
+ attributes[:method].must_equal "PUT"
81
+ attributes[:name].must_equal "update-<%= name %>"
82
+ attributes[:title].must_equal "Update <%= model_class_name %>"
83
+ attributes[:type].must_equal "application/json"
84
+ attributes[:fields].size.must_equal <%= params.size %>
85
+
86
+ payload = fill_form attributes[:fields]
87
+ put attributes[:href], payload
88
+ status.must_equal 200
89
+ link_rels.must_include(:self)
90
90
  end
91
91
 
92
92
  it "<%= plural_name %> can be deleted" do
@@ -1,6 +1,3 @@
1
- require 'fileutils'
2
- require 'erb'
3
- require 'ostruct'
4
1
  require 'shaf/registrable_factory'
5
2
 
6
3
  module Shaf
@@ -8,63 +5,10 @@ module Shaf
8
5
  class Factory
9
6
  extend RegistrableFactory
10
7
  end
11
-
12
- class Base
13
- attr_reader :args
14
-
15
- class << self
16
- def inherited(child)
17
- Factory.register(child)
18
- end
19
-
20
- def identifier(*ids)
21
- @identifiers = ids.flatten
22
- end
23
-
24
- def usage(str = nil, &block)
25
- @usage = str || block
26
- end
27
-
28
- def options(option_parser, options); end
29
- end
30
-
31
- def initialize(*args)
32
- @args = args.dup
33
- end
34
-
35
- def call(options = {}); end
36
-
37
- def template_dir
38
- File.expand_path('../generator/templates', __FILE__)
39
- end
40
-
41
- def read_template(file, directory = nil)
42
- directory ||= template_dir
43
- filename = File.join(directory, file)
44
- filename << ".erb" unless filename.end_with?(".erb")
45
- File.read(filename)
46
- end
47
-
48
- def render(template, locals = {})
49
- str = read_template(template)
50
- locals[:changes] ||= []
51
- b = OpenStruct.new(locals).instance_eval { binding }
52
- ERB.new(str, 0, '%-<>').result(b)
53
- rescue SystemCallError => e
54
- puts "Failed to render template #{template}: #{e.message}"
55
- raise
56
- end
57
-
58
- def write_output(file, content)
59
- dir = File.dirname(file)
60
- FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
61
- File.write(file, content)
62
- puts "Added: #{file}"
63
- end
64
- end
65
8
  end
66
9
  end
67
10
 
11
+ require 'shaf/generator/base'
68
12
  require 'shaf/generator/controller'
69
13
  require 'shaf/generator/migration'
70
14
  require 'shaf/generator/model'
@@ -0,0 +1,21 @@
1
+ module Shaf
2
+ module CacheControl
3
+
4
+ def cache_control(*args, **kwargs)
5
+ return unless Shaf::Settings.http_cache
6
+ set_max_age(kwargs)
7
+ super(*args, **kwargs)
8
+ end
9
+
10
+ private
11
+
12
+ def set_max_age(kwargs)
13
+ max_age = kwargs[:http_cache_max_age] or return
14
+ if max_age.is_a? Symbol
15
+ key = "http_cache_max_age_#{max_age}".to_sym
16
+ max_age = Settings.respond_to?(key) ? Settings.send(key) : 86400
17
+ kwargs[:http_cache_max_age] = max_age
18
+ end
19
+ end
20
+ end
21
+ end
data/lib/shaf/helpers.rb CHANGED
@@ -1,13 +1,15 @@
1
- require 'shaf/helpers/payload'
1
+ require 'shaf/helpers/cache_control'
2
2
  require 'shaf/helpers/json_html'
3
3
  require 'shaf/helpers/paginate'
4
+ require 'shaf/helpers/payload'
4
5
 
5
6
  module Shaf
6
7
  def self.helpers
7
8
  [
8
- Payload,
9
+ CacheControl,
9
10
  JsonHtml,
10
11
  Paginate,
12
+ Payload,
11
13
  ]
12
14
  end
13
15
  end
data/lib/shaf/rake/db.rb CHANGED
@@ -73,7 +73,7 @@ Shaf::DbTask.new(:seed, description: "Seed the Database") do
73
73
 
74
74
  if Dir.exist? "db/seeds"
75
75
  Dir['db/seeds/**/*.rb'].each do |file|
76
- require file.sub(".rb", "")
76
+ require file.sub(/.rb\Z/, "")
77
77
  end
78
78
  end
79
79
  end
@@ -3,22 +3,39 @@ require 'yaml'
3
3
  module Shaf
4
4
  module Upgrade
5
5
  class Manifest
6
- attr_reader :target_version, :patches
6
+ attr_reader :target_version, :files
7
7
 
8
- def initialize(target_version:, patches: {})
8
+ def initialize(target_version:, patches: nil, add: nil, drop: nil)
9
9
  @target_version = target_version
10
- @patches = build_patterns(patches)
10
+ @files = {}
11
+ @files[:patch] = build_patterns(patches)
12
+ @files[:add] = add || {}
13
+ @files[:drop] = (drop || []).map { |d| Regexp.new(d) }
11
14
  end
12
15
 
16
+ def patch_for(file)
17
+ first_match = files[:patch].find { |_, pattern| file =~ pattern }
18
+ (first_match || []).first
19
+ end
20
+
21
+ def drop?(file)
22
+ files[:drop].any? { |pattern| file =~ pattern }
23
+ end
24
+
25
+ def stats
26
+ "Add: #{files[:add].size}, " \
27
+ "Del: #{files[:drop].size}, " \
28
+ "Patch: #{files[:patch].size}"
29
+ end
30
+
31
+ private
32
+
13
33
  def build_patterns(patches)
34
+ return {} unless patches
14
35
  patches.each_with_object({}) do |(chksum, pattern), hash|
15
- hash[chksum] = /#{pattern}/
36
+ hash[chksum] = Regexp.new(pattern)
16
37
  end
17
38
  end
18
-
19
- def patch_name_for(file)
20
- @patches.select { |_, pattern| pattern =~ file }.keys.first
21
- end
22
39
  end
23
40
  end
24
41
  end
@@ -29,3 +46,7 @@ end
29
46
  # patches:
30
47
  # cd5b0bf61070a9fd57e60c45e9aaf64a: config/database.rb
31
48
  # 59783ecfa5f41b84c6fad734e7aa6a1d: Rakefile
49
+ # add:
50
+ # 8ece24b8c440675bd3d188155909431c: api/policies/base_policy.rb
51
+ # drop:
52
+ # - api/policies/base_policy.rb
@@ -3,6 +3,7 @@ require 'zlib'
3
3
  require 'set'
4
4
  require 'digest'
5
5
  require 'open3'
6
+ require 'fileutils'
6
7
 
7
8
  module Shaf
8
9
  module Upgrade
@@ -13,8 +14,8 @@ module Shaf
13
14
  class VersionNotFoundError < UpgradeError; end
14
15
  class VersionConflictError < UpgradeError; end
15
16
  class ManifestNotFoundError < UpgradeError; end
16
- class MissingPatchError < UpgradeError; end
17
- class PatchNotInManifestError < UpgradeError; end
17
+ class MissingFileError < UpgradeError; end
18
+ class FileNotInManifestError < UpgradeError; end
18
19
  class BadChecksumError < UpgradeError; end
19
20
 
20
21
  UPGRADE_FILES_PATH = File.join(Utils.gem_root, 'upgrades').freeze
@@ -50,10 +51,10 @@ module Shaf
50
51
  end
51
52
  end
52
53
 
53
- def initialize(version)
54
+ def initialize(version, manifest = nil, files = {})
54
55
  @version = Version.new(version)
55
- @manifest = nil
56
- @patches = {}
56
+ @manifest = manifest
57
+ @files = files
57
58
  end
58
59
 
59
60
  def load
@@ -74,22 +75,15 @@ module Shaf
74
75
  end
75
76
 
76
77
  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
78
+ apply_patches(dir)
79
+ apply_additions
80
+ apply_drops(dir)
83
81
  end
84
82
 
85
83
  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
84
+ str = "Shaf::Upgrade::Package for version #{@version}"
85
+ return str if @manifest.nil?
86
+ "#{str} (#{@manifest.stats})"
93
87
  end
94
88
 
95
89
  private
@@ -107,15 +101,17 @@ module Shaf
107
101
  if filename == MANIFEST_FILENAME
108
102
  parse_manifest content
109
103
  else
110
- @patches[filename] = content
104
+ @files[filename] = content
111
105
  end
112
106
  end
113
107
 
114
108
  def parse_manifest(content)
115
- h = YAML.safe_load(content)
109
+ hash = YAML.safe_load(content)
116
110
  @manifest = Manifest.new(
117
- target_version: h['target_version'],
118
- patches: h['patches']
111
+ target_version: hash["target_version"],
112
+ patches: hash["patches"],
113
+ add: hash["add"],
114
+ drop: hash["drop"]
119
115
  )
120
116
  end
121
117
 
@@ -123,12 +119,18 @@ module Shaf
123
119
  raise ManifestNotFoundError unless @manifest
124
120
  raise VersionConflictError unless @version == @manifest.target_version
125
121
 
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
122
+ from_file = @files.keys.to_set
130
123
 
131
- @patches.each do |md5, content|
124
+ manifest_patches = @manifest.files[:patch].keys.to_set
125
+ raise MissingFileError if from_file < manifest_patches
126
+
127
+
128
+ to_add = @manifest.files[:add].keys.to_set
129
+ raise MissingFileError if from_file < to_add
130
+
131
+ raise FileNotInManifestError if (manifest_patches + to_add) < from_file
132
+
133
+ @files.each do |md5, content|
132
134
  raise BadChecksumError unless Digest::MD5.hexdigest(content) == md5
133
135
  end
134
136
 
@@ -140,6 +142,15 @@ module Shaf
140
142
  Dir["#{dir}**/*"]
141
143
  end
142
144
 
145
+ def apply_patches(dir = nil)
146
+ files_in(dir).all? do |file|
147
+ name = @manifest.patch_for(file) # returns nil when file
148
+ next true unless name # shouldn't be patched
149
+ patch = @files[name]
150
+ apply_patch(file, patch)
151
+ end
152
+ end
153
+
143
154
  def apply_patch(file, patch)
144
155
  success = nil
145
156
  Open3.popen3('patch', file, '-r', '-') do |i, o, e, t|
@@ -153,6 +164,19 @@ module Shaf
153
164
  success
154
165
  end
155
166
 
167
+ def apply_additions
168
+ @manifest.files[:add].each do |chksum, filename|
169
+ content = @files[chksum]
170
+ FileUtils.mkdir_p File.dirname(filename)
171
+ File.open(filename, 'w') { |file| file.write(content) }
172
+ end
173
+ end
174
+
175
+ def apply_drops(dir = nil)
176
+ files_in(dir).map do |file|
177
+ File.unlink(file) if @manifest.drop?(file)
178
+ end
179
+ end
156
180
  end
157
181
  end
158
182
  end
data/lib/shaf/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Shaf
2
- VERSION = "0.4.1"
2
+ VERSION = "0.5.0"
3
3
  end