audiothority 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +30 -0
- data/bin/audiothorian +8 -0
- data/lib/audiothority.rb +18 -0
- data/lib/audiothority/change.rb +68 -0
- data/lib/audiothority/cli.rb +82 -0
- data/lib/audiothority/crawler.rb +30 -0
- data/lib/audiothority/custodian.rb +26 -0
- data/lib/audiothority/enforcer.rb +60 -0
- data/lib/audiothority/extract.rb +30 -0
- data/lib/audiothority/inspector.rb +31 -0
- data/lib/audiothority/society.rb +18 -0
- data/lib/audiothority/summary.rb +32 -0
- data/lib/audiothority/tracker.rb +17 -0
- data/lib/audiothority/validation.rb +33 -0
- data/lib/audiothority/validators.rb +15 -0
- data/lib/audiothority/validators/album.rb +11 -0
- data/lib/audiothority/validators/artist.rb +11 -0
- data/lib/audiothority/validators/track.rb +16 -0
- data/lib/audiothority/validators/unique.rb +22 -0
- data/lib/audiothority/validators/year.rb +11 -0
- data/lib/audiothority/version.rb +5 -0
- data/spec/audiothority/crawler_spec.rb +62 -0
- data/spec/audiothority/custodian_spec.rb +62 -0
- data/spec/audiothority/extract_spec.rb +126 -0
- data/spec/audiothority/inspector_spec.rb +72 -0
- data/spec/audiothority/society_spec.rb +27 -0
- data/spec/audiothority/tracker_spec.rb +34 -0
- data/spec/audiothority/validators_spec.rb +134 -0
- data/spec/integration/enforce_integration_spec.rb +91 -0
- data/spec/integration/scan_integration_spec.rb +95 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/support/cli_setup.rb +47 -0
- data/spec/support/interactive.rb +30 -0
- metadata +119 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Audiothority
|
4
|
+
class Summary
|
5
|
+
def initialize(state)
|
6
|
+
@state = state
|
7
|
+
end
|
8
|
+
|
9
|
+
def display(console)
|
10
|
+
if @state.any?
|
11
|
+
@state.each do |path, violations|
|
12
|
+
console.say %(#{path} is inconsistent due to:)
|
13
|
+
violations.each do |violation|
|
14
|
+
checkmark = console.set_color(%( ✗ ), :red, :bold)
|
15
|
+
console.say(checkmark + violation.message)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
else
|
19
|
+
checkmark = console.set_color(%( ✓ ), :green, :bold)
|
20
|
+
console.say(checkmark + %(All is good))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class PathsOnlySummary < Summary
|
26
|
+
def display(console)
|
27
|
+
@state.each do |path, _|
|
28
|
+
console.say(File.expand_path(path))
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Audiothority
|
4
|
+
class Validation
|
5
|
+
def initialize(valid=true)
|
6
|
+
@valid = valid
|
7
|
+
end
|
8
|
+
|
9
|
+
def valid?
|
10
|
+
!!@valid
|
11
|
+
end
|
12
|
+
|
13
|
+
def invalid?
|
14
|
+
!valid?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Violation < Validation
|
19
|
+
attr_reader :field, :reason, :message
|
20
|
+
|
21
|
+
def initialize(field, reason, message, applicable=true)
|
22
|
+
super(false)
|
23
|
+
@field = field
|
24
|
+
@reason = reason
|
25
|
+
@message = message
|
26
|
+
@applicable = applicable
|
27
|
+
end
|
28
|
+
|
29
|
+
def applicable?
|
30
|
+
!!@applicable
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Audiothority
|
4
|
+
module Validators
|
5
|
+
def self.default
|
6
|
+
[Artist, Album, TrackNumber, Year].map(&:new)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'audiothority/validators/unique'
|
12
|
+
require 'audiothority/validators/album'
|
13
|
+
require 'audiothority/validators/artist'
|
14
|
+
require 'audiothority/validators/track'
|
15
|
+
require 'audiothority/validators/year'
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Audiothority
|
4
|
+
module Validators
|
5
|
+
class TrackNumber
|
6
|
+
def validate(tags)
|
7
|
+
missing = tags.map(&:track).select(&:zero?)
|
8
|
+
if missing.empty?
|
9
|
+
Validation.new
|
10
|
+
else
|
11
|
+
Violation.new(:track, :missing, 'track(s) without track numbers', false)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Audiothority
|
4
|
+
module Validators
|
5
|
+
class Unique
|
6
|
+
def initialize(thing)
|
7
|
+
@thing = thing
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate(tags)
|
11
|
+
items = tags.map { |t| t.send(@thing) }.uniq
|
12
|
+
if items.one?
|
13
|
+
Validation.new
|
14
|
+
elsif items.compact.empty?
|
15
|
+
Violation.new(@thing, :missing, %(missing #{@thing} field))
|
16
|
+
elsif items.size > 1
|
17
|
+
Violation.new(@thing, :multiple, %(multiple #{@thing}s: #{items.map(&:inspect).join(', ')}))
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Audiothority
|
7
|
+
describe Crawler do
|
8
|
+
let :crawler do
|
9
|
+
described_class.new([dir], blacklist)
|
10
|
+
end
|
11
|
+
|
12
|
+
let :dir do
|
13
|
+
double(:pathname)
|
14
|
+
end
|
15
|
+
|
16
|
+
let :blacklist do
|
17
|
+
[]
|
18
|
+
end
|
19
|
+
|
20
|
+
let :child do
|
21
|
+
double(:child)
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#crawl' do
|
25
|
+
it 'does not yield non-readable paths' do
|
26
|
+
non_readable = double(readable?: false)
|
27
|
+
allow(dir).to receive(:each_child).and_yield(non_readable)
|
28
|
+
expect { |b| crawler.crawl(&b) }.to_not yield_control
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not yield empty directories' do
|
32
|
+
empty_path = double(readable?: true, directory?: true, children: [])
|
33
|
+
allow(dir).to receive(:each_child).and_yield(empty_path)
|
34
|
+
expect { |b| crawler.crawl(&b) }.to_not yield_control
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'does not yield single files' do
|
38
|
+
single_file = double(readable?: true, directory?: false)
|
39
|
+
allow(dir).to receive(:each_child).and_yield(single_file)
|
40
|
+
expect { |b| crawler.crawl(&b) }.to_not yield_control
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'yields non-empty directories' do
|
44
|
+
valid_dir = double(readable?: true, directory?: true, children: ['non-empty'])
|
45
|
+
allow(dir).to receive(:each_child).and_yield(valid_dir)
|
46
|
+
expect { |b| crawler.crawl(&b) }.to yield_with_args(valid_dir)
|
47
|
+
end
|
48
|
+
|
49
|
+
context 'with blacklist' do
|
50
|
+
let :blacklist do
|
51
|
+
[/ignore/]
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'does not yield paths that match the blacklist' do
|
55
|
+
blacklisted_dir = double(readable?: true, directory?: true, children: ['non-empty'], basename: 'ignored')
|
56
|
+
allow(dir).to receive(:each_child).and_yield(blacklisted_dir)
|
57
|
+
expect { |b| crawler.crawl(&b) }.to_not yield_control
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Audiothority
|
7
|
+
describe Custodian do
|
8
|
+
let :custodian do
|
9
|
+
described_class.new(custody, suspects, fileutils)
|
10
|
+
end
|
11
|
+
|
12
|
+
let :custody do
|
13
|
+
Dir.mktmpdir
|
14
|
+
end
|
15
|
+
|
16
|
+
let :suspects do
|
17
|
+
{
|
18
|
+
Pathname.new('suspect1') => double(:violations),
|
19
|
+
Pathname.new('suspect2') => double(:violations),
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
let :fileutils do
|
24
|
+
double(:fileutils)
|
25
|
+
end
|
26
|
+
|
27
|
+
before do
|
28
|
+
allow(fileutils).to receive(:copy_entry)
|
29
|
+
end
|
30
|
+
|
31
|
+
after do
|
32
|
+
FileUtils.remove_entry_secure(custody) if File.exists?(custody)
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '#throw_in_custody' do
|
36
|
+
context 'when `custody` exists' do
|
37
|
+
before do
|
38
|
+
custodian.throw_in_custody
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'moves all suspects to custody' do
|
42
|
+
expect(fileutils).to have_received(:copy_entry).with('suspect1', %(#{custody}/suspect1), anything)
|
43
|
+
expect(fileutils).to have_received(:copy_entry).with('suspect2', %(#{custody}/suspect2), anything)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'peserves attributes' do
|
47
|
+
expect(fileutils).to have_received(:copy_entry).with(anything, anything, true).twice
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'when `custody does not exist' do
|
52
|
+
let :custody do
|
53
|
+
'i do not exist, mkay?'
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'assumes that the custody has been torched' do
|
57
|
+
expect { custodian.throw_in_custody }.to raise_error(CustodyTorchedError, /seems to have been torched/)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Audiothority
|
7
|
+
describe Extract do
|
8
|
+
let :extract do
|
9
|
+
described_class.new(file_ref)
|
10
|
+
end
|
11
|
+
|
12
|
+
let :file_ref do
|
13
|
+
double(:file_ref)
|
14
|
+
end
|
15
|
+
|
16
|
+
before do
|
17
|
+
allow(file_ref).to receive(:new).and_return(*refs)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#as_tags' do
|
21
|
+
context 'when given a proper set of audio files' do
|
22
|
+
let :paths do
|
23
|
+
[double(:audio_file)]
|
24
|
+
end
|
25
|
+
|
26
|
+
let :refs do
|
27
|
+
[double(:file_ref, null?: false, tag: tags.first, close: nil)]
|
28
|
+
end
|
29
|
+
|
30
|
+
let :tags do
|
31
|
+
[double(:tag)]
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'yields extracted tags' do
|
35
|
+
expect { |b| extract.as_tags(paths, &b) }.to yield_with_args(tags)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'closes file refs' do
|
39
|
+
extract.as_tags(paths) { }
|
40
|
+
refs.each do |ref|
|
41
|
+
expect(ref).to have_received(:close)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'when given a set of files that includes non-audio files' do
|
47
|
+
let :paths do
|
48
|
+
[double(:audio_file), double(:non_audio_file)]
|
49
|
+
end
|
50
|
+
|
51
|
+
let :refs do
|
52
|
+
[
|
53
|
+
double(:file_ref, null?: false, tag: tags.first, close: nil),
|
54
|
+
double(:file_ref, null?: true, close: nil),
|
55
|
+
]
|
56
|
+
end
|
57
|
+
|
58
|
+
let :tags do
|
59
|
+
[double(:tag)]
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'skips `null` tags' do
|
63
|
+
expect { |b| extract.as_tags(paths, &b) }.to yield_with_args(tags)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'closes `null` refs' do
|
67
|
+
extract.as_tags(paths) { }
|
68
|
+
expect(refs.last).to have_received(:close)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'closes file refs' do
|
72
|
+
extract.as_tags(paths) { }
|
73
|
+
refs.each do |ref|
|
74
|
+
expect(ref).to have_received(:close)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context 'when given a set of non-audio files' do
|
80
|
+
let :paths do
|
81
|
+
[double(:non_audio_file), double(:non_audio_file)]
|
82
|
+
end
|
83
|
+
|
84
|
+
let :refs do
|
85
|
+
[
|
86
|
+
double(:file_ref, null?: true, close: nil),
|
87
|
+
double(:file_ref, null?: true, close: nil),
|
88
|
+
]
|
89
|
+
end
|
90
|
+
|
91
|
+
it 'does not yield anything' do
|
92
|
+
expect { |b| extract.as_tags(paths, &b) }.to_not yield_control
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'closes file refs' do
|
96
|
+
extract.as_tags(paths) { }
|
97
|
+
refs.each do |ref|
|
98
|
+
expect(ref).to have_received(:close)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when given `:save` => true' do
|
104
|
+
let :paths do
|
105
|
+
[double(:audio_file)]
|
106
|
+
end
|
107
|
+
|
108
|
+
let :refs do
|
109
|
+
[double(:file_ref, null?: false, tag: tags.first, close: nil, save: nil)]
|
110
|
+
end
|
111
|
+
|
112
|
+
let :tags do
|
113
|
+
[double(:tag)]
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'saves file refs before closing them' do
|
117
|
+
extract.as_tags(paths, save: true) { }
|
118
|
+
refs.each do |ref|
|
119
|
+
expect(ref).to have_received(:save).ordered
|
120
|
+
expect(ref).to have_received(:close).ordered
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
|
6
|
+
module Audiothority
|
7
|
+
describe Inspector do
|
8
|
+
let :inspector do
|
9
|
+
described_class.new(crawler, [validator], tracker, extract: extract)
|
10
|
+
end
|
11
|
+
|
12
|
+
let :crawler do
|
13
|
+
double(:crawler)
|
14
|
+
end
|
15
|
+
|
16
|
+
let :validator do
|
17
|
+
double(:validator)
|
18
|
+
end
|
19
|
+
|
20
|
+
let :tracker do
|
21
|
+
double(:tracker, mark: nil)
|
22
|
+
end
|
23
|
+
|
24
|
+
let :extract do
|
25
|
+
double(:extract)
|
26
|
+
end
|
27
|
+
|
28
|
+
let :path do
|
29
|
+
double(:path, children: children)
|
30
|
+
end
|
31
|
+
|
32
|
+
let :children do
|
33
|
+
[Pathname.new('file01')]
|
34
|
+
end
|
35
|
+
|
36
|
+
let :violation do
|
37
|
+
double(:violation, invalid?: true)
|
38
|
+
end
|
39
|
+
|
40
|
+
let :tags do
|
41
|
+
[double(:tag)]
|
42
|
+
end
|
43
|
+
|
44
|
+
before do
|
45
|
+
allow(crawler).to receive(:crawl).and_yield(path)
|
46
|
+
allow(extract).to receive(:as_tags).with(children).and_yield(tags)
|
47
|
+
allow(validator).to receive(:validate).with(tags).and_return(violation)
|
48
|
+
end
|
49
|
+
|
50
|
+
before do
|
51
|
+
inspector.investigate
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#investigate' do
|
55
|
+
it 'crawls for paths with violations' do
|
56
|
+
expect(crawler).to have_received(:crawl)
|
57
|
+
end
|
58
|
+
|
59
|
+
it 'extracts tags' do
|
60
|
+
expect(extract).to have_received(:as_tags).with(children)
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'validates each tag set' do
|
64
|
+
expect(validator).to have_received(:validate).with(tags)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'tracks paths with violations' do
|
68
|
+
expect(tracker).to have_received(:mark).with(path, [violation])
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|