tobacco 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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 content
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
@@ -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 :smoker,
4
+ attr_accessor :consumer,
11
5
  :file_path_generator,
12
6
  :reader,
13
- :writer,
7
+ :persister,
14
8
  :content
15
9
 
16
- def initialize(smoker, content = '')
17
- self.smoker = smoker
18
- self.content = 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(smoker)
20
+ self.file_path_generator = Tobacco::Roller.new(consumer)
23
21
  end
24
22
 
25
23
  def read
26
- choose_reader
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
- return unless content_present?
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
- filepath = file_path_generator.output_filepath
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
- callback(:on_success, modified_content)
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
- # Read helper methods
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
- def missing_content_error(content)
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 content_present?
96
- @content_present ||= content?
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
- # private
64
+ private
129
65
 
130
- def error_object(msg, modified_content, e)
66
+ def error_object(msg, e)
131
67
  Tobacco::Error.new(
132
68
  msg: msg,
133
- filepath: file_path_generator.output_filepath,
134
- content: modified_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
@@ -1,3 +1,3 @@
1
1
  module Tobacco
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
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
@@ -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(:smoker) { mock('smoker') }
15
+ let(:consumer) { mock('consumer') }
17
16
 
18
- subject { Tobacco::Smoker.new(smoker) }
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
- context 'when content is empty' do
23
- let(:inhaler) { mock('inhaler', read: '') }
24
- let(:filepath) { mock('roller', content_url: '/video', output_filepath: '/user') }
22
+ before do
23
+ subject.file_path_generator = filepath
24
+ end
25
25
 
26
- before do
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(:content) { 'Directly set content' }
41
- let(:exhaler) { mock('exhaler', write!: true) }
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
- it 'allows setting content directly' do
52
- exhaler.should_receive(:write!)
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 'callback :on_success is called' do
59
- smoker.should_receive(:before_write).with(content).and_return(content)
60
- smoker.should_receive(:on_success).with(content)
61
- subject.content = content
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(:error) { raise RuntimeError.new('File Permission Error') }
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
- smoker.should_receive(:on_write_error)
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
- it 'sets the file_path_generator' do
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
- it 'should have the correct content' do
142
- subject.choose_reader
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
- message.should match(/No error encountered/)
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.2
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-01 00:00:00.000000000 Z
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