druid-tools 1.0.0 → 2.2.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.
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