audiothority 0.1.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 +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
|