druid-tools 0.4.1 → 1.0.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.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.rubocop.yml +41 -0
- data/.rubocop_todo.yml +372 -0
- data/.travis.yml +12 -2
- data/README.md +36 -16
- data/Rakefile +4 -1
- data/VERSION +1 -1
- data/druid-tools.gemspec +4 -1
- data/lib/druid_tools/druid.rb +19 -6
- data/spec/druid_spec.rb +470 -0
- data/spec/spec_helper.rb +9 -0
- metadata +49 -36
- data/doc/classes/DruidTools.html +0 -132
- data/doc/classes/DruidTools/DifferentContentExistsError.html +0 -111
- data/doc/classes/DruidTools/Druid.html +0 -352
- data/doc/classes/DruidTools/Druid.src/M000001.html +0 -18
- data/doc/classes/DruidTools/Druid.src/M000002.html +0 -18
- data/doc/classes/DruidTools/Druid.src/M000003.html +0 -18
- data/doc/classes/DruidTools/Druid.src/M000004.html +0 -24
- data/doc/classes/DruidTools/Druid.src/M000005.html +0 -18
- data/doc/classes/DruidTools/Druid.src/M000006.html +0 -18
- data/doc/classes/DruidTools/Druid.src/M000007.html +0 -20
- data/doc/classes/DruidTools/Druid.src/M000008.html +0 -25
- data/doc/classes/DruidTools/Druid.src/M000009.html +0 -20
- data/doc/classes/DruidTools/Druid.src/M000010.html +0 -27
- data/doc/classes/DruidTools/Druid.src/M000011.html +0 -24
- data/doc/classes/DruidTools/Druid.src/M000012.html +0 -29
- data/doc/classes/DruidTools/InvalidDruidError.html +0 -111
- data/doc/classes/DruidTools/SameContentExistsError.html +0 -111
- data/doc/created.rid +0 -1
- data/doc/files/lib/druid-tools_rb.html +0 -108
- data/doc/files/lib/druid_tools/druid_rb.html +0 -108
- data/doc/files/lib/druid_tools/exceptions_rb.html +0 -101
- data/doc/files/lib/druid_tools/version_rb.html +0 -101
- data/doc/files/lib/druid_tools_rb.html +0 -110
- data/doc/files/spec/druid_tools_spec_rb.html +0 -115
- data/doc/files/spec/spec_helper_rb.html +0 -114
- data/doc/fr_class_index.html +0 -28
- data/doc/fr_file_index.html +0 -30
- data/doc/fr_method_index.html +0 -38
- data/doc/index.html +0 -24
- data/doc/rdoc-style.css +0 -208
- data/spec/druid_tools_spec.rb +0 -426
data/README.md
CHANGED
@@ -1,9 +1,35 @@
|
|
1
|
+
[](https://travis-ci.org/sul-dlss/druid-tools)
|
2
|
+
[](https://coveralls.io/github/sul-dlss/druid-tools?branch=master)
|
3
|
+
[](https://gemnasium.com/github.com/sul-dlss/druid-tools)
|
4
|
+
[](https://badge.fury.io/rb/druid-tools)
|
5
|
+
|
1
6
|
# Druid::Tools
|
2
7
|
|
3
8
|
Tools to manipulate DRUID trees and content directories
|
4
9
|
|
10
|
+
Note that druid syntax is defined in consul (and druids are issued by the SURI service). See https://consul.stanford.edu/pages/viewpage.action?title=SURI+2.0+Specification&spaceKey=chimera
|
11
|
+
|
12
|
+
Druid format:
|
13
|
+
|
14
|
+
bbdddbbdddd (two letters two digits two letters 4 digits)
|
15
|
+
|
16
|
+
Letters must be lowercase, and must not include A, E, I, O, U or L. (capitals for easier distinction here)
|
17
|
+
We often use vowels in our test data, and this code base has allowed vowels historically (though not
|
18
|
+
uppercase). We now recommend setting the strict argument to true whenever using this code to build
|
19
|
+
DruidTools::Druid objects or to validate druid identifier strings.
|
20
|
+
|
5
21
|
## Usage
|
6
22
|
|
23
|
+
### with strict argument
|
24
|
+
|
25
|
+
```ruby
|
26
|
+
d = DruidTools::Druid.new('druid:ab123cd4567', '/dor/workspace', true) # no aeioul
|
27
|
+
=> ArgumentError: Invalid DRUID: 'druid:ab123cd4567'
|
28
|
+
d = DruidTools::Druid.new('druid:bb123cd4567', '/dor/workspace', true)
|
29
|
+
d.druid
|
30
|
+
=> "druid:bb123cd4567"
|
31
|
+
```
|
32
|
+
|
7
33
|
### Get attributes and paths
|
8
34
|
|
9
35
|
```ruby
|
@@ -27,6 +53,13 @@ d = DruidTools::Druid.valid?('druid:ab123cd4567')
|
|
27
53
|
=> true
|
28
54
|
d = DruidTools::Druid.valid?('blah')
|
29
55
|
=> false
|
56
|
+
d = DruidTools::Druid.valid?('druid:ab123cd4567', true) # strict validation: no aeioul
|
57
|
+
=> false
|
58
|
+
d = DruidTools::Druid.valid?('druid:ab123cd4567', false)
|
59
|
+
=> true
|
60
|
+
d = DruidTools::Druid.valid?('druid:bb123cd4567', true)
|
61
|
+
=> true
|
62
|
+
|
30
63
|
```
|
31
64
|
|
32
65
|
### Manipulate directories and symlinks
|
@@ -80,9 +113,9 @@ d.find_content('this/file/does/not/exist.jpg')
|
|
80
113
|
### Pruning: removes leaves of tree up to non-empty branches
|
81
114
|
|
82
115
|
```ruby
|
83
|
-
d1 = DruidTools::Druid.new
|
116
|
+
d1 = DruidTools::Druid.new('druid:cd456ef7890', '/workspace')
|
84
117
|
d1.mkdir
|
85
|
-
d2 = DruidTools::Druid.new
|
118
|
+
d2 = DruidTools::Druid.new('druid:cd456gh1234', '/workspace')
|
86
119
|
d2.mkdir
|
87
120
|
|
88
121
|
# /workspace/cd/456/gh/1234/cd456gh1234 pruned down to /workspace/cd/456
|
@@ -93,22 +126,9 @@ d2.prune!
|
|
93
126
|
### Stacks and Purl compatible Druid. All files at the leaf directories
|
94
127
|
|
95
128
|
```ruby
|
96
|
-
pd = DruidTools::PurlDruid.new
|
129
|
+
pd = DruidTools::PurlDruid.new('druid:ab123cd4567', '/purl')
|
97
130
|
pd.path
|
98
131
|
=> "/purl/ab/123/cd/4567"
|
99
132
|
pd.content_dir
|
100
133
|
=> "/purl/ab/123/cd/4567"
|
101
134
|
```
|
102
|
-
|
103
|
-
### History
|
104
|
-
|
105
|
-
- <b>0.3.0</b> - Added #prune method. Added AccessDruid for stacks and purl access
|
106
|
-
- <b>0.2.6</b> - Fixed VERSION warning message, and documentation cleanup
|
107
|
-
- <b>0.2.5</b> - Added glob pattern as DruidTools::Druid.glob
|
108
|
-
- <b>0.2.4</b> - Allow non-String as .new parameter and added InvalidDruidError
|
109
|
-
- <b>0.2.3</b> - Fine tune behavior of find_filelist_parent
|
110
|
-
- <b>0.2.2</b> - Added find_filelist_parent method allowing search for a set of files
|
111
|
-
- <b>0.2.1</b> - Do not error out during symlink creation if it already exists
|
112
|
-
- <b>0.2.0</b> - Added DruidTools::Druid.valid?
|
113
|
-
- <b>0.1.0</b> - Additional support for alternate content locations
|
114
|
-
- <b>0.0.1</b> - Initial Release
|
data/Rakefile
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
1.0.0
|
data/druid-tools.gemspec
CHANGED
@@ -15,7 +15,10 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.name = 'druid-tools'
|
16
16
|
gem.require_paths = ['lib']
|
17
17
|
gem.version = File.read('VERSION').strip
|
18
|
-
|
18
|
+
|
19
19
|
gem.add_development_dependency 'rake', '>= 10.1.0'
|
20
20
|
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
|
21
24
|
end
|
data/lib/druid_tools/druid.rb
CHANGED
@@ -6,11 +6,17 @@ module DruidTools
|
|
6
6
|
@@deletes_directory_name = '.deletes'
|
7
7
|
attr_accessor :druid, :base
|
8
8
|
|
9
|
+
# See https://consul.stanford.edu/pages/viewpage.action?title=SURI+2.0+Specification&spaceKey=chimera
|
10
|
+
# 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
|
+
|
9
13
|
class << self
|
10
14
|
attr_accessor :prefix
|
11
15
|
|
16
|
+
# @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
|
12
17
|
# @return [Regexp] matches druid:aa111aa1111 or aa111aa1111
|
13
|
-
def pattern
|
18
|
+
def pattern(strict=false)
|
19
|
+
return /\A(?:#{self.prefix}:)?(#{STRICT_LET}{2})(\d{3})(#{STRICT_LET}{2})(\d{4})\z/ if strict
|
14
20
|
/\A(?:#{self.prefix}:)?([a-z]{2})(\d{3})([a-z]{2})(\d{4})\z/
|
15
21
|
end
|
16
22
|
|
@@ -19,10 +25,16 @@ module DruidTools
|
|
19
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]"
|
20
26
|
end
|
21
27
|
|
28
|
+
# @return [String] suitable for use in [Dir#glob]
|
29
|
+
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]"
|
31
|
+
end
|
32
|
+
|
22
33
|
# @param [String] druid id
|
34
|
+
# @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
|
23
35
|
# @return [Boolean] true if druid matches pattern; otherwise false
|
24
|
-
def valid?(druid)
|
25
|
-
|
36
|
+
def valid?(druid, strict=false)
|
37
|
+
druid =~ pattern(strict) ? true : false
|
26
38
|
end
|
27
39
|
|
28
40
|
end
|
@@ -41,13 +53,14 @@ module DruidTools
|
|
41
53
|
end
|
42
54
|
|
43
55
|
# @param druid [String] A valid druid
|
56
|
+
# @param [boolean] true if validation should be more restrictive about allowed letters (no aeioul)
|
44
57
|
# @param base [String] The directory used by #path
|
45
|
-
def initialize(druid, base='.')
|
58
|
+
def initialize(druid, base='.', strict=false)
|
46
59
|
druid = druid.to_s unless druid.is_a? String
|
47
|
-
unless self.class.valid?(druid)
|
60
|
+
unless self.class.valid?(druid, strict)
|
48
61
|
raise ArgumentError, "Invalid DRUID: '#{druid}'"
|
49
62
|
end
|
50
|
-
druid = [self.class.prefix,druid].join(':') unless druid =~ /^#{self.class.prefix}:/
|
63
|
+
druid = [self.class.prefix, druid].join(':') unless druid =~ /^#{self.class.prefix}:/
|
51
64
|
@base = base
|
52
65
|
@druid = druid
|
53
66
|
end
|
data/spec/druid_spec.rb
ADDED
@@ -0,0 +1,470 @@
|
|
1
|
+
describe DruidTools::Druid do
|
2
|
+
let(:fixture_dir) { File.expand_path("../fixtures", __FILE__) }
|
3
|
+
let(:druid_str) { 'druid:cd456ef7890' }
|
4
|
+
let(:tree_1) { File.join(fixture_dir, 'cd/456/ef/7890/cd456ef7890') }
|
5
|
+
let(:strictly_valid_druid_str) { 'druid:cd456gh1234' }
|
6
|
+
let(:tree_2) { File.join(fixture_dir, 'cd/456/gh/1234/cd456gh1234') }
|
7
|
+
|
8
|
+
after(:each) do
|
9
|
+
FileUtils.rm_rf(File.join(fixture_dir, 'cd'))
|
10
|
+
end
|
11
|
+
|
12
|
+
context '.valid?' do
|
13
|
+
# also tests .pattern
|
14
|
+
it "correctly validates druid strings" do
|
15
|
+
tests = [
|
16
|
+
# Expected Input druid
|
17
|
+
[true, 'druid:aa000bb0001'],
|
18
|
+
[true, 'aa000bb0001'],
|
19
|
+
[false, 'Aa000bb0001'],
|
20
|
+
[false, "xxx\naa000bb0001"],
|
21
|
+
[false, 'aaa000bb0001'],
|
22
|
+
[false, 'druidX:aa000bb0001'],
|
23
|
+
[false, ':aa000bb0001'],
|
24
|
+
[true, 'aa123bb1234'],
|
25
|
+
[false, 'aa12bb1234'],
|
26
|
+
[false, 'aa1234bb1234'],
|
27
|
+
[false, 'aa123bb123'],
|
28
|
+
[false, 'aa123bb12345'],
|
29
|
+
[false, 'a123bb1234'],
|
30
|
+
[false, 'aaa123bb1234'],
|
31
|
+
[false, 'aa123b1234'],
|
32
|
+
[false, 'aa123bbb1234'],
|
33
|
+
[false, 'druid:az918AZ9381'.upcase],
|
34
|
+
[true, 'druid:az918AZ9381'.downcase],
|
35
|
+
[true, 'druid:zz943vx1492']
|
36
|
+
]
|
37
|
+
tests.each do |exp, dru|
|
38
|
+
expect(DruidTools::Druid.valid?(dru)).to eq(exp)
|
39
|
+
expect(DruidTools::Druid.valid?(dru, false)).to eq(exp)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
context 'strict' do
|
43
|
+
it "correctly validates druid strings" do
|
44
|
+
tests = [
|
45
|
+
# Expected Input druid
|
46
|
+
[false, 'aa000aa0000'],
|
47
|
+
[false, 'ee000ee0000'],
|
48
|
+
[false, 'ii000ii0000'],
|
49
|
+
[false, 'oo000oo0000'],
|
50
|
+
[false, 'uu000uu0000'],
|
51
|
+
[false, 'll000ll0000'],
|
52
|
+
[false, 'aa000bb0001'],
|
53
|
+
[true, 'druid:dd000bb0001'],
|
54
|
+
[false, 'druid:aa000bb0001'],
|
55
|
+
[true, 'dd000bb0001'],
|
56
|
+
[false, 'Dd000bb0001'],
|
57
|
+
[false, "xxx\ndd000bb0001"],
|
58
|
+
[false, 'ddd000bb0001'],
|
59
|
+
[false, 'druidX:dd000bb0001'],
|
60
|
+
[false, ':dd000bb0001'],
|
61
|
+
[true, 'cc123bb1234'],
|
62
|
+
[false, 'aa123bb1234'],
|
63
|
+
[false, 'dd12bb1234'],
|
64
|
+
[false, 'dd1234bb1234'],
|
65
|
+
[false, 'dd123bb123'],
|
66
|
+
[false, 'dd123bb12345'],
|
67
|
+
[false, 'd123bb1234'],
|
68
|
+
[false, 'ddd123bb1234'],
|
69
|
+
[false, 'dd123b1234'],
|
70
|
+
[false, 'dd123bbb1234'],
|
71
|
+
[false, 'druid:bz918BZ9381'.upcase],
|
72
|
+
[true, 'druid:bz918BZ9381'.downcase],
|
73
|
+
[false, 'druid:az918AZ9381'.downcase],
|
74
|
+
[true, 'druid:zz943vx1492']
|
75
|
+
]
|
76
|
+
tests.each do |exp, dru|
|
77
|
+
expect(DruidTools::Druid.valid?(dru, true)).to eq(exp)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "#druid provides the full druid including the prefix" do
|
84
|
+
expect(DruidTools::Druid.new('druid:cd456ef7890', fixture_dir).druid).to eq('druid:cd456ef7890')
|
85
|
+
expect(DruidTools::Druid.new('cd456ef7890', fixture_dir).druid).to eq('druid:cd456ef7890')
|
86
|
+
end
|
87
|
+
|
88
|
+
it "#id extracts the ID from the stem" do
|
89
|
+
expect(DruidTools::Druid.new('druid:cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
|
90
|
+
expect(DruidTools::Druid.new('cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
|
91
|
+
end
|
92
|
+
|
93
|
+
context '#new' do
|
94
|
+
it "raises exception if the druid is invalid" do
|
95
|
+
expect { DruidTools::Druid.new('nondruid:cd456ef7890', fixture_dir) }.to raise_error(ArgumentError)
|
96
|
+
expect { DruidTools::Druid.new('druid:cd4567ef890', fixture_dir) }.to raise_error(ArgumentError)
|
97
|
+
end
|
98
|
+
it "takes strict argument" do
|
99
|
+
DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir, true)
|
100
|
+
expect { DruidTools::Druid.new(druid_str, fixture_dir, true) }.to raise_error(ArgumentError)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
it "#tree builds a druid tree from a druid" do
|
105
|
+
druid = DruidTools::Druid.new(druid_str, fixture_dir)
|
106
|
+
expect(druid.tree).to eq(['cd', '456', 'ef', '7890', 'cd456ef7890'])
|
107
|
+
expect(druid.path).to eq(tree_1)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "#mkdir, #rmdir create and destroy druid directories" do
|
111
|
+
expect(File.exists?(tree_1)).to eq false
|
112
|
+
expect(File.exists?(tree_2)).to eq false
|
113
|
+
|
114
|
+
druid_1 = DruidTools::Druid.new(druid_str, fixture_dir)
|
115
|
+
druid_2 = DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir)
|
116
|
+
|
117
|
+
druid_1.mkdir
|
118
|
+
expect(File.exists?(tree_1)).to eq true
|
119
|
+
expect(File.exists?(tree_2)).to eq false
|
120
|
+
|
121
|
+
druid_2.mkdir
|
122
|
+
expect(File.exists?(tree_1)).to eq true
|
123
|
+
expect(File.exists?(tree_2)).to eq true
|
124
|
+
|
125
|
+
druid_2.rmdir
|
126
|
+
expect(File.exists?(tree_1)).to eq true
|
127
|
+
expect(File.exists?(tree_2)).to eq false
|
128
|
+
|
129
|
+
druid_1.rmdir
|
130
|
+
expect(File.exists?(tree_1)).to eq false
|
131
|
+
expect(File.exists?(tree_2)).to eq false
|
132
|
+
expect(File.exists?(File.join(fixture_dir, 'cd'))).to eq false
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "alternate prefixes" do
|
136
|
+
before :all do
|
137
|
+
DruidTools::Druid.prefix = 'sulair'
|
138
|
+
end
|
139
|
+
|
140
|
+
after :all do
|
141
|
+
DruidTools::Druid.prefix = 'druid'
|
142
|
+
end
|
143
|
+
|
144
|
+
it "handles alternate prefixes" do
|
145
|
+
expect { DruidTools::Druid.new('druid:cd456ef7890', fixture_dir) }.to raise_error(ArgumentError)
|
146
|
+
expect(DruidTools::Druid.new('sulair:cd456ef7890', fixture_dir).id).to eq('cd456ef7890')
|
147
|
+
expect(DruidTools::Druid.new('cd456ef7890', fixture_dir).druid).to eq('sulair:cd456ef7890')
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
describe "content directories" do
|
152
|
+
it "knows where its content goes" do
|
153
|
+
druid = DruidTools::Druid.new(druid_str, fixture_dir)
|
154
|
+
expect(druid.content_dir(false)).to eq(File.join(tree_1, 'content'))
|
155
|
+
expect(druid.metadata_dir(false)).to eq(File.join(tree_1, 'metadata'))
|
156
|
+
expect(druid.temp_dir(false)).to eq(File.join(tree_1, 'temp'))
|
157
|
+
|
158
|
+
expect(File.exists?(File.join(tree_1, 'content'))).to eq false
|
159
|
+
expect(File.exists?(File.join(tree_1, 'metadata'))).to eq false
|
160
|
+
expect(File.exists?(File.join(tree_1, 'temp'))).to eq false
|
161
|
+
end
|
162
|
+
|
163
|
+
it "creates its content directories on the fly" do
|
164
|
+
druid = DruidTools::Druid.new(druid_str, fixture_dir)
|
165
|
+
expect(druid.content_dir).to eq(File.join(tree_1, 'content'))
|
166
|
+
expect(druid.metadata_dir).to eq(File.join(tree_1, 'metadata'))
|
167
|
+
expect(druid.temp_dir).to eq(File.join(tree_1, 'temp'))
|
168
|
+
|
169
|
+
expect(File.exists?(File.join(tree_1, 'content'))).to eq true
|
170
|
+
expect(File.exists?(File.join(tree_1, 'metadata'))).to eq true
|
171
|
+
expect(File.exists?(File.join(tree_1, 'temp'))).to eq true
|
172
|
+
end
|
173
|
+
|
174
|
+
it "matches glob" do
|
175
|
+
druid = DruidTools::Druid.new(druid_str, fixture_dir)
|
176
|
+
druid.mkdir
|
177
|
+
expect(Dir.glob(File.join(File.dirname(druid.path), DruidTools::Druid::glob)).size).to eq(1)
|
178
|
+
end
|
179
|
+
it "matches strict_glob" do
|
180
|
+
druid = DruidTools::Druid.new(druid_str, fixture_dir)
|
181
|
+
druid.mkdir
|
182
|
+
expect(Dir.glob(File.join(File.dirname(druid.path), DruidTools::Druid::strict_glob)).size).to eq(0)
|
183
|
+
druid = DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir)
|
184
|
+
druid.mkdir
|
185
|
+
expect(Dir.glob(File.join(File.dirname(druid.path), DruidTools::Druid::strict_glob)).size).to eq(1)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
describe "content discovery" do
|
190
|
+
let(:druid) { DruidTools::Druid.new(druid_str, fixture_dir) }
|
191
|
+
let(:filelist) { %w(1 2 3 4).collect { |num| "someFile#{num}" } }
|
192
|
+
|
193
|
+
it "finds content in content directories" do
|
194
|
+
location = druid.content_dir
|
195
|
+
File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
|
196
|
+
expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
|
197
|
+
end
|
198
|
+
|
199
|
+
it "finds content in the root directory" do
|
200
|
+
location = druid.path(nil, true)
|
201
|
+
File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
|
202
|
+
expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
|
203
|
+
end
|
204
|
+
|
205
|
+
it "finds content in the leaf directory" do
|
206
|
+
location = File.expand_path('..', druid.path(nil, true))
|
207
|
+
File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
|
208
|
+
expect(druid.find_content('someContent')).to eq(File.join(location, 'someContent'))
|
209
|
+
end
|
210
|
+
|
211
|
+
it "does not find content in the wrong content directory" do
|
212
|
+
location = druid.metadata_dir
|
213
|
+
File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
|
214
|
+
expect(druid.find_content('someContent')).to be_nil
|
215
|
+
end
|
216
|
+
|
217
|
+
it "does not find content in a higher-up directory" do
|
218
|
+
location = File.expand_path('../..', druid.path(nil, true))
|
219
|
+
File.open(File.join(location, 'someContent'), 'w') { |f| f.write 'This is the content' }
|
220
|
+
expect(druid.find_content('someContent')).to be_nil
|
221
|
+
end
|
222
|
+
|
223
|
+
it "finds a filelist in the content directory" do
|
224
|
+
location = Pathname(druid.content_dir)
|
225
|
+
filelist.each do |filename|
|
226
|
+
location.join(filename).open('w') { |f| f.write "This is #{filename}" }
|
227
|
+
end
|
228
|
+
expect(druid.find_filelist_parent('content', filelist)).to eq(location)
|
229
|
+
end
|
230
|
+
|
231
|
+
it "finds a filelist in the root directory" do
|
232
|
+
location = Pathname(druid.path(nil, true))
|
233
|
+
filelist.each do |filename|
|
234
|
+
location.join(filename).open('w') { |f| f.write "This is #{filename}" }
|
235
|
+
end
|
236
|
+
expect(druid.find_filelist_parent('content', filelist)).to eq(location)
|
237
|
+
end
|
238
|
+
|
239
|
+
it "finds a filelist in the leaf directory" do
|
240
|
+
location = Pathname(File.expand_path('..', druid.path(nil, true)))
|
241
|
+
filelist.each do |filename|
|
242
|
+
location.join(filename).open('w') { |f| f.write "This is #{filename}" }
|
243
|
+
end
|
244
|
+
expect(druid.find_filelist_parent('content', filelist)).to eq(location)
|
245
|
+
end
|
246
|
+
|
247
|
+
it "raises an exception if the first file in the filelist is not found" do
|
248
|
+
location = Pathname(druid.content_dir)
|
249
|
+
expect{druid.find_filelist_parent('content', filelist)}.to raise_exception(/content dir not found for 'someFile1' when searching/)
|
250
|
+
end
|
251
|
+
|
252
|
+
it "raises an exception if any other file in the filelist is not found" do
|
253
|
+
location = Pathname(druid.content_dir)
|
254
|
+
location.join(filelist.first).open('w') { |f| f.write "This is #{filelist.first}" }
|
255
|
+
expect{druid.find_filelist_parent('content', filelist)}.to raise_exception(/File 'someFile2' not found/)
|
256
|
+
end
|
257
|
+
end
|
258
|
+
|
259
|
+
describe "#mkdir error handling" do
|
260
|
+
it "raises SameContentExistsError if the directory already exists" do
|
261
|
+
druid_obj = DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir)
|
262
|
+
druid_obj.mkdir
|
263
|
+
expect { druid_obj.mkdir }.to raise_error(DruidTools::SameContentExistsError)
|
264
|
+
end
|
265
|
+
|
266
|
+
it "raises DifferentContentExistsError if a link already exists in the workspace for this druid" do
|
267
|
+
source_dir = '/tmp/content_dir'
|
268
|
+
FileUtils.mkdir_p(source_dir)
|
269
|
+
dr = DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir)
|
270
|
+
dr.mkdir_with_final_link(source_dir)
|
271
|
+
expect { dr.mkdir }.to raise_error(DruidTools::DifferentContentExistsError)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
describe "#mkdir_with_final_link" do
|
276
|
+
let(:source_dir) { '/tmp/content_dir' }
|
277
|
+
let(:druid_obj){ DruidTools::Druid.new(strictly_valid_druid_str, fixture_dir) }
|
278
|
+
|
279
|
+
before(:each) do
|
280
|
+
FileUtils.mkdir_p(source_dir)
|
281
|
+
end
|
282
|
+
|
283
|
+
it "creates a druid tree in the workspace with the final directory being a link to the passed in source" do
|
284
|
+
druid_obj.mkdir_with_final_link(source_dir)
|
285
|
+
expect(File).to be_symlink(druid_obj.path)
|
286
|
+
expect(File.readlink(tree_2)).to eq(source_dir)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "does not error out if the link to source already exists" do
|
290
|
+
druid_obj.mkdir_with_final_link(source_dir)
|
291
|
+
expect(File).to be_symlink(druid_obj.path)
|
292
|
+
expect(File.readlink(tree_2)).to eq(source_dir)
|
293
|
+
end
|
294
|
+
|
295
|
+
it "raises DifferentContentExistsError if a directory already exists in the workspace for this druid" do
|
296
|
+
druid_obj.mkdir(fixture_dir)
|
297
|
+
expect { druid_obj.mkdir_with_final_link(source_dir) }.to raise_error(DruidTools::DifferentContentExistsError)
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
describe "#prune!" do
|
302
|
+
let(:workspace) { Dir.mktmpdir }
|
303
|
+
let(:dr1) { DruidTools::Druid.new(druid_str, workspace) }
|
304
|
+
let(:dr2) { DruidTools::Druid.new(strictly_valid_druid_str, workspace) }
|
305
|
+
let(:pathname1) { dr1.pathname }
|
306
|
+
|
307
|
+
after(:each) do
|
308
|
+
FileUtils.remove_entry workspace
|
309
|
+
end
|
310
|
+
|
311
|
+
it "throws error on misconfig when base dir cannot be created" do
|
312
|
+
dir = '/some/dir/that/does/not/exist' # we don't have permissions to create
|
313
|
+
dr0 = DruidTools::Druid.new(druid_str, dir)
|
314
|
+
expect {dr0.prune!}.to raise_error(StandardError)
|
315
|
+
expect(File).to_not exist(dir)
|
316
|
+
end
|
317
|
+
|
318
|
+
it "does not throw error when base can be created" do
|
319
|
+
subdir = File.join(Dir.mktmpdir, 'some', 'nonexistant', 'subdir') # but this one *can* be created
|
320
|
+
dr2 = DruidTools::Druid.new(strictly_valid_druid_str, subdir)
|
321
|
+
expect {dr2.prune!}.not_to raise_error()
|
322
|
+
expect(File).to exist(subdir)
|
323
|
+
end
|
324
|
+
|
325
|
+
context "shared ancestor" do
|
326
|
+
|
327
|
+
before(:each) do
|
328
|
+
#Nil the create records for this context because we're in a known read only one
|
329
|
+
dr1.mkdir
|
330
|
+
dr2.mkdir
|
331
|
+
dr1.prune!
|
332
|
+
end
|
333
|
+
|
334
|
+
it "deletes the outermost directory" do
|
335
|
+
expect(File).to_not exist(dr1.path)
|
336
|
+
end
|
337
|
+
|
338
|
+
it "deletes empty ancestor directories" do
|
339
|
+
expect(File).to_not exist(pathname1.parent)
|
340
|
+
expect(File).to_not exist(pathname1.parent.parent)
|
341
|
+
end
|
342
|
+
|
343
|
+
it "stops at ancestor directories that have children" do
|
344
|
+
# 'cd/456' should still exist because of druid2
|
345
|
+
shared_ancestor = pathname1.parent.parent.parent
|
346
|
+
expect(shared_ancestor.to_s).to match(/cd\/456$/)
|
347
|
+
expect(File).to exist(shared_ancestor)
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
it "removes all directories up to the base path when there are no common ancestors" do
|
352
|
+
#Make sure a delete record is not present
|
353
|
+
expect(dr1.deletes_record_exists?).to be_falsey
|
354
|
+
|
355
|
+
#Nil the create records for this test
|
356
|
+
dr1.mkdir
|
357
|
+
dr1.prune!
|
358
|
+
expect(File).to_not exist(File.join(workspace, 'cd'))
|
359
|
+
expect(File).to exist(workspace)
|
360
|
+
|
361
|
+
#Make sure a delete record was created
|
362
|
+
expect(dr1.deletes_dir_exists?).to be_truthy
|
363
|
+
expect(dr1.deletes_record_exists?).to be_truthy
|
364
|
+
end
|
365
|
+
|
366
|
+
it "removes directories with symlinks" do
|
367
|
+
#Make sure a delete record is not present
|
368
|
+
expect(dr2.deletes_record_exists?).to be_falsey
|
369
|
+
|
370
|
+
#Nil the create records for this test
|
371
|
+
source_dir = File.join workspace, 'src_dir'
|
372
|
+
FileUtils.mkdir_p(source_dir)
|
373
|
+
dr2.mkdir_with_final_link(source_dir)
|
374
|
+
dr2.prune!
|
375
|
+
expect(File).to_not exist(dr2.path)
|
376
|
+
expect(File).to_not exist(File.join(workspace, 'cd'))
|
377
|
+
|
378
|
+
#Make sure a delete record was created
|
379
|
+
expect(dr2.deletes_dir_exists?).to be_truthy
|
380
|
+
expect(dr2.deletes_record_exists?).to be_truthy
|
381
|
+
end
|
382
|
+
|
383
|
+
describe "logging deleted druids" do
|
384
|
+
|
385
|
+
#Purge any paths or delete records created in the test
|
386
|
+
after :each do
|
387
|
+
#Remove the .deletes dir to clean up
|
388
|
+
dr2.deletes_delete_record if dr2.deletes_record_exists?
|
389
|
+
FileUtils.rm_rf dr2.deletes_dir_pathname
|
390
|
+
end
|
391
|
+
|
392
|
+
it "returns the path to the .deletes directory as a Pathname" do
|
393
|
+
expect(dr2.deletes_dir_pathname.class).to eq(Pathname)
|
394
|
+
end
|
395
|
+
|
396
|
+
it "returns the path to the delete record for a druid as a Pathname" do
|
397
|
+
expect(dr2.deletes_record_pathname.class).to eq(Pathname)
|
398
|
+
end
|
399
|
+
|
400
|
+
it "returns the path to the delete record for a druid as top_level/.deletes/druid" do
|
401
|
+
expect(dr2.deletes_record_pathname.to_s).to eq("#{dr2.base}/.deletes/#{dr2.id}")
|
402
|
+
end
|
403
|
+
|
404
|
+
it "returns false when the .deletes dir is not present on the file system" do
|
405
|
+
expect(dr2.deletes_dir_exists?).to be_falsey
|
406
|
+
end
|
407
|
+
|
408
|
+
it "creates the .deletes dir and detect it exists" do
|
409
|
+
|
410
|
+
#Clean the .deletes dir if present
|
411
|
+
FileUtils.rm_rf dr2.deletes_dir_pathname
|
412
|
+
|
413
|
+
#Test for exists? and create
|
414
|
+
expect(dr2.deletes_dir_exists?).to be_falsey
|
415
|
+
dr2.create_deletes_dir
|
416
|
+
expect(dr2.deletes_dir_exists?).to be_truthy
|
417
|
+
end
|
418
|
+
|
419
|
+
it "returns false when the .deletes dir does not have a deleted record for a druid" do
|
420
|
+
expect(dr2.deletes_record_exists?).to be_falsey
|
421
|
+
end
|
422
|
+
|
423
|
+
it "creates a deleted record with a parent directory that has no .deletes directory and no deleted for the file and successfully create a delete record there" do
|
424
|
+
#Expect there not to be a .deletes dir or file (the file expectation is redundant I know)
|
425
|
+
expect(dr2.deletes_dir_exists?).to be_falsey
|
426
|
+
expect(dr2.deletes_record_exists?).to be_falsey
|
427
|
+
|
428
|
+
#Create the delete record
|
429
|
+
dr2.creates_delete_record
|
430
|
+
|
431
|
+
#Check to ensure items were created
|
432
|
+
expect(dr2.deletes_dir_exists?).to be_truthy
|
433
|
+
expect(dr2.deletes_record_exists?).to be_truthy
|
434
|
+
end
|
435
|
+
|
436
|
+
it "creates a delete record with a parent directory that has a .deletes directory that does not contain a delete record for this druid" do
|
437
|
+
#Expect there not to be a .deletes dir or file (the file expectation is redundant I know)
|
438
|
+
expect(dr2.deletes_dir_exists?).to be_falsey
|
439
|
+
expect(dr2.deletes_record_exists?).to be_falsey
|
440
|
+
|
441
|
+
#Creates the deletes dir and check
|
442
|
+
dr2.create_deletes_dir
|
443
|
+
expect(dr2.deletes_dir_exists?).to be_truthy
|
444
|
+
expect(dr2.deletes_record_exists?).to be_falsey
|
445
|
+
|
446
|
+
#Create the delete record
|
447
|
+
dr2.creates_delete_record
|
448
|
+
|
449
|
+
#Check to ensure items were created
|
450
|
+
expect(dr2.deletes_dir_exists?).to be_truthy
|
451
|
+
expect(dr2.deletes_record_exists?).to be_truthy
|
452
|
+
end
|
453
|
+
|
454
|
+
it "creates a delete record with a parent directory that does not have a .deletes directory and contains an older delete record" do
|
455
|
+
#Expect there not to be a .deletes dir or file (the file expectation is redundant I know)
|
456
|
+
expect(dr2.deletes_dir_exists?).to be_falsey
|
457
|
+
expect(dr2.deletes_record_exists?).to be_falsey
|
458
|
+
|
459
|
+
dr2.creates_delete_record
|
460
|
+
time = Time.now
|
461
|
+
expect(File.mtime(dr2.deletes_record_pathname)).to be <= time
|
462
|
+
sleep(1) #force a one second pause in case the machine is fast, since mtime only goes down to the second
|
463
|
+
|
464
|
+
dr2.creates_delete_record
|
465
|
+
#Should have a new newer deleted record
|
466
|
+
expect(File.mtime(dr2.deletes_record_pathname)).to be > time
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
end
|