audiothority 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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,17 @@
1
+ # encoding: utf-8
2
+
3
+ module Audiothority
4
+ class Tracker
5
+ def initialize
6
+ @suspects = {}
7
+ end
8
+
9
+ def suspects
10
+ @suspects.freeze
11
+ end
12
+
13
+ def mark(path, violations)
14
+ @suspects[path] = violations
15
+ end
16
+ end
17
+ 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,11 @@
1
+ # encoding: utf-8
2
+
3
+ module Audiothority
4
+ module Validators
5
+ class Album < Unique
6
+ def initialize
7
+ super(:album)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: utf-8
2
+
3
+ module Audiothority
4
+ module Validators
5
+ class Artist < Unique
6
+ def initialize
7
+ super(:artist)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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,11 @@
1
+ # encoding: utf-8
2
+
3
+ module Audiothority
4
+ module Validators
5
+ class Year < Unique
6
+ def initialize
7
+ super(:year)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ # encoding: utf-8
2
+
3
+ module Audiothority
4
+ VERSION = '0.1.0'.freeze
5
+ 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