tobacco 0.0.2 → 0.0.3
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.
- data/README.md +4 -0
- data/lib/tobacco/callback.rb +32 -0
- data/lib/tobacco/content_reader.rb +48 -0
- data/lib/tobacco/content_validator.rb +81 -0
- data/lib/tobacco/exhaler.rb +21 -1
- data/lib/tobacco/safety_net.rb +66 -0
- data/lib/tobacco/smoker.rb +37 -106
- data/lib/tobacco/version.rb +1 -1
- data/lib/tobacco.rb +9 -1
- data/spec/tobacco/content_reader_spec.rb +99 -0
- data/spec/tobacco/content_validator_spec.rb +53 -0
- data/spec/tobacco/exhaler_spec.rb +5 -0
- data/spec/tobacco/safety_net_spec.rb +37 -0
- data/spec/tobacco/smoker_spec.rb +34 -147
- metadata +12 -2
data/README.md
CHANGED
@@ -297,6 +297,10 @@ end
|
|
297
297
|
|
298
298
|
To avoid duplication, we wrap the callbacks and write! method in a helper module that is included in all the Writer classes. This makes the individual Writers very small and easy to maintain.
|
299
299
|
|
300
|
+
## Future Improvements ##
|
301
|
+
|
302
|
+
Make a backup of the file before attempting a new write. If something goes wrong with the write and an empty file is created, restore the original.
|
303
|
+
|
300
304
|
|
301
305
|
## Contributing
|
302
306
|
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Tobacco
|
2
|
+
class Callback
|
3
|
+
attr_accessor :writer
|
4
|
+
|
5
|
+
def self.instance(writer = nil)
|
6
|
+
@instance ||= new(writer)
|
7
|
+
end
|
8
|
+
private_class_method :new
|
9
|
+
|
10
|
+
def initialize(writer = nil)
|
11
|
+
self.writer = writer
|
12
|
+
end
|
13
|
+
|
14
|
+
# Public: Notify the writer class based on callback name
|
15
|
+
# passing along data which is either content or
|
16
|
+
# an error object
|
17
|
+
#
|
18
|
+
# name - symbol - the callback name
|
19
|
+
#
|
20
|
+
# data - text or object
|
21
|
+
#
|
22
|
+
# return - data
|
23
|
+
#
|
24
|
+
def notify(name, data)
|
25
|
+
if writer.respond_to? name
|
26
|
+
writer.send(name, data)
|
27
|
+
else
|
28
|
+
data
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module Tobacco
|
2
|
+
class ContentReader
|
3
|
+
attr_accessor :reader, :content, :modified_content
|
4
|
+
|
5
|
+
def initialize(smoker)
|
6
|
+
@smoker = smoker
|
7
|
+
@consumer = smoker.consumer
|
8
|
+
@filepath = smoker.file_path_generator
|
9
|
+
end
|
10
|
+
|
11
|
+
def read
|
12
|
+
choose_reader
|
13
|
+
read_content
|
14
|
+
modify_content
|
15
|
+
end
|
16
|
+
|
17
|
+
def modify_content
|
18
|
+
self.modified_content = \
|
19
|
+
Tobacco::Callback.instance.notify(:before_write, content)
|
20
|
+
end
|
21
|
+
|
22
|
+
def read_content
|
23
|
+
self.content = reader.send(Tobacco.content_method)
|
24
|
+
end
|
25
|
+
|
26
|
+
# The reader will either be the calling class (consumer)
|
27
|
+
# if it provides the content method or a new Inhaler
|
28
|
+
# object that will be used to read the content from a url
|
29
|
+
#
|
30
|
+
def choose_reader
|
31
|
+
self.reader = \
|
32
|
+
if @consumer.respond_to? Tobacco.content_method
|
33
|
+
@consumer
|
34
|
+
else
|
35
|
+
Inhaler.new(@filepath.content_url).tap do |inhaler|
|
36
|
+
|
37
|
+
# Add an alias for the user configured content_method
|
38
|
+
# so that when it is called it calls :read
|
39
|
+
# on the Inhaler instance
|
40
|
+
#
|
41
|
+
inhaler.instance_eval %{
|
42
|
+
alias :"#{Tobacco.content_method}" :read
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Tobacco
|
2
|
+
class MissingContentError < RuntimeError
|
3
|
+
def message
|
4
|
+
"No error encountered but content is empty"
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class ContentValidator
|
9
|
+
attr_accessor :content
|
10
|
+
|
11
|
+
def initialize(smoker)
|
12
|
+
@smoker = smoker
|
13
|
+
self.content = smoker.content
|
14
|
+
end
|
15
|
+
|
16
|
+
# Public: Verifies that content is present and calls
|
17
|
+
# continue_write on the Smoker class if so
|
18
|
+
# and notifies the consumer class of the
|
19
|
+
# read error if not
|
20
|
+
#
|
21
|
+
def validate!
|
22
|
+
if content_present?
|
23
|
+
@smoker.continue_write
|
24
|
+
else
|
25
|
+
|
26
|
+
# ????? IS this still true
|
27
|
+
#
|
28
|
+
# At this point, the content might be an error object
|
29
|
+
# but if not, we create one
|
30
|
+
#
|
31
|
+
object = missing_content_error(content)
|
32
|
+
error = error_object('Error Reading', object)
|
33
|
+
|
34
|
+
Tobacco::Callback.instance.notify(:on_read_error, error)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#---------------------------------------------------------
|
39
|
+
private
|
40
|
+
|
41
|
+
def content_present?
|
42
|
+
@content_present ||= content?
|
43
|
+
end
|
44
|
+
|
45
|
+
def content?
|
46
|
+
return false if content.nil? || content.empty?
|
47
|
+
|
48
|
+
Array(content).last !~ /404 Not Found|The page you were looking for doesn't exist/
|
49
|
+
end
|
50
|
+
|
51
|
+
# Private: Convenience method to create a Tobacco::Error object
|
52
|
+
#
|
53
|
+
# msg - Context where the error occurred
|
54
|
+
# modified_content - Content after the :before_write callback
|
55
|
+
# e - The error that was raised
|
56
|
+
#
|
57
|
+
def error_object(msg, e)
|
58
|
+
Tobacco::Error.new(
|
59
|
+
msg: msg,
|
60
|
+
filepath: @smoker.filepath,
|
61
|
+
content: content,
|
62
|
+
object: e
|
63
|
+
)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Private: Creates an error object if content is a string or nil
|
67
|
+
#
|
68
|
+
# content - A string, nil or Error object
|
69
|
+
#
|
70
|
+
# returns an Error object
|
71
|
+
#
|
72
|
+
def missing_content_error(content)
|
73
|
+
if content.respond_to? :message
|
74
|
+
content
|
75
|
+
else
|
76
|
+
Tobacco::MissingContentError.new
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
data/lib/tobacco/exhaler.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
|
1
3
|
module Tobacco
|
2
4
|
class Exhaler
|
3
5
|
attr_accessor :content, :filepath
|
@@ -8,6 +10,8 @@ module Tobacco
|
|
8
10
|
end
|
9
11
|
|
10
12
|
def write!
|
13
|
+
safety_net.backup
|
14
|
+
|
11
15
|
create_directory
|
12
16
|
write_content_to_file
|
13
17
|
end
|
@@ -17,10 +21,26 @@ module Tobacco
|
|
17
21
|
end
|
18
22
|
|
19
23
|
def write_content_to_file
|
24
|
+
begin
|
25
|
+
persist(content)
|
26
|
+
safety_net.destroy
|
27
|
+
rescue => e
|
28
|
+
safety_net.restore
|
29
|
+
raise
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def persist(file_content)
|
20
37
|
File.open(filepath, 'wb') do |f|
|
21
|
-
f.write
|
38
|
+
f.write file_content
|
22
39
|
end
|
23
40
|
end
|
24
41
|
|
42
|
+
def safety_net
|
43
|
+
@safety_net ||= Tobacco::SafetyNet.new(filepath)
|
44
|
+
end
|
25
45
|
end
|
26
46
|
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# Backup is responsible for backing up the current file
|
2
|
+
# if it exists and restoring it in the event an error
|
3
|
+
# occurs while attempting to write new content.
|
4
|
+
#
|
5
|
+
module Tobacco
|
6
|
+
class SafetyNet
|
7
|
+
attr_reader :file
|
8
|
+
|
9
|
+
def initialize(filepath)
|
10
|
+
@filepath = filepath
|
11
|
+
end
|
12
|
+
|
13
|
+
# Public: Creates a backup of the original file
|
14
|
+
# so it can be restored if necessary
|
15
|
+
#
|
16
|
+
def backup
|
17
|
+
Tobacco.log("Backup: #{@filepath} => #{destination}")
|
18
|
+
|
19
|
+
if File.exists? @filepath
|
20
|
+
FileUtils.mv(@filepath, destination)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Public: Restores the original file in
|
25
|
+
# the event an error occurs during
|
26
|
+
# writing the new content
|
27
|
+
#
|
28
|
+
def restore
|
29
|
+
Tobacco.log("Restoring: #{destination} => #{@filepath}")
|
30
|
+
|
31
|
+
if File.exists? destination
|
32
|
+
FileUtils.mv(destination, @filepath)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Public: Destroys the backup file
|
37
|
+
#
|
38
|
+
def destroy
|
39
|
+
Tobacco.log("Destroying: #{destination}")
|
40
|
+
|
41
|
+
if File.exists? destination
|
42
|
+
FileUtils.rm(destination)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Public: Memoizes the destination path
|
47
|
+
#
|
48
|
+
def destination
|
49
|
+
@destination ||= destination_path
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Private: Creates a temporary path with
|
55
|
+
# a unique name using a timestamp
|
56
|
+
#
|
57
|
+
def destination_path
|
58
|
+
name = File.basename @filepath
|
59
|
+
dir = File.dirname @filepath
|
60
|
+
additive = Time.now.to_i
|
61
|
+
temp_name = "#{additive}_#{name}"
|
62
|
+
|
63
|
+
"#{dir}/#{temp_name}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/tobacco/smoker.rb
CHANGED
@@ -1,145 +1,76 @@
|
|
1
1
|
module Tobacco
|
2
|
-
class MissingContentError < RuntimeError
|
3
|
-
def message
|
4
|
-
"No error encountered but content is empty"
|
5
|
-
end
|
6
|
-
end
|
7
|
-
|
8
2
|
class Smoker
|
9
3
|
|
10
|
-
attr_accessor :
|
4
|
+
attr_accessor :consumer,
|
11
5
|
:file_path_generator,
|
12
6
|
:reader,
|
13
|
-
:
|
7
|
+
:persister,
|
14
8
|
:content
|
15
9
|
|
16
|
-
def initialize(
|
17
|
-
self.
|
18
|
-
self.content
|
10
|
+
def initialize(consumer, content = nil)
|
11
|
+
self.consumer = consumer
|
12
|
+
self.content = content
|
13
|
+
|
14
|
+
Tobacco::Callback.instance(consumer)
|
15
|
+
|
16
|
+
generate_file_paths
|
19
17
|
end
|
20
18
|
|
21
19
|
def generate_file_paths
|
22
|
-
self.file_path_generator = Roller.new(
|
20
|
+
self.file_path_generator = Tobacco::Roller.new(consumer)
|
23
21
|
end
|
24
22
|
|
25
23
|
def read
|
26
|
-
|
27
|
-
read_content
|
28
|
-
verify_content
|
24
|
+
self.content = Tobacco::ContentReader.new(self).read
|
29
25
|
end
|
30
26
|
|
27
|
+
# Public: Writes content to file allowing for manipulation
|
28
|
+
# of the content beforehand through the :before_write callback
|
29
|
+
# This is due to the fact that content can be set directly
|
30
|
+
# without going through the read method.
|
31
|
+
#
|
32
|
+
# Validate is only in the write method because
|
33
|
+
# the content can be set directly and the read method
|
34
|
+
# never called.
|
35
|
+
#
|
31
36
|
def write!
|
32
|
-
|
37
|
+
Tobacco::ContentValidator.new(self).validate!
|
38
|
+
end
|
33
39
|
|
40
|
+
# Public: Called by ContentValidator if content is valid
|
41
|
+
#
|
42
|
+
def continue_write
|
34
43
|
begin
|
35
|
-
|
36
|
-
modified_content = modify_content_before_writing
|
37
|
-
content_writer = Tobacco::Exhaler.new(modified_content, filepath)
|
38
|
-
|
44
|
+
content_writer = Tobacco::Exhaler.new(content, filepath)
|
39
45
|
content_writer.write!
|
40
46
|
|
41
|
-
|
47
|
+
Tobacco::Callback.instance.notify(:on_success, content)
|
42
48
|
|
43
49
|
rescue => e
|
44
|
-
Tobacco.log("ErrorWriting: #{filepath}")
|
45
|
-
|
46
|
-
# Remove the empty file
|
47
|
-
File.delete(filepath) if File.exists?(filepath)
|
48
|
-
|
49
|
-
error = error_object('Error Writing', modified_content, e)
|
50
|
-
callback(:on_write_error, error)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
|
55
|
-
#---------------------------------------------------------
|
56
|
-
# End of Public API
|
57
|
-
#---------------------------------------------------------
|
58
|
-
|
59
|
-
|
60
|
-
#---------------------------------------------------------
|
61
|
-
# Write helper methods
|
62
|
-
#---------------------------------------------------------
|
63
|
-
def modify_content_before_writing
|
64
|
-
callback(:before_write, content)
|
65
|
-
end
|
50
|
+
Tobacco.log("ErrorWriting: #{filepath}\nError: #{e}")
|
66
51
|
|
67
|
-
|
68
|
-
|
69
|
-
#---------------------------------------------------------
|
70
|
-
def read_content
|
71
|
-
self.content = reader.send(Tobacco.content_method)
|
72
|
-
end
|
73
|
-
|
74
|
-
def verify_content
|
75
|
-
unless content_present?
|
76
|
-
|
77
|
-
# At this point, the content might be an error object
|
78
|
-
# but if not, we create one
|
79
|
-
#
|
80
|
-
object = missing_content_error(content)
|
81
|
-
error = error_object('Error Reading', '', object)
|
82
|
-
|
83
|
-
callback(:on_read_error, error)
|
84
|
-
end
|
85
|
-
end
|
52
|
+
error = error_object('Error Writing', e)
|
53
|
+
Tobacco::Callback.instance.notify(:on_write_error, error)
|
86
54
|
|
87
|
-
|
88
|
-
if content.respond_to? :message
|
89
|
-
content
|
90
|
-
else
|
91
|
-
Tobacco::MissingContentError.new
|
55
|
+
# raise
|
92
56
|
end
|
93
57
|
end
|
94
58
|
|
95
|
-
def
|
96
|
-
@
|
97
|
-
end
|
98
|
-
|
99
|
-
def content?
|
100
|
-
return false if content.nil? || content.empty?
|
101
|
-
|
102
|
-
Array(content).last !~ /404 Not Found|The page you were looking for doesn't exist/
|
103
|
-
end
|
104
|
-
|
105
|
-
def choose_reader
|
106
|
-
# The reader will either be the calling class (smoker)
|
107
|
-
# if it provides the content method or a new Inhaler
|
108
|
-
# object that will be used to read the content from a url
|
109
|
-
#
|
110
|
-
self.reader = \
|
111
|
-
if smoker.respond_to? Tobacco.content_method
|
112
|
-
smoker
|
113
|
-
else
|
114
|
-
Inhaler.new(file_path_generator.content_url).tap do |inhaler|
|
115
|
-
|
116
|
-
# Add an alias for the user configured content_method
|
117
|
-
# so that when it is called it calls :read
|
118
|
-
# on the Inhaler instance
|
119
|
-
#
|
120
|
-
inhaler.instance_eval %{
|
121
|
-
alias :"#{Tobacco.content_method}" :read
|
122
|
-
}
|
123
|
-
end
|
124
|
-
end
|
59
|
+
def filepath
|
60
|
+
@filepath ||= file_path_generator.output_filepath
|
125
61
|
end
|
126
62
|
|
127
63
|
#---------------------------------------------------------
|
128
|
-
|
64
|
+
private
|
129
65
|
|
130
|
-
def error_object(msg,
|
66
|
+
def error_object(msg, e)
|
131
67
|
Tobacco::Error.new(
|
132
68
|
msg: msg,
|
133
|
-
filepath:
|
134
|
-
content:
|
69
|
+
filepath: filepath,
|
70
|
+
content: content,
|
135
71
|
object: e
|
136
72
|
)
|
137
73
|
end
|
138
74
|
|
139
|
-
def callback(name, error)
|
140
|
-
if smoker.respond_to? name
|
141
|
-
smoker.send(name, error)
|
142
|
-
end
|
143
|
-
end
|
144
75
|
end
|
145
76
|
end
|
data/lib/tobacco/version.rb
CHANGED
data/lib/tobacco.rb
CHANGED
@@ -13,13 +13,15 @@ module Tobacco
|
|
13
13
|
@content_method = :content
|
14
14
|
@content_url_method = :content_url
|
15
15
|
@output_filepath_method = :output_filepath
|
16
|
+
@logging = false
|
16
17
|
|
17
18
|
class << self
|
18
19
|
attr_accessor :base_path,
|
19
20
|
:published_host,
|
20
21
|
:content_method,
|
21
22
|
:content_url_method,
|
22
|
-
:output_filepath_method
|
23
|
+
:output_filepath_method,
|
24
|
+
:logging
|
23
25
|
end
|
24
26
|
|
25
27
|
def self.configure
|
@@ -27,6 +29,8 @@ module Tobacco
|
|
27
29
|
end
|
28
30
|
|
29
31
|
def self.log(msg)
|
32
|
+
return unless logging
|
33
|
+
|
30
34
|
log_msg = "\n*******************************************\n"
|
31
35
|
log_msg += "Tobacco::Log: #{msg}\n"
|
32
36
|
log_msg += "*******************************************\n"
|
@@ -45,4 +49,8 @@ require 'tobacco/roller'
|
|
45
49
|
require 'tobacco/inhaler'
|
46
50
|
require 'tobacco/exhaler'
|
47
51
|
require 'tobacco/error'
|
52
|
+
require 'tobacco/safety_net'
|
53
|
+
require 'tobacco/content_validator'
|
54
|
+
require 'tobacco/content_reader'
|
55
|
+
require 'tobacco/callback'
|
48
56
|
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tobacco::ContentReader do
|
4
|
+
before do
|
5
|
+
Tobacco.configure do |config|
|
6
|
+
config.published_host = 'http://localhost:3000'
|
7
|
+
config.base_path = '/tmp/published_content'
|
8
|
+
config.content_method = :content
|
9
|
+
config.content_url_method = :content_url
|
10
|
+
config.output_filepath_method = :output_filepath
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:consumer) { mock('consumer', content: 'Base content') }
|
15
|
+
let(:smoker) { mock('smoker', file_path_generator: filepath, consumer: consumer, content: '') }
|
16
|
+
let(:filepath) { mock('roller', content_url: '/video', output_filepath: '/file/path') }
|
17
|
+
|
18
|
+
subject { Tobacco::ContentReader.new(smoker) }
|
19
|
+
|
20
|
+
describe '#choose_reader' do
|
21
|
+
context 'when consumer provides the content' do
|
22
|
+
let(:consumer) { mock('consumer', content: 'Base content') }
|
23
|
+
|
24
|
+
it 'uses the consumer for the content' do
|
25
|
+
subject.choose_reader
|
26
|
+
|
27
|
+
subject.reader.should == consumer
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'when smoker does not provide content' do
|
32
|
+
let(:inhaler) { mock('inhaler', read: 'reader method') }
|
33
|
+
let(:filepath) { mock('roller', content_url: '/video') }
|
34
|
+
|
35
|
+
before do
|
36
|
+
smoker.consumer.unstub!(:content)
|
37
|
+
Tobacco::Inhaler.stub(:new).and_return(inhaler)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'uses the smoker for the content' do
|
41
|
+
subject.choose_reader
|
42
|
+
subject.reader.should == inhaler
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
describe '#read_content' do
|
49
|
+
context 'when content is read from the consumer' do
|
50
|
+
let(:content) { 'Provided content' }
|
51
|
+
|
52
|
+
before { smoker.consumer.stub(:content).and_return(content) }
|
53
|
+
|
54
|
+
it 'should have the correct content' do
|
55
|
+
subject.choose_reader
|
56
|
+
subject.read_content
|
57
|
+
|
58
|
+
subject.content.should == content
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context 'when content is read from url' do
|
63
|
+
let(:content) { 'Content from reading url' }
|
64
|
+
let(:inhaler) { mock('inhaler', read: content) }
|
65
|
+
let(:filepath) { mock('roller', content_url: '/video', output_filepath: '/tmp/howdy.txt') }
|
66
|
+
|
67
|
+
before do
|
68
|
+
smoker.consumer.unstub!(:content)
|
69
|
+
Tobacco::Inhaler.stub(:new).and_return(inhaler)
|
70
|
+
smoker.stub(:file_path_generator).and_return(filepath)
|
71
|
+
end
|
72
|
+
|
73
|
+
it 'should have the correct content' do
|
74
|
+
subject.choose_reader
|
75
|
+
subject.read
|
76
|
+
|
77
|
+
subject.content.should == content
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
describe 'modifies content' do
|
83
|
+
let(:content) { '<h1>Summer Gear</h1>' }
|
84
|
+
let(:modified_content) { '<h1>Winter Gear</h1>' }
|
85
|
+
|
86
|
+
before do
|
87
|
+
Tobacco::Callback.instance.writer = consumer
|
88
|
+
smoker.consumer.stub(:content).and_return(content)
|
89
|
+
smoker.consumer.stub(:before_write).and_return(modified_content)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'allows the consumer to modify content after reading' do
|
93
|
+
subject.read
|
94
|
+
|
95
|
+
subject.modified_content.should == modified_content
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
|
4
|
+
describe Tobacco::ContentValidator do
|
5
|
+
|
6
|
+
class Consumer
|
7
|
+
def on_read_error(error)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
let(:consumer) { Consumer.new }
|
12
|
+
let(:smoker) { Tobacco::Smoker.new(consumer) }
|
13
|
+
|
14
|
+
subject { Tobacco::ContentValidator.new(smoker) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
smoker.stub(:filepath).and_return('/path')
|
18
|
+
Tobacco::Callback.instance.stub(:writer).and_return(consumer)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#validate!' do
|
22
|
+
context 'when validation passes' do
|
23
|
+
let(:content) { 'Lorem ipsum' }
|
24
|
+
|
25
|
+
before { smoker.content = content }
|
26
|
+
|
27
|
+
it 'calls continue write' do
|
28
|
+
smoker.should_receive(:continue_write)
|
29
|
+
smoker.consumer.should_not_receive(:on_read_error)
|
30
|
+
|
31
|
+
subject.validate!
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#validate!' do
|
37
|
+
context 'when validation fails' do
|
38
|
+
before { smoker.content = '' }
|
39
|
+
|
40
|
+
it 'does not call continue write on' do
|
41
|
+
smoker.should_not_receive(:continue_write)
|
42
|
+
|
43
|
+
subject.validate!
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'calls on_read_error' do
|
47
|
+
consumer.should_receive(:on_read_error)
|
48
|
+
|
49
|
+
subject.validate!
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -43,6 +43,11 @@ describe Tobacco::Exhaler do
|
|
43
43
|
let(:dir) { '/tmp/base_dir/' }
|
44
44
|
let(:content) { '<h1>Page Title</h1>' }
|
45
45
|
|
46
|
+
before do
|
47
|
+
FileUtils.mkdir_p(dir)
|
48
|
+
File.open(filepath, 'w') { |f| f.write content }
|
49
|
+
end
|
50
|
+
|
46
51
|
after { FileUtils.rm_rf(dir) }
|
47
52
|
|
48
53
|
describe '#create_directory' do
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tobacco::SafetyNet do
|
4
|
+
let(:safety_net) { Tobacco::SafetyNet }
|
5
|
+
let(:filepath) { '/tmp/tempfile.txt' }
|
6
|
+
let(:content) { 'This little piggy went to market...' }
|
7
|
+
|
8
|
+
before do
|
9
|
+
File.open(filepath, 'w') {|f| f.write content }
|
10
|
+
@safety_net = safety_net.new(filepath)
|
11
|
+
@safety_net.backup
|
12
|
+
end
|
13
|
+
|
14
|
+
context 'when backing up' do
|
15
|
+
after { @safety_net.destroy }
|
16
|
+
|
17
|
+
it 'creates a tempfile' do
|
18
|
+
File.exists?(@safety_net.destination).should be_true
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'has correct content' do
|
22
|
+
File.read(@safety_net.destination).should == content
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'when restoring the original file' do
|
27
|
+
before { @safety_net.restore }
|
28
|
+
|
29
|
+
it 'works' do
|
30
|
+
File.exists?(filepath).should be_true
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'has the correct content' do
|
34
|
+
File.read(filepath).should == content
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/spec/tobacco/smoker_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'tobacco'
|
3
3
|
|
4
|
-
|
5
4
|
describe Tobacco::Smoker do
|
6
5
|
before do
|
7
6
|
Tobacco.configure do |config|
|
@@ -13,22 +12,21 @@ describe Tobacco::Smoker do
|
|
13
12
|
end
|
14
13
|
end
|
15
14
|
|
16
|
-
let(:
|
15
|
+
let(:consumer) { mock('consumer') }
|
17
16
|
|
18
|
-
subject { Tobacco::Smoker.new(
|
17
|
+
subject { Tobacco::Smoker.new(consumer) }
|
19
18
|
|
20
19
|
context '#write!' do
|
20
|
+
let(:filepath) { mock('roller', content_url: '/video', output_filepath: '/tmp/tobacco.txt') }
|
21
21
|
|
22
|
-
|
23
|
-
|
24
|
-
|
22
|
+
before do
|
23
|
+
subject.file_path_generator = filepath
|
24
|
+
end
|
25
25
|
|
26
|
-
|
27
|
-
Tobacco::Inhaler.stub(:new).and_return(inhaler)
|
28
|
-
subject.file_path_generator = filepath
|
29
|
-
end
|
26
|
+
context 'when content is empty' do
|
30
27
|
|
31
28
|
it 'does not attempt a write' do
|
29
|
+
consumer.stub(:content).and_return('')
|
32
30
|
Tobacco::Exhaler.should_not_receive(:new)
|
33
31
|
|
34
32
|
subject.read
|
@@ -36,46 +34,44 @@ describe Tobacco::Smoker do
|
|
36
34
|
end
|
37
35
|
end
|
38
36
|
|
39
|
-
context 'content' do
|
40
|
-
let(:
|
41
|
-
|
42
|
-
let(:filepath) { mock('roller', content_url: '/video', output_filepath: '/desktop') }
|
43
|
-
|
44
|
-
before do
|
45
|
-
Tobacco::Exhaler.stub(:new).and_return(exhaler)
|
46
|
-
subject.file_path_generator = filepath
|
47
|
-
end
|
37
|
+
context 'content success' do
|
38
|
+
let(:consumer) { mock('smoker') }
|
39
|
+
subject { Tobacco::Smoker.new(consumer) }
|
48
40
|
|
49
41
|
context 'when providing content directly' do
|
42
|
+
let(:content) { 'Directly set content' }
|
43
|
+
let(:exhaler) { mock('exhaler', write!: true) }
|
44
|
+
let(:filepath) { mock('roller', content_url: '/video', output_filepath: '/tmp/tobacco.txt') }
|
50
45
|
|
51
|
-
|
52
|
-
|
46
|
+
before do
|
47
|
+
Tobacco::Exhaler.stub(:new).and_return(exhaler)
|
53
48
|
subject.content = content
|
49
|
+
end
|
54
50
|
|
51
|
+
it 'uses the provided content' do
|
52
|
+
exhaler.should_receive(:write!)
|
55
53
|
subject.write!
|
56
54
|
end
|
57
55
|
|
58
|
-
it '
|
59
|
-
|
60
|
-
|
61
|
-
|
56
|
+
it 'does not call the :before_write callback' do
|
57
|
+
Tobacco::Callback.instance.should_not_receive(:notify).with(:before_write, content)
|
58
|
+
subject.write!
|
59
|
+
end
|
62
60
|
|
61
|
+
it 'callback :on_success is called' do
|
62
|
+
Tobacco::Callback.instance.writer.should_receive(:on_success).with(content)
|
63
63
|
subject.write!
|
64
64
|
end
|
65
65
|
end
|
66
|
+
end
|
66
67
|
|
68
|
+
context 'content errors' do
|
67
69
|
context 'when an error occurs during writing' do
|
68
|
-
let(:
|
69
|
-
|
70
|
-
before do
|
71
|
-
subject.file_path_generator = filepath
|
72
|
-
smoker.should_receive(:before_write).with(content).and_return(content)
|
73
|
-
exhaler.should_receive(:write!).and_return { error }
|
74
|
-
end
|
70
|
+
let(:filepath) { mock('roller', content_url: '/video', output_filepath: '/users/blah.txt') }
|
75
71
|
|
76
72
|
it 'calls the callback :on_write_error' do
|
77
|
-
|
78
|
-
subject.content = content
|
73
|
+
Tobacco::Callback.instance.should_receive(:notify).once
|
74
|
+
subject.content = 'Directly set content'
|
79
75
|
|
80
76
|
subject.write!
|
81
77
|
end
|
@@ -83,122 +79,13 @@ describe Tobacco::Smoker do
|
|
83
79
|
end
|
84
80
|
end
|
85
81
|
|
86
|
-
describe '#modify_content_before_writing' do
|
87
|
-
let(:content) { '<h1>Summer Gear</h1>' }
|
88
|
-
let(:modified_content) { '<h1>Winter Gear</h1>' }
|
89
|
-
|
90
|
-
before do
|
91
|
-
smoker.stub(:before_write).and_return(modified_content)
|
92
|
-
end
|
93
|
-
|
94
|
-
it 'allows the smoker to modify content before writing' do
|
95
|
-
subject.content = content
|
96
|
-
|
97
|
-
subject.modify_content_before_writing.should == modified_content
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
82
|
describe '#generate_file_paths' do
|
102
|
-
|
103
|
-
Tobacco::Roller.should_receive(:new).with(smoker)
|
104
|
-
|
105
|
-
subject.generate_file_paths
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
describe '#choose_reader' do
|
110
|
-
context 'when smoker provides the content' do
|
111
|
-
before { smoker.stub(:content) }
|
112
|
-
|
113
|
-
it 'uses the smoker for the content' do
|
114
|
-
subject.choose_reader
|
115
|
-
subject.reader.should == smoker
|
116
|
-
end
|
117
|
-
end
|
118
|
-
|
119
|
-
context 'when smoker does not provide content' do
|
120
|
-
let(:inhaler) { mock('inhaler', read: 'reader method') }
|
121
|
-
let(:filepath) { mock('roller', content_url: '/video') }
|
122
|
-
|
123
|
-
before do
|
124
|
-
Tobacco::Inhaler.stub(:new).and_return(inhaler)
|
125
|
-
subject.file_path_generator = filepath
|
126
|
-
end
|
127
|
-
|
128
|
-
it 'uses the smoker for the content' do
|
129
|
-
subject.choose_reader
|
130
|
-
subject.reader.should == inhaler
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
describe '#read_content' do
|
136
|
-
context 'when content is read from the smoker' do
|
137
|
-
let(:content) { 'Provided content' }
|
138
|
-
|
139
|
-
before { smoker.stub(:content).and_return(content) }
|
83
|
+
before { consumer.stub(:output_filepath).and_return('/tmp/path.txt') }
|
140
84
|
|
141
|
-
|
142
|
-
|
143
|
-
subject.read_content
|
144
|
-
|
145
|
-
subject.content.should == content
|
146
|
-
end
|
147
|
-
end
|
148
|
-
|
149
|
-
context 'when content is read from url' do
|
150
|
-
let(:content) { 'Content from reading url' }
|
151
|
-
let(:inhaler) { mock('inhaler', read: content) }
|
152
|
-
let(:filepath) { mock('roller', content_url: '/video') }
|
153
|
-
|
154
|
-
before do
|
155
|
-
Tobacco::Inhaler.stub(:new).and_return(inhaler)
|
156
|
-
subject.file_path_generator = filepath
|
157
|
-
end
|
158
|
-
|
159
|
-
it 'should have the correct content' do
|
160
|
-
subject.choose_reader
|
161
|
-
subject.read_content
|
162
|
-
|
163
|
-
subject.content.should == content
|
164
|
-
end
|
165
|
-
end
|
166
|
-
end
|
167
|
-
|
168
|
-
describe '#read' do
|
169
|
-
context 'when content is empty' do
|
170
|
-
class Writer
|
171
|
-
attr_accessor :error
|
172
|
-
def on_read_error(error)
|
173
|
-
self.error = error
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
describe '#on_read_error' do
|
178
|
-
let(:inhaler) { mock('inhaler', read: '') }
|
179
|
-
let(:filepath) { mock('roller', content_url: '/video', output_filepath: '/file/path') }
|
180
|
-
let(:smoker) { Writer.new }
|
181
|
-
|
182
|
-
before do
|
183
|
-
Tobacco::Inhaler.stub(:new).and_return(inhaler)
|
184
|
-
subject.file_path_generator = filepath
|
185
|
-
end
|
186
|
-
|
187
|
-
it 'the callback is called on the smoker' do
|
188
|
-
subject.smoker.should_receive(:on_read_error)
|
189
|
-
|
190
|
-
subject.read
|
191
|
-
end
|
192
|
-
|
193
|
-
it 'has the correct message on the error object' do
|
194
|
-
subject.read
|
195
|
-
|
196
|
-
error = subject.smoker.error
|
197
|
-
message = error.object.message
|
85
|
+
it 'sets the file_path_generator' do
|
86
|
+
Tobacco::Roller.should_receive(:new).with(consumer)
|
198
87
|
|
199
|
-
|
200
|
-
end
|
201
|
-
end
|
88
|
+
Tobacco::Smoker.new(consumer)
|
202
89
|
end
|
203
90
|
end
|
204
91
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tobacco
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-10-
|
12
|
+
date: 2012-10-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -108,19 +108,26 @@ files:
|
|
108
108
|
- Rakefile
|
109
109
|
- lib/tobacco.rb
|
110
110
|
- lib/tobacco/burnout.rb
|
111
|
+
- lib/tobacco/callback.rb
|
112
|
+
- lib/tobacco/content_reader.rb
|
113
|
+
- lib/tobacco/content_validator.rb
|
111
114
|
- lib/tobacco/error.rb
|
112
115
|
- lib/tobacco/exhaler.rb
|
113
116
|
- lib/tobacco/inhaler.rb
|
114
117
|
- lib/tobacco/roller.rb
|
118
|
+
- lib/tobacco/safety_net.rb
|
115
119
|
- lib/tobacco/smoker.rb
|
116
120
|
- lib/tobacco/version.rb
|
117
121
|
- spec/spec_helper.rb
|
118
122
|
- spec/support/vcr_cassettes/url_content.yml
|
119
123
|
- spec/tobacco/burnout_spec.rb
|
124
|
+
- spec/tobacco/content_reader_spec.rb
|
125
|
+
- spec/tobacco/content_validator_spec.rb
|
120
126
|
- spec/tobacco/error_spec.rb
|
121
127
|
- spec/tobacco/exhaler_spec.rb
|
122
128
|
- spec/tobacco/inhaler_spec.rb
|
123
129
|
- spec/tobacco/roller_spec.rb
|
130
|
+
- spec/tobacco/safety_net_spec.rb
|
124
131
|
- spec/tobacco/smoker_spec.rb
|
125
132
|
- spec/tobacco_spec.rb
|
126
133
|
- tobacco.gemspec
|
@@ -152,9 +159,12 @@ test_files:
|
|
152
159
|
- spec/spec_helper.rb
|
153
160
|
- spec/support/vcr_cassettes/url_content.yml
|
154
161
|
- spec/tobacco/burnout_spec.rb
|
162
|
+
- spec/tobacco/content_reader_spec.rb
|
163
|
+
- spec/tobacco/content_validator_spec.rb
|
155
164
|
- spec/tobacco/error_spec.rb
|
156
165
|
- spec/tobacco/exhaler_spec.rb
|
157
166
|
- spec/tobacco/inhaler_spec.rb
|
158
167
|
- spec/tobacco/roller_spec.rb
|
168
|
+
- spec/tobacco/safety_net_spec.rb
|
159
169
|
- spec/tobacco/smoker_spec.rb
|
160
170
|
- spec/tobacco_spec.rb
|