druid-tools 1.0.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  [![Build Status](https://travis-ci.org/sul-dlss/druid-tools.svg?branch=delete-records)](https://travis-ci.org/sul-dlss/druid-tools)
2
2
  [![Coverage Status](https://coveralls.io/repos/github/sul-dlss/druid-tools/badge.svg?branch=master)](https://coveralls.io/github/sul-dlss/druid-tools?branch=master)
3
- [![Dependency Status](https://gemnasium.com/badges/github.com/sul-dlss/druid-tools.svg)](https://gemnasium.com/github.com/sul-dlss/druid-tools)
4
3
  [![Gem Version](https://badge.fury.io/rb/druid-tools.svg)](https://badge.fury.io/rb/druid-tools)
5
4
 
6
5
  # Druid::Tools
@@ -11,7 +10,7 @@ Note that druid syntax is defined in consul (and druids are issued by the SURI s
11
10
 
12
11
  Druid format:
13
12
 
14
- bbdddbbdddd (two letters two digits two letters 4 digits)
13
+ bbdddbbdddd (two letters three digits two letters 4 digits)
15
14
 
16
15
  Letters must be lowercase, and must not include A, E, I, O, U or L. (capitals for easier distinction here)
17
16
  We often use vowels in our test data, and this code base has allowed vowels historically (though not
data/Rakefile CHANGED
@@ -1,5 +1,7 @@
1
1
  #!/usr/bin/env rake
2
- require "bundler/gem_tasks"
2
+ # frozen_string_literal: true
3
+
4
+ require 'bundler/gem_tasks'
3
5
 
4
6
  require 'rspec/core/rake_task'
5
7
  RSpec::Core::RakeTask.new(:spec)
@@ -7,4 +9,4 @@ RSpec::Core::RakeTask.new(:spec)
7
9
  require 'rubocop/rake_task'
8
10
  RuboCop::RakeTask.new
9
11
 
10
- task :default => [:spec, :rubocop]
12
+ task default: %i[spec rubocop]
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.0.0
1
+ 2.0.0
@@ -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']
@@ -9,16 +9,16 @@ Gem::Specification.new do |gem|
9
9
  gem.licenses = ['ALv2', 'Stanford University Libraries']
10
10
  gem.has_rdoc = 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.files = `git ls-files`.split($OUTPUT_RECORD_SEPARATOR)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
14
14
  gem.test_files = gem.files.grep(%r{^spec/})
15
15
  gem.name = 'druid-tools'
16
16
  gem.require_paths = ['lib']
17
17
  gem.version = File.read('VERSION').strip
18
18
 
19
+ gem.add_development_dependency 'coveralls'
19
20
  gem.add_development_dependency 'rake', '>= 10.1.0'
20
21
  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
22
+ gem.add_development_dependency 'rubocop', '~> 0.70.0'
23
+ gem.add_development_dependency 'rubocop-rspec', '~> 1.33.0'
24
24
  end
@@ -1 +1,3 @@
1
- require 'druid_tools'
1
+ # frozen_string_literal: true
2
+
3
+ require 'druid_tools'
@@ -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'
@@ -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,13 @@ 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
-
20
18
  end
21
19
 
22
20
  PurlDruid = AccessDruid
23
21
  StacksDruid = AccessDruid
24
-
25
22
  end
@@ -1,47 +1,48 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'pathname'
2
4
  require 'fileutils'
3
5
 
4
6
  module DruidTools
5
7
  class Druid
6
- @@deletes_directory_name = '.deletes'
7
8
  attr_accessor :druid, :base
8
9
 
9
10
  # See https://consul.stanford.edu/pages/viewpage.action?title=SURI+2.0+Specification&spaceKey=chimera
10
11
  # character class matching allowed letters in a druid suitable for use in regex (no aeioul)
11
- STRICT_LET = '[b-df-hjkmnp-tv-z]'.freeze
12
+ STRICT_LET = '[b-df-hjkmnp-tv-z]'
12
13
 
13
14
  class << self
14
15
  attr_accessor :prefix
15
16
 
16
17
  # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
17
18
  # @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/
19
+ def pattern(strict = false)
20
+ return /\A(?:#{prefix}:)?(#{STRICT_LET}{2})(\d{3})(#{STRICT_LET}{2})(\d{4})\z/ if strict
21
+
22
+ /\A(?:#{prefix}:)?([a-z]{2})(\d{3})([a-z]{2})(\d{4})\z/
21
23
  end
22
24
 
23
25
  # @return [String] suitable for use in [Dir#glob]
24
26
  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]"
27
+ "{#{prefix}:,}[a-z][a-z][0-9][0-9][0-9][a-z][a-z][0-9][0-9][0-9][0-9]"
26
28
  end
27
29
 
28
30
  # @return [String] suitable for use in [Dir#glob]
29
31
  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]"
32
+ "{#{prefix}:,}#{STRICT_LET}#{STRICT_LET}[0-9][0-9][0-9]#{STRICT_LET}#{STRICT_LET}[0-9][0-9][0-9][0-9]"
31
33
  end
32
34
 
33
35
  # @param [String] druid id
34
36
  # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
35
37
  # @return [Boolean] true if druid matches pattern; otherwise false
36
- def valid?(druid, strict=false)
38
+ def valid?(druid, strict = false)
37
39
  druid =~ pattern(strict) ? true : false
38
40
  end
39
-
40
41
  end
41
42
  self.prefix = 'druid'
42
43
 
43
- [:content, :metadata, :temp].each do |dir_type|
44
- self.class_eval <<-EOC
44
+ %i[content metadata temp].each do |dir_type|
45
+ class_eval <<-EOC
45
46
  def #{dir_type}_dir(create=true)
46
47
  path("#{dir_type}",create)
47
48
  end
@@ -55,11 +56,10 @@ module DruidTools
55
56
  # @param druid [String] A valid druid
56
57
  # @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
57
58
  # @param base [String] The directory used by #path
58
- def initialize(druid, base='.', strict=false)
59
+ def initialize(druid, base = '.', strict = false)
59
60
  druid = druid.to_s unless druid.is_a? String
60
- unless self.class.valid?(druid, strict)
61
- raise ArgumentError, "Invalid DRUID: '#{druid}'"
62
- end
61
+ raise ArgumentError, "Invalid DRUID: '#{druid}'" unless self.class.valid?(druid, strict)
62
+
63
63
  druid = [self.class.prefix, druid].join(':') unless druid =~ /^#{self.class.prefix}:/
64
64
  @base = base
65
65
  @druid = druid
@@ -73,27 +73,24 @@ module DruidTools
73
73
  @druid.scan(self.class.pattern).flatten + [id]
74
74
  end
75
75
 
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)
76
+ def path(extra = nil, create = false)
77
+ result = File.join(*[base, tree, extra].compact)
78
+ mkdir(extra) if create && !File.exist?(result)
79
79
  result
80
80
  end
81
81
 
82
- def mkdir(extra=nil)
82
+ def mkdir(extra = nil)
83
83
  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
84
+ raise DruidTools::DifferentContentExistsError, "Unable to create directory, link already exists: #{new_path}" if File.symlink? new_path
85
+ raise DruidTools::SameContentExistsError, "The directory already exists: #{new_path}" if File.directory? new_path
86
+
90
87
  FileUtils.mkdir_p(new_path)
91
88
  end
92
89
 
93
90
  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)
91
+ possibles = [self.path(type.to_s), self.path, File.expand_path('..', self.path)]
92
+ loc = possibles.find { |p| File.exist?(File.join(p, path)) }
93
+ loc.nil? ? nil : File.join(loc, path)
97
94
  end
98
95
 
99
96
  # @param [String] type The type of directory being sought ('content', 'metadata', or 'temp')
@@ -101,35 +98,38 @@ module DruidTools
101
98
  # @return [Pathname] Search for and return the pathname of the directory that contains the list of files.
102
99
  # Raises an exception unless a directory is found that contains all the files in the list.
103
100
  def find_filelist_parent(type, filelist)
104
- raise "File list not specified" if filelist.nil? or filelist.empty?
101
+ raise 'File list not specified' if filelist.nil? || filelist.empty?
102
+
105
103
  filelist = [filelist] unless filelist.is_a?(Array)
106
- search_dir = Pathname(self.path(type))
104
+ search_dir = Pathname(path(type))
107
105
  directories = [search_dir, search_dir.parent, search_dir.parent.parent]
108
106
  found_dir = directories.find { |pathname| pathname.join(filelist[0]).exist? }
109
107
  raise "#{type} dir not found for '#{filelist[0]}' when searching '#{search_dir}'" if found_dir.nil?
108
+
110
109
  filelist.each do |filename|
111
- raise "File '#{filename}' not found in #{type} dir s'#{found_dir}'" unless found_dir.join(filename).exist?
110
+ raise "File '#{filename}' not found in #{type} dir '#{found_dir}'" unless found_dir.join(filename).exist?
112
111
  end
113
112
  found_dir
114
113
  end
115
114
 
116
- def mkdir_with_final_link(source, extra=nil)
115
+ def mkdir_with_final_link(source, extra = nil)
117
116
  new_path = path(extra)
118
- if(File.directory?(new_path) && !File.symlink?(new_path))
117
+ if File.directory?(new_path) && !File.symlink?(new_path)
119
118
  raise DruidTools::DifferentContentExistsError, "Unable to create link, directory already exists: #{new_path}"
120
119
  end
121
- real_path = File.expand_path('..',new_path)
120
+
121
+ real_path = File.expand_path('..', new_path)
122
122
  FileUtils.mkdir_p(real_path)
123
- FileUtils.ln_s(source, new_path, :force=>true)
123
+ FileUtils.ln_s(source, new_path, force: true)
124
124
  end
125
125
 
126
- def rmdir(extra=nil)
126
+ def rmdir(extra = nil)
127
127
  parts = tree
128
128
  parts << extra unless extra.nil?
129
- while parts.length > 0
129
+ until parts.empty?
130
130
  dir = File.join(base, *parts)
131
131
  begin
132
- FileUtils.rm(File.join(dir,'.DS_Store'), :force => true)
132
+ FileUtils.rm(File.join(dir, '.DS_Store'), force: true)
133
133
  FileUtils.rmdir(dir)
134
134
  rescue Errno::ENOTEMPTY
135
135
  break
@@ -139,11 +139,11 @@ module DruidTools
139
139
  end
140
140
 
141
141
  def pathname
142
- Pathname self.path
142
+ Pathname path
143
143
  end
144
144
 
145
145
  def base_pathname
146
- Pathname self.base
146
+ Pathname base
147
147
  end
148
148
 
149
149
  def prune!
@@ -151,84 +151,16 @@ module DruidTools
151
151
  parent = this_path.parent
152
152
  parent.rmtree if parent.exist? && parent != base_pathname
153
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))
178
- end
179
-
180
- def deletes_record_pathname
181
- return Pathname(deletes_dir_pathname.to_s + File::SEPARATOR + self.id)
182
- end
183
-
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)
221
154
  end
222
155
 
223
156
  # @param [Pathname] outermost_branch The branch at which pruning begins
224
157
  # @return [void] Ascend the druid tree and prune empty branches
225
158
  def prune_ancestors(outermost_branch)
226
- while outermost_branch.exist? && outermost_branch.children.size == 0
159
+ while outermost_branch.exist? && outermost_branch.children.empty?
227
160
  outermost_branch.rmdir
228
161
  outermost_branch = outermost_branch.parent
229
- break if outermost_branch == base_pathname
162
+ break if outermost_branch == base_pathname
230
163
  end
231
164
  end
232
-
233
165
  end
234
166
  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
@@ -0,0 +1,27 @@
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:cd456ef7890', purl_root }
9
+
10
+ after do
11
+ FileUtils.remove_entry purl_root
12
+ end
13
+
14
+ it 'overrides Druid#tree so that the leaf is not Druid#id' do
15
+ expect(druid.tree).to eq(%w[cd 456 ef 7890])
16
+ end
17
+
18
+ describe '#content_dir' do
19
+ it 'creates content directories at leaf of the druid tree' do
20
+ expect(druid.content_dir).to match(%r{ef/7890$})
21
+ end
22
+
23
+ it "does not create a 'content' subdirectory" do
24
+ expect(druid.content_dir).not_to match(/content$/)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,356 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe DruidTools::Druid do
4
+ let(:fixture_dir) { File.expand_path('fixtures', __dir__) }
5
+ let(:druid_str) { 'druid:cd456ef7890' }
6
+ let(:tree1) { File.join(fixture_dir, 'cd/456/ef/7890/cd456ef7890') }
7
+ let(:strictly_valid_druid_str) { 'druid:cd456gh1234' }
8
+ let(:tree2) { File.join(fixture_dir, 'cd/456/gh/1234/cd456gh1234') }
9
+
10
+ after do
11
+ FileUtils.rm_rf(File.join(fixture_dir, 'cd'))
12
+ end
13
+
14
+ describe '.valid?' do
15
+ # also tests .pattern
16
+ it 'correctly validates druid strings' do
17
+ tests = [
18
+ # Expected Input druid
19
+ [true, 'druid:aa000bb0001'],
20
+ [true, 'aa000bb0001'],
21
+ [false, 'Aa000bb0001'],
22
+ [false, "xxx\naa000bb0001"],
23
+ [false, 'aaa000bb0001'],
24
+ [false, 'druidX:aa000bb0001'],
25
+ [false, ':aa000bb0001'],
26
+ [true, 'aa123bb1234'],
27
+ [false, 'aa12bb1234'],
28
+ [false, 'aa1234bb1234'],
29
+ [false, 'aa123bb123'],
30
+ [false, 'aa123bb12345'],
31
+ [false, 'a123bb1234'],
32
+ [false, 'aaa123bb1234'],
33
+ [false, 'aa123b1234'],
34
+ [false, 'aa123bbb1234'],
35
+ [false, 'druid:az918AZ9381'.upcase],
36
+ [true, 'druid:az918AZ9381'.downcase],
37
+ [true, 'druid:zz943vx1492']
38
+ ]
39
+ tests.each do |exp, dru|
40
+ expect(described_class.valid?(dru)).to eq(exp)
41
+ expect(described_class.valid?(dru, false)).to eq(exp)
42
+ end
43
+ end
44
+ context 'with strict validation' do
45
+ it 'correctly validates druid strings' do
46
+ tests = [
47
+ # Expected Input druid
48
+ [false, 'aa000aa0000'],
49
+ [false, 'ee000ee0000'],
50
+ [false, 'ii000ii0000'],
51
+ [false, 'oo000oo0000'],
52
+ [false, 'uu000uu0000'],
53
+ [false, 'll000ll0000'],
54
+ [false, 'aa000bb0001'],
55
+ [true, 'druid:dd000bb0001'],
56
+ [false, 'druid:aa000bb0001'],
57
+ [true, 'dd000bb0001'],
58
+ [false, 'Dd000bb0001'],
59
+ [false, "xxx\ndd000bb0001"],
60
+ [false, 'ddd000bb0001'],
61
+ [false, 'druidX:dd000bb0001'],
62
+ [false, ':dd000bb0001'],
63
+ [true, 'cc123bb1234'],
64
+ [false, 'aa123bb1234'],
65
+ [false, 'dd12bb1234'],
66
+ [false, 'dd1234bb1234'],
67
+ [false, 'dd123bb123'],
68
+ [false, 'dd123bb12345'],
69
+ [false, 'd123bb1234'],
70
+ [false, 'ddd123bb1234'],
71
+ [false, 'dd123b1234'],
72
+ [false, 'dd123bbb1234'],
73
+ [false, 'druid:bz918BZ9381'.upcase],
74
+ [true, 'druid:bz918BZ9381'.downcase],
75
+ [false, 'druid:az918AZ9381'.downcase],
76
+ [true, 'druid:zz943vx1492']
77
+ ]
78
+ tests.each do |exp, dru|
79
+ expect(described_class.valid?(dru, true)).to eq(exp)
80
+ end
81
+ end
82
+ end
83
+ end
84
+
85
+ it '#druid provides the full druid including the prefix' do
86
+ expect(described_class.new('druid:cd456ef7890', fixture_dir).druid).to eq('druid:cd456ef7890')
87
+ expect(described_class.new('cd456ef7890', fixture_dir).druid).to eq('druid:cd456ef7890')
88
+ end
89
+
90
+ it '#id extracts the ID from the stem' do
91
+ expect(described_class.new('druid:cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
92
+ expect(described_class.new('cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
93
+ end
94
+
95
+ describe '#new' do
96
+ it 'raises exception if the druid is invalid' do
97
+ expect { described_class.new('nondruid:cd456ef7890', fixture_dir) }.to raise_error(ArgumentError)
98
+ expect { described_class.new('druid:cd4567ef890', fixture_dir) }.to raise_error(ArgumentError)
99
+ end
100
+ it 'takes strict argument' do
101
+ described_class.new(strictly_valid_druid_str, fixture_dir, true)
102
+ expect { described_class.new(druid_str, fixture_dir, true) }.to raise_error(ArgumentError)
103
+ end
104
+ end
105
+
106
+ it '#tree builds a druid tree from a druid' do
107
+ druid = described_class.new(druid_str, fixture_dir)
108
+ expect(druid.tree).to eq(%w[cd 456 ef 7890 cd456ef7890])
109
+ expect(druid.path).to eq(tree1)
110
+ end
111
+
112
+ it '#mkdir, #rmdir create and destroy druid directories' do
113
+ expect(File.exist?(tree1)).to eq false
114
+ expect(File.exist?(tree2)).to eq false
115
+
116
+ druid1 = described_class.new(druid_str, fixture_dir)
117
+ druid2 = described_class.new(strictly_valid_druid_str, fixture_dir)
118
+
119
+ druid1.mkdir
120
+ expect(File.exist?(tree1)).to eq true
121
+ expect(File.exist?(tree2)).to eq false
122
+
123
+ druid2.mkdir
124
+ expect(File.exist?(tree1)).to eq true
125
+ expect(File.exist?(tree2)).to eq true
126
+
127
+ druid2.rmdir
128
+ expect(File.exist?(tree1)).to eq true
129
+ expect(File.exist?(tree2)).to eq false
130
+
131
+ druid1.rmdir
132
+ expect(File.exist?(tree1)).to eq false
133
+ expect(File.exist?(tree2)).to eq false
134
+ expect(File.exist?(File.join(fixture_dir, 'cd'))).to eq false
135
+ end
136
+
137
+ describe 'alternate prefixes' do
138
+ before :all do
139
+ described_class.prefix = 'sulair'
140
+ end
141
+
142
+ after :all do
143
+ described_class.prefix = 'druid'
144
+ end
145
+
146
+ it 'handles alternate prefixes' do
147
+ expect { described_class.new('druid:cd456ef7890', fixture_dir) }.to raise_error(ArgumentError)
148
+ expect(described_class.new('sulair:cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
149
+ expect(described_class.new('cd456ef7890', fixture_dir).druid).to eq('sulair:cd456ef7890')
150
+ end
151
+ end
152
+
153
+ describe 'content directories' do
154
+ it 'knows where its content goes' do
155
+ druid = described_class.new(druid_str, fixture_dir)
156
+ expect(druid.content_dir(false)).to eq(File.join(tree1, 'content'))
157
+ expect(druid.metadata_dir(false)).to eq(File.join(tree1, 'metadata'))
158
+ expect(druid.temp_dir(false)).to eq(File.join(tree1, 'temp'))
159
+
160
+ expect(File.exist?(File.join(tree1, 'content'))).to eq false
161
+ expect(File.exist?(File.join(tree1, 'metadata'))).to eq false
162
+ expect(File.exist?(File.join(tree1, 'temp'))).to eq false
163
+ end
164
+
165
+ it 'creates its content directories on the fly' do
166
+ druid = described_class.new(druid_str, fixture_dir)
167
+ expect(druid.content_dir).to eq(File.join(tree1, 'content'))
168
+ expect(druid.metadata_dir).to eq(File.join(tree1, 'metadata'))
169
+ expect(druid.temp_dir).to eq(File.join(tree1, 'temp'))
170
+
171
+ expect(File.exist?(File.join(tree1, 'content'))).to eq true
172
+ expect(File.exist?(File.join(tree1, 'metadata'))).to eq true
173
+ expect(File.exist?(File.join(tree1, 'temp'))).to eq true
174
+ end
175
+
176
+ it 'matches glob' do
177
+ druid = described_class.new(druid_str, fixture_dir)
178
+ druid.mkdir
179
+ expect(Dir.glob(File.join(File.dirname(druid.path), described_class.glob)).size).to eq(1)
180
+ end
181
+ it 'matches strict_glob' do
182
+ druid = described_class.new(druid_str, fixture_dir)
183
+ druid.mkdir
184
+ expect(Dir.glob(File.join(File.dirname(druid.path), described_class.strict_glob)).size).to eq(0)
185
+ druid = described_class.new(strictly_valid_druid_str, fixture_dir)
186
+ druid.mkdir
187
+ expect(Dir.glob(File.join(File.dirname(druid.path), described_class.strict_glob)).size).to eq(1)
188
+ end
189
+ end
190
+
191
+ describe 'content discovery' do
192
+ let(:druid) { described_class.new(druid_str, fixture_dir) }
193
+ let(:filelist) { %w[1 2 3 4].collect { |num| "someFile#{num}" } }
194
+
195
+ it 'finds content in content directories' do
196
+ location = druid.content_dir
197
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
198
+ expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
199
+ end
200
+
201
+ it 'finds content in the root directory' do
202
+ location = druid.path(nil, true)
203
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
204
+ expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
205
+ end
206
+
207
+ it 'finds content in the leaf directory' do
208
+ location = File.expand_path('..', druid.path(nil, true))
209
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
210
+ expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
211
+ end
212
+
213
+ it 'does not find content in the wrong content directory' do
214
+ location = druid.metadata_dir
215
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
216
+ expect(druid.find_content('someContent')).to be_nil
217
+ end
218
+
219
+ it 'does not find content in a higher-up directory' do
220
+ location = File.expand_path('../..', druid.path(nil, true))
221
+ File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
222
+ expect(druid.find_content('someContent')).to be_nil
223
+ end
224
+
225
+ it 'finds a filelist in the content directory' do
226
+ location = Pathname(druid.content_dir)
227
+ filelist.each do |filename|
228
+ location.join(filename).open('w') { |f| f.write "This is #{filename}" }
229
+ end
230
+ expect(druid.find_filelist_parent('content', filelist)).to eq(location)
231
+ end
232
+
233
+ it 'finds a filelist in the root directory' do
234
+ location = Pathname(druid.path(nil, true))
235
+ filelist.each do |filename|
236
+ location.join(filename).open('w') { |f| f.write "This is #{filename}" }
237
+ end
238
+ expect(druid.find_filelist_parent('content', filelist)).to eq(location)
239
+ end
240
+
241
+ it 'finds a filelist in the leaf directory' do
242
+ location = Pathname(File.expand_path('..', druid.path(nil, true)))
243
+ filelist.each do |filename|
244
+ location.join(filename).open('w') { |f| f.write "This is #{filename}" }
245
+ end
246
+ expect(druid.find_filelist_parent('content', filelist)).to eq(location)
247
+ end
248
+
249
+ it 'raises an exception if the first file in the filelist is not found' do
250
+ Pathname(druid.content_dir)
251
+ expect { druid.find_filelist_parent('content', filelist) }.to raise_exception(/content dir not found for 'someFile1' when searching/)
252
+ end
253
+
254
+ it 'raises an exception if any other file in the filelist is not found' do
255
+ location = Pathname(druid.content_dir)
256
+ location.join(filelist.first).open('w') { |f| f.write "This is #{filelist.first}" }
257
+ expect { druid.find_filelist_parent('content', filelist) }.to raise_exception(/File 'someFile2' not found/)
258
+ end
259
+ end
260
+
261
+ describe '#mkdir error handling' do
262
+ it 'raises SameContentExistsError if the directory already exists' do
263
+ druid_obj = described_class.new(strictly_valid_druid_str, fixture_dir)
264
+ druid_obj.mkdir
265
+ expect { druid_obj.mkdir }.to raise_error(DruidTools::SameContentExistsError)
266
+ end
267
+
268
+ it 'raises DifferentContentExistsError if a link already exists in the workspace for this druid' do
269
+ source_dir = '/tmp/content_dir'
270
+ FileUtils.mkdir_p(source_dir)
271
+ dr = described_class.new(strictly_valid_druid_str, fixture_dir)
272
+ dr.mkdir_with_final_link(source_dir)
273
+ expect { dr.mkdir }.to raise_error(DruidTools::DifferentContentExistsError)
274
+ end
275
+ end
276
+
277
+ describe '#mkdir_with_final_link' do
278
+ let(:source_dir) { '/tmp/content_dir' }
279
+ let(:druid_obj) { described_class.new(strictly_valid_druid_str, fixture_dir) }
280
+
281
+ before do
282
+ FileUtils.mkdir_p(source_dir)
283
+ end
284
+
285
+ it 'creates a druid tree in the workspace with the final directory being a link to the passed in source' do
286
+ druid_obj.mkdir_with_final_link(source_dir)
287
+ expect(File).to be_symlink(druid_obj.path)
288
+ expect(File.readlink(tree2)).to eq(source_dir)
289
+ end
290
+
291
+ it 'does not error out if the link to source already exists' do
292
+ druid_obj.mkdir_with_final_link(source_dir)
293
+ expect(File).to be_symlink(druid_obj.path)
294
+ expect(File.readlink(tree2)).to eq(source_dir)
295
+ end
296
+
297
+ it 'raises DifferentContentExistsError if a directory already exists in the workspace for this druid' do
298
+ druid_obj.mkdir(fixture_dir)
299
+ expect { druid_obj.mkdir_with_final_link(source_dir) }.to raise_error(DruidTools::DifferentContentExistsError)
300
+ end
301
+ end
302
+
303
+ describe '#prune!' do
304
+ let(:workspace) { Dir.mktmpdir }
305
+ let(:dr1) { described_class.new(druid_str, workspace) }
306
+ let(:dr2) { described_class.new(strictly_valid_druid_str, workspace) }
307
+ let(:pathname1) { dr1.pathname }
308
+
309
+ after do
310
+ FileUtils.remove_entry workspace
311
+ end
312
+
313
+ context 'when there is a shared ancestor' do
314
+ before do
315
+ # Nil the create records for this context because we're in a known read only one
316
+ dr1.mkdir
317
+ dr2.mkdir
318
+ dr1.prune!
319
+ end
320
+
321
+ it 'deletes the outermost directory' do
322
+ expect(File).not_to exist(dr1.path)
323
+ end
324
+
325
+ it 'deletes empty ancestor directories' do
326
+ expect(File).not_to exist(pathname1.parent)
327
+ expect(File).not_to exist(pathname1.parent.parent)
328
+ end
329
+
330
+ it 'stops at ancestor directories that have children' do
331
+ # 'cd/456' should still exist because of druid2
332
+ shared_ancestor = pathname1.parent.parent.parent
333
+ expect(shared_ancestor.to_s).to match(%r{cd/456$})
334
+ expect(File).to exist(shared_ancestor)
335
+ end
336
+ end
337
+
338
+ it 'removes all directories up to the base path when there are no common ancestors' do
339
+ # Nil the create records for this test
340
+ dr1.mkdir
341
+ dr1.prune!
342
+ expect(File).not_to exist(File.join(workspace, 'cd'))
343
+ expect(File).to exist(workspace)
344
+ end
345
+
346
+ it 'removes directories with symlinks' do
347
+ # Nil the create records for this test
348
+ source_dir = File.join workspace, 'src_dir'
349
+ FileUtils.mkdir_p(source_dir)
350
+ dr2.mkdir_with_final_link(source_dir)
351
+ dr2.prune!
352
+ expect(File).not_to exist(dr2.path)
353
+ expect(File).not_to exist(File.join(workspace, 'cd'))
354
+ end
355
+ end
356
+ end