druid-tools 1.0.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/druid-tools.gemspec CHANGED
@@ -1,4 +1,4 @@
1
- # -*- encoding: utf-8 -*-
1
+ # frozen_string_literal: true
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.authors = ['Michael Klein', 'Darren Hardy']
@@ -7,18 +7,21 @@ Gem::Specification.new do |gem|
7
7
  gem.summary = 'Tools to manipulate DRUID trees and content directories'
8
8
  gem.homepage = 'http://github.com/sul-dlss/druid-tools'
9
9
  gem.licenses = ['ALv2', 'Stanford University Libraries']
10
- gem.has_rdoc = true
10
+ gem.metadata['rubygems_mfa_required'] = 'true'
11
11
 
12
- gem.files = `git ls-files`.split($\)
13
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
12
+ gem.required_ruby_version = '>= 3.0'
13
+
14
+ gem.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
15
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
14
16
  gem.test_files = gem.files.grep(%r{^spec/})
15
17
  gem.name = 'druid-tools'
16
18
  gem.require_paths = ['lib']
17
19
  gem.version = File.read('VERSION').strip
18
20
 
21
+ gem.add_dependency 'deprecation'
22
+ gem.add_development_dependency 'coveralls'
19
23
  gem.add_development_dependency 'rake', '>= 10.1.0'
20
24
  gem.add_development_dependency 'rspec', '~> 3.0'
21
- gem.add_development_dependency 'coveralls'
22
- gem.add_development_dependency 'rubocop', '~> 0.50.0' # avoid code churn due to rubocop changes
23
- gem.add_development_dependency 'rubocop-rspec', '~> 1.18.0' # avoid code churn due to rubocop-rspec changes
25
+ gem.add_development_dependency 'rubocop'
26
+ gem.add_development_dependency 'rubocop-rspec'
24
27
  end
data/lib/druid-tools.rb CHANGED
@@ -1 +1,3 @@
1
- require 'druid_tools'
1
+ # frozen_string_literal: true
2
+
3
+ require 'druid_tools'
@@ -1,9 +1,8 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  module DruidTools
3
-
4
4
  # Overrides the Druid#tree method
5
5
  class AccessDruid < Druid
6
-
7
6
  self.prefix = 'druid'
8
7
 
9
8
  def tree
@@ -11,15 +10,17 @@ module DruidTools
11
10
  end
12
11
 
13
12
  # all content lives in the base druid directory
14
- def path(extra=nil, create=false)
15
- result = File.join(*([base,tree].compact))
16
- mkdir(extra) if create and not File.exists?(result)
13
+ def path(extra = nil, create = false)
14
+ result = File.join(*[base, tree].compact)
15
+ mkdir(extra) if create && !File.exist?(result)
17
16
  result
18
17
  end
19
18
 
19
+ def pruning_base
20
+ pathname
21
+ end
20
22
  end
21
23
 
22
24
  PurlDruid = AccessDruid
23
25
  StacksDruid = AccessDruid
24
-
25
26
  end
@@ -1,99 +1,112 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
  require 'fileutils'
5
+ require 'deprecation'
3
6
 
4
7
  module DruidTools
5
8
  class Druid
6
- @@deletes_directory_name = '.deletes'
9
+ extend Deprecation
10
+ self.deprecation_horizon = 'druid-tools 3.0.0'
11
+
7
12
  attr_accessor :druid, :base
8
13
 
9
14
  # See https://consul.stanford.edu/pages/viewpage.action?title=SURI+2.0+Specification&spaceKey=chimera
10
15
  # character class matching allowed letters in a druid suitable for use in regex (no aeioul)
11
- STRICT_LET = '[b-df-hjkmnp-tv-z]'.freeze
16
+ STRICT_LET = '[b-df-hjkmnp-tv-z]'
12
17
 
13
18
  class << self
14
19
  attr_accessor :prefix
15
20
 
16
21
  # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
17
22
  # @return [Regexp] matches druid:aa111aa1111 or aa111aa1111
18
- def pattern(strict=false)
19
- return /\A(?:#{self.prefix}:)?(#{STRICT_LET}{2})(\d{3})(#{STRICT_LET}{2})(\d{4})\z/ if strict
20
- /\A(?:#{self.prefix}:)?([a-z]{2})(\d{3})([a-z]{2})(\d{4})\z/
23
+ def pattern(strict = false)
24
+ return /\A(?:#{prefix}:)?(#{STRICT_LET}{2})(\d{3})(#{STRICT_LET}{2})(\d{4})\z/ if strict
25
+
26
+ /\A(?:#{prefix}:)?([a-z]{2})(\d{3})([a-z]{2})(\d{4})\z/
21
27
  end
22
28
 
23
29
  # @return [String] suitable for use in [Dir#glob]
24
30
  def glob
25
- "{#{self.prefix}:,}[a-z][a-z][0-9][0-9][0-9][a-z][a-z][0-9][0-9][0-9][0-9]"
31
+ "{#{prefix}:,}[a-z][a-z][0-9][0-9][0-9][a-z][a-z][0-9][0-9][0-9][0-9]"
26
32
  end
27
33
 
28
34
  # @return [String] suitable for use in [Dir#glob]
29
35
  def strict_glob
30
- "{#{self.prefix}:,}#{STRICT_LET}#{STRICT_LET}[0-9][0-9][0-9]#{STRICT_LET}#{STRICT_LET}[0-9][0-9][0-9][0-9]"
36
+ "{#{prefix}:,}#{STRICT_LET}#{STRICT_LET}[0-9][0-9][0-9]#{STRICT_LET}#{STRICT_LET}[0-9][0-9][0-9][0-9]"
31
37
  end
32
38
 
33
39
  # @param [String] druid id
34
40
  # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
35
41
  # @return [Boolean] true if druid matches pattern; otherwise false
36
- def valid?(druid, strict=false)
42
+ def valid?(druid, strict = false)
37
43
  druid =~ pattern(strict) ? true : false
38
44
  end
39
-
40
45
  end
41
46
  self.prefix = 'druid'
42
47
 
43
- [:content, :metadata, :temp].each do |dir_type|
44
- self.class_eval <<-EOC
45
- def #{dir_type}_dir(create=true)
46
- path("#{dir_type}",create)
47
- end
48
+ def content_dir(create = true)
49
+ path('content', create)
50
+ end
48
51
 
49
- def find_#{dir_type}(path)
50
- find(:#{dir_type},path)
51
- end
52
- EOC
52
+ def metadata_dir(create = true)
53
+ path('metadata', create)
54
+ end
55
+
56
+ def temp_dir(create = true)
57
+ path('temp', create)
58
+ end
59
+
60
+ def find_content(path)
61
+ find(:content, path)
62
+ end
63
+
64
+ def find_metadata(path)
65
+ find(:metadata, path)
66
+ end
67
+
68
+ def find_temp(path)
69
+ find(:temp, path)
53
70
  end
54
71
 
55
72
  # @param druid [String] A valid druid
56
73
  # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
57
74
  # @param base [String] The directory used by #path
58
- def initialize(druid, base='.', strict=false)
75
+ def initialize(druid, base = '.', strict = false)
59
76
  druid = druid.to_s unless druid.is_a? String
60
- unless self.class.valid?(druid, strict)
61
- raise ArgumentError, "Invalid DRUID: '#{druid}'"
62
- end
77
+ raise ArgumentError, "Invalid DRUID: '#{druid}'" unless self.class.valid?(druid, strict)
78
+
63
79
  druid = [self.class.prefix, druid].join(':') unless druid =~ /^#{self.class.prefix}:/
64
80
  @base = base
65
81
  @druid = druid
66
82
  end
67
83
 
68
84
  def id
69
- @druid.scan(self.class.pattern).flatten.join('')
85
+ @druid.scan(self.class.pattern).flatten.join
70
86
  end
71
87
 
72
88
  def tree
73
89
  @druid.scan(self.class.pattern).flatten + [id]
74
90
  end
75
91
 
76
- def path(extra=nil, create=false)
77
- result = File.join(*([base,tree,extra].compact))
78
- mkdir(extra) if create and not File.exists?(result)
92
+ def path(extra = nil, create = false)
93
+ result = File.join(*[base, tree, extra].compact)
94
+ mkdir(extra) if create && !File.exist?(result)
79
95
  result
80
96
  end
81
97
 
82
- def mkdir(extra=nil)
98
+ def mkdir(extra = nil)
83
99
  new_path = path(extra)
84
- if(File.symlink? new_path)
85
- raise DruidTools::DifferentContentExistsError, "Unable to create directory, link already exists: #{new_path}"
86
- end
87
- if(File.directory? new_path)
88
- raise DruidTools::SameContentExistsError, "The directory already exists: #{new_path}"
89
- end
100
+ raise DruidTools::DifferentContentExistsError, "Unable to create directory, link already exists: #{new_path}" if File.symlink? new_path
101
+ raise DruidTools::SameContentExistsError, "The directory already exists: #{new_path}" if File.directory? new_path
102
+
90
103
  FileUtils.mkdir_p(new_path)
91
104
  end
92
105
 
93
106
  def find(type, path)
94
- possibles = [self.path(type.to_s),self.path,File.expand_path('..',self.path)]
95
- loc = possibles.find { |p| File.exists?(File.join(p,path)) }
96
- loc.nil? ? nil : File.join(loc,path)
107
+ possibles = [self.path(type.to_s), self.path, File.expand_path('..', self.path)]
108
+ loc = possibles.find { |p| File.exist?(File.join(p, path)) }
109
+ loc.nil? ? nil : File.join(loc, path)
97
110
  end
98
111
 
99
112
  # @param [String] type The type of directory being sought ('content', 'metadata', or 'temp')
@@ -101,35 +114,39 @@ module DruidTools
101
114
  # @return [Pathname] Search for and return the pathname of the directory that contains the list of files.
102
115
  # Raises an exception unless a directory is found that contains all the files in the list.
103
116
  def find_filelist_parent(type, filelist)
104
- raise "File list not specified" if filelist.nil? or filelist.empty?
117
+ raise 'File list not specified' if filelist.nil? || filelist.empty?
118
+
105
119
  filelist = [filelist] unless filelist.is_a?(Array)
106
- search_dir = Pathname(self.path(type))
120
+ search_dir = Pathname(path(type))
107
121
  directories = [search_dir, search_dir.parent, search_dir.parent.parent]
108
122
  found_dir = directories.find { |pathname| pathname.join(filelist[0]).exist? }
109
123
  raise "#{type} dir not found for '#{filelist[0]}' when searching '#{search_dir}'" if found_dir.nil?
124
+
110
125
  filelist.each do |filename|
111
- raise "File '#{filename}' not found in #{type} dir s'#{found_dir}'" unless found_dir.join(filename).exist?
126
+ raise "File '#{filename}' not found in #{type} dir '#{found_dir}'" unless found_dir.join(filename).exist?
112
127
  end
113
128
  found_dir
114
129
  end
115
130
 
116
- def mkdir_with_final_link(source, extra=nil)
131
+ def mkdir_with_final_link(source, extra = nil)
117
132
  new_path = path(extra)
118
- if(File.directory?(new_path) && !File.symlink?(new_path))
133
+ if File.directory?(new_path) && !File.symlink?(new_path)
119
134
  raise DruidTools::DifferentContentExistsError, "Unable to create link, directory already exists: #{new_path}"
120
135
  end
121
- real_path = File.expand_path('..',new_path)
136
+
137
+ real_path = File.expand_path('..', new_path)
122
138
  FileUtils.mkdir_p(real_path)
123
- FileUtils.ln_s(source, new_path, :force=>true)
139
+ FileUtils.ln_s(source, new_path, force: true)
124
140
  end
141
+ deprecation_deprecate :mkdir_with_final_link
125
142
 
126
- def rmdir(extra=nil)
143
+ def rmdir(extra = nil)
127
144
  parts = tree
128
145
  parts << extra unless extra.nil?
129
- while parts.length > 0
146
+ until parts.empty?
130
147
  dir = File.join(base, *parts)
131
148
  begin
132
- FileUtils.rm(File.join(dir,'.DS_Store'), :force => true)
149
+ FileUtils.rm(File.join(dir, '.DS_Store'), force: true)
133
150
  FileUtils.rmdir(dir)
134
151
  rescue Errno::ENOTEMPTY
135
152
  break
@@ -137,98 +154,35 @@ module DruidTools
137
154
  parts.pop
138
155
  end
139
156
  end
157
+ deprecation_deprecate :rmdir
140
158
 
141
159
  def pathname
142
- Pathname self.path
160
+ Pathname path
143
161
  end
144
162
 
145
163
  def base_pathname
146
- Pathname self.base
147
- end
148
-
149
- def prune!
150
- this_path = pathname
151
- parent = this_path.parent
152
- parent.rmtree if parent.exist? && parent != base_pathname
153
- prune_ancestors parent.parent
154
- creates_delete_record
155
- end
156
-
157
- #This function checks for existance of a .deletes dir one level into the path (ex: stacks/.deletes or purl/.deletes).
158
- #If the directory does not exist, it is created. If the directory exists, check to see if the current druid has an entry there, if it does delete it.
159
- #This is done because a file might be deleted, then republishing, then deleted we again, and we want to log the most recent delete.
160
- #
161
- #@raises [Errno::EACCES] If write priveleges are denied
162
- #
163
- #@return [void]
164
- def prep_deletes_dir
165
- #Check for existences of deletes dir
166
- create_deletes_dir if !deletes_dir_exists?
167
- #In theory we could return true after this step (if it fires), since if there was no deletes dir then the file can't be present in the dir
168
-
169
- #Check to see if this druid has been deleted before, meaning file currently exists
170
- deletes_delete_record if deletes_record_exists?
171
- end
172
-
173
- #Provide the location for the .deletes directory in the tree
174
- #
175
- #@return [Pathname] the path to the directory, ex: "stacks/.deletes"
176
- def deletes_dir_pathname
177
- return Pathname(self.base.to_s + (File::SEPARATOR+@@deletes_directory_name))
164
+ Pathname base
178
165
  end
179
166
 
180
- def deletes_record_pathname
181
- return Pathname(deletes_dir_pathname.to_s + File::SEPARATOR + self.id)
167
+ def pruning_base
168
+ pathname.parent
182
169
  end
183
170
 
184
- #Using the deletes directory path supplied by deletes_dir_pathname, this function determines if this directory exists
185
- #
186
- #@return [Boolean] true if if exists, false if it does not
187
- def deletes_dir_exists?
188
- return File.directory?(deletes_dir_pathname)
189
- end
190
-
191
- def deletes_record_exists?
192
- return File.exists?(deletes_dir_pathname.to_s + File::SEPARATOR + self.id)
193
- end
194
-
195
- #Creates the deletes dir using the path supplied by deletes_dir_pathname
196
- #
197
- #@raises [Errno::EACCES] If write priveleges are denied
198
- #
199
- #@return [void]
200
- def create_deletes_dir
201
- FileUtils::mkdir_p deletes_dir_pathname
202
- end
203
-
204
- #Deletes the delete record if it currently exists. This is done to change the filed created, not just last modified time, on the system
205
- #
206
- #@raises [Errno::EACCES] If write priveleges are denied
207
- #
208
- #return [void]
209
- def deletes_delete_record
210
- FileUtils.rm(deletes_record_pathname) if deletes_record_exists? #thrown in to prevent an Errno::ENOENT if you call this on something without a delete record
211
- end
212
-
213
- #Creates an empty (pointer) file using the object's id in the .deletes dir
214
- #
215
- #@raises [Errno::EACCES] If write priveleges are denied
216
- #
217
- #@return [void]
218
- def creates_delete_record
219
- prep_deletes_dir
220
- FileUtils.touch(deletes_record_pathname)
171
+ def prune!
172
+ pruning_base.rmtree if pruning_base.exist? && pruning_base != base_pathname
173
+ prune_ancestors pruning_base.parent
221
174
  end
175
+ deprecation_deprecate :prune!
222
176
 
223
177
  # @param [Pathname] outermost_branch The branch at which pruning begins
224
178
  # @return [void] Ascend the druid tree and prune empty branches
225
179
  def prune_ancestors(outermost_branch)
226
- while outermost_branch.exist? && outermost_branch.children.size == 0
180
+ while outermost_branch.exist? && outermost_branch.children.empty?
227
181
  outermost_branch.rmdir
228
182
  outermost_branch = outermost_branch.parent
229
- break if outermost_branch == base_pathname
183
+ break if outermost_branch == base_pathname
230
184
  end
231
185
  end
232
-
186
+ deprecation_deprecate :prune_ancestors
233
187
  end
234
188
  end
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DruidTools
2
- class SameContentExistsError < Exception; end
3
- class DifferentContentExistsError < Exception; end
4
- class InvalidDruidError < Exception; end
5
- end
4
+ class SameContentExistsError < RuntimeError; end
5
+ class DifferentContentExistsError < RuntimeError; end
6
+ class InvalidDruidError < RuntimeError; end
7
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DruidTools
2
4
  VERSION = File.read(File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'VERSION'))).to_s.strip
3
5
  end
data/lib/druid_tools.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module DruidTools
2
4
  require 'druid_tools/version'
3
5
  require 'druid_tools/druid'
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+
5
+ RSpec.describe DruidTools::PurlDruid do
6
+ let(:purl_root) { Dir.mktmpdir }
7
+
8
+ let(:druid) { described_class.new druid_str, purl_root }
9
+ let(:druid_str) { 'druid:cd456ef7890' }
10
+
11
+ after do
12
+ FileUtils.remove_entry purl_root
13
+ end
14
+
15
+ it 'overrides Druid#tree so that the leaf is not Druid#id' do
16
+ expect(druid.tree).to eq(%w[cd 456 ef 7890])
17
+ end
18
+
19
+ describe '#pruning_base' do
20
+ subject(:path) { described_class.new(druid_str).pruning_base }
21
+
22
+ it { is_expected.to eq(Pathname.new('./cd/456/ef/7890')) }
23
+ end
24
+
25
+ describe '#content_dir' do
26
+ it 'creates content directories at leaf of the druid tree' do
27
+ expect(druid.content_dir).to match(%r{ef/7890$})
28
+ end
29
+
30
+ it "does not create a 'content' subdirectory" do
31
+ expect(druid.content_dir).not_to match(/content$/)
32
+ end
33
+ end
34
+ end