gmail 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.coveralls.yml +1 -0
  3. data/.gitignore +27 -27
  4. data/.rspec +1 -1
  5. data/.rubocop.yml +53 -13
  6. data/.rubocop_todo.yml +299 -239
  7. data/.travis.yml +19 -19
  8. data/CHANGELOG.md +152 -145
  9. data/Gemfile +5 -3
  10. data/LICENSE +21 -21
  11. data/README.md +380 -355
  12. data/Rakefile +44 -46
  13. data/gmail.gemspec +32 -34
  14. data/lib/gmail.rb +72 -78
  15. data/lib/gmail/client.rb +35 -34
  16. data/lib/gmail/client/base.rb +244 -229
  17. data/lib/gmail/client/plain.rb +24 -24
  18. data/lib/gmail/client/xoauth.rb +67 -68
  19. data/lib/gmail/client/xoauth2.rb +39 -39
  20. data/lib/gmail/imap_extensions.rb +156 -159
  21. data/lib/gmail/labels.rb +80 -79
  22. data/lib/gmail/mailbox.rb +178 -175
  23. data/lib/gmail/message.rb +212 -207
  24. data/lib/gmail/version.rb +3 -3
  25. data/spec/account.yml.example +1 -1
  26. data/spec/account.yml.obfus +2 -2
  27. data/spec/gmail/client/base_spec.rb +5 -5
  28. data/spec/gmail/client/plain_spec.rb +169 -169
  29. data/spec/gmail/client/xoauth2_spec.rb +186 -186
  30. data/spec/gmail/client/xoauth_spec.rb +5 -5
  31. data/spec/gmail/client_spec.rb +5 -5
  32. data/spec/gmail/imap_extensions_spec.rb +47 -47
  33. data/spec/gmail/labels_spec.rb +27 -27
  34. data/spec/gmail/mailbox_spec.rb +84 -84
  35. data/spec/gmail/message_spec.rb +181 -181
  36. data/spec/gmail_spec.rb +40 -39
  37. data/spec/recordings/gmail/_new_connects_with_client_and_give_it_context_when_block_given.yml +61 -28
  38. data/spec/recordings/gmail/_new_connects_with_gmail_service_and_return_valid_connection_object.yml +43 -28
  39. data/spec/recordings/gmail/_new_does_not_raise_error_when_couldn_t_connect_with_given_account.yml +21 -13
  40. data/spec/recordings/gmail/_new_raises_error_when_couldn_t_connect_with_given_account.yml +21 -13
  41. data/spec/recordings/gmail_client_plain/instance/delivers_inline_composed_email.yml +61 -42
  42. data/spec/recordings/gmail_client_plain/instance/does_not_log_in_when_given_gmail_account_is_invalid.yml +21 -13
  43. data/spec/recordings/gmail_client_plain/instance/does_not_raise_error_even_though_gmail_account_is_invalid.yml +21 -13
  44. data/spec/recordings/gmail_client_plain/instance/labels/checks_if_there_is_given_label_defined.yml +409 -196
  45. data/spec/recordings/gmail_client_plain/instance/labels/creates_given_label.yml +280 -151
  46. data/spec/recordings/gmail_client_plain/instance/labels/removes_existing_label.yml +271 -146
  47. data/spec/recordings/gmail_client_plain/instance/labels/returns_list_of_all_available_labels.yml +227 -113
  48. data/spec/recordings/gmail_client_plain/instance/properly_logs_in_to_valid_gmail_account.yml +61 -42
  49. data/spec/recordings/gmail_client_plain/instance/properly_logs_out_from_gmail.yml +61 -42
  50. data/spec/recordings/gmail_client_plain/instance/properly_switches_to_given_mailbox.yml +164 -109
  51. data/spec/recordings/gmail_client_plain/instance/properly_switches_to_given_mailbox_using_block_style.yml +164 -109
  52. data/spec/recordings/gmail_client_plain/instance/raises_error_when_given_gmail_account_is_invalid_and_errors_enabled.yml +21 -13
  53. data/spec/recordings/gmail_client_xo_auth2/instance/does_not_log_in_when_given_gmail_account_is_invalid.yml +24 -13
  54. data/spec/recordings/gmail_client_xo_auth2/instance/labels/checks_if_there_is_given_label_defined.yml +39 -27
  55. data/spec/recordings/gmail_client_xo_auth2/instance/labels/creates_given_label.yml +52 -39
  56. data/spec/recordings/gmail_client_xo_auth2/instance/labels/removes_existing_label.yml +52 -39
  57. data/spec/recordings/gmail_client_xo_auth2/instance/labels/returns_list_of_all_available_labels.yml +39 -27
  58. data/spec/recordings/gmail_client_xo_auth2/instance/properly_logs_in_to_valid_gmail_account.yml +26 -15
  59. data/spec/recordings/gmail_client_xo_auth2/instance/properly_logs_out_from_gmail.yml +26 -15
  60. data/spec/recordings/gmail_client_xo_auth2/instance/properly_switches_to_given_mailbox.yml +63 -40
  61. data/spec/recordings/gmail_client_xo_auth2/instance/properly_switches_to_given_mailbox_using_block_style.yml +63 -40
  62. data/spec/recordings/gmail_client_xo_auth2/instance/raises_error_when_given_gmail_account_is_invalid_and_errors_enabled.yml +24 -13
  63. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/all/localizes_into_the_appropriate_label.yml +229 -116
  64. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/and_the_mailbox_does_not_exist/returns_the_mailbox_name_as_a_string.yml +229 -110
  65. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/drafts/localizes_into_the_appropriate_label.yml +229 -116
  66. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/flagged/localizes_into_the_appropriate_label.yml +229 -116
  67. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/important/localizes_into_the_appropriate_label.yml +229 -116
  68. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/inbox/localizes_into_the_appropriate_label.yml +61 -42
  69. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/junk/localizes_into_the_appropriate_label.yml +229 -116
  70. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/sent/localizes_into_the_appropriate_label.yml +229 -116
  71. data/spec/recordings/gmail_labels/localize/when_given_the_xl_is_tflag/trash/localizes_into_the_appropriate_label.yml +229 -116
  72. data/spec/recordings/gmail_mailbox/instance/counts_all_emails.yml +595 -277
  73. data/spec/recordings/gmail_mailbox/instance/finds_messages.yml +1049 -586
  74. data/spec/recordings/gmail_mailbox/instance/waits_once.yml +191 -136
  75. data/spec/recordings/gmail_mailbox/instance/waits_repeatedly.yml +217 -141
  76. data/spec/recordings/gmail_mailbox/instance/waits_with_29-minute_re-issue.yml +188 -136
  77. data/spec/recordings/gmail_mailbox/instance/waits_with_an_unblocked_connection.yml +644 -207
  78. data/spec/recordings/gmail_mailbox/on_initialize/sets_client_and_name.yml +61 -42
  79. data/spec/recordings/gmail_mailbox/on_initialize/works_in_inbox_by_default.yml +61 -42
  80. data/spec/recordings/gmail_message/initialize/sets_prefetch_attrs.yml +1068 -578
  81. data/spec/recordings/gmail_message/initialize/sets_uid_and_mailbox.yml +1068 -580
  82. data/spec/recordings/gmail_message/instance_methods/deletes_itself.yml +1084 -637
  83. data/spec/recordings/gmail_message/instance_methods/marks_itself_read.yml +1137 -682
  84. data/spec/recordings/gmail_message/instance_methods/marks_itself_unread.yml +1153 -686
  85. data/spec/recordings/gmail_message/instance_methods/moves_from_one_tag_to_other.yml +1447 -862
  86. data/spec/recordings/gmail_message/instance_methods/removes_a_given_label.yml +1288 -776
  87. data/spec/recordings/gmail_message/instance_methods/removes_a_given_label_with_old_method.yml +1288 -776
  88. data/spec/recordings/gmail_message/instance_methods/sets_given_label.yml +1165 -690
  89. data/spec/recordings/gmail_message/instance_methods/sets_given_label_with_old_method.yml +1157 -691
  90. data/spec/spec_helper.rb +56 -53
  91. data/spec/support/imap_mock.rb +181 -181
  92. data/spec/support/obfuscation.rb +49 -52
  93. metadata +21 -90
  94. data/spec/recordings/gmail_client_plain/instance/_connection_automatically_logs_in_to_gmail_account_when_it_s_called.yml +0 -42
@@ -1,53 +1,56 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
-
3
- require 'rubygems'
4
- require 'rspec'
5
- require 'yaml'
6
- require 'gmail'
7
-
8
- # require_support_files
9
- Dir[File.join(File.dirname(__FILE__), 'support', '*.rb')].each { |f| require f }
10
-
11
- RSpec.configure do |config|
12
- Spec::ImapMock.configure_rspec!(config)
13
- end
14
-
15
- def within_gmail(&block)
16
- Gmail.connect!(*TEST_ACCOUNT) do |gmail|
17
- yield(gmail)
18
- end
19
- end
20
-
21
- def mock_client(&block)
22
- client = Gmail::Client::Plain.new(*TEST_ACCOUNT)
23
- client.connect
24
-
25
- if block_given?
26
- client.login
27
- yield client
28
- client.logout
29
- end
30
-
31
- client
32
- end
33
-
34
- def mock_mailbox(box = "INBOX", &block)
35
- within_gmail do |gmail|
36
- mailbox = gmail.mailbox(box)
37
- yield(mailbox) if block_given?
38
- mailbox
39
- end
40
- end
41
-
42
- # TODO: move this to it's own dir; get rid of global variable
43
- # Run test by creating your own test account with credentials in account.yml
44
- # Otherwise default credentials from an obfuscated file are used.
45
- clear_file = File.join(File.dirname(__FILE__), 'account.yml')
46
- obfus_file = File.join(File.dirname(__FILE__), 'account.yml.obfus')
47
- if File.exist?(clear_file)
48
- TEST_ACCOUNT = YAML.load_file(clear_file)
49
- elsif File.exist?(obfus_file)
50
- TEST_ACCOUNT = Spec::Obfuscation.decrypt_file(obfus_file)
51
- else
52
- raise 'account.yml file not found'
53
- end
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require 'rubygems'
4
+ require 'rspec'
5
+ require 'yaml'
6
+ require 'gmail'
7
+ require 'coveralls'
8
+
9
+ Coveralls.wear!
10
+
11
+ # require_support_files
12
+ Dir[File.join(File.dirname(__FILE__), 'support', '*.rb')].each { |f| require f }
13
+
14
+ RSpec.configure do |config|
15
+ Spec::ImapMock.configure_rspec!(config)
16
+ end
17
+
18
+ def within_gmail(&block)
19
+ Gmail.connect!(*TEST_ACCOUNT) do |gmail|
20
+ yield(gmail)
21
+ end
22
+ end
23
+
24
+ def mock_client(&block)
25
+ client = Gmail::Client::Plain.new(*TEST_ACCOUNT)
26
+ client.connect
27
+
28
+ if block_given?
29
+ client.login
30
+ yield client
31
+ client.logout
32
+ end
33
+
34
+ client
35
+ end
36
+
37
+ def mock_mailbox(box = "INBOX", &block)
38
+ within_gmail do |gmail|
39
+ mailbox = gmail.mailbox(box)
40
+ yield(mailbox) if block_given?
41
+ mailbox
42
+ end
43
+ end
44
+
45
+ # TODO: move this to it's own dir; get rid of global variable
46
+ # Run test by creating your own test account with credentials in account.yml
47
+ # Otherwise default credentials from an obfuscated file are used.
48
+ clear_file = File.join(File.dirname(__FILE__), 'account.yml')
49
+ obfus_file = File.join(File.dirname(__FILE__), 'account.yml.obfus')
50
+ if File.exist?(clear_file)
51
+ TEST_ACCOUNT = YAML.load_file(clear_file)
52
+ elsif File.exist?(obfus_file)
53
+ TEST_ACCOUNT = Spec::Obfuscation.decrypt_file(obfus_file)
54
+ else
55
+ raise 'account.yml file not found'
56
+ end
@@ -1,181 +1,181 @@
1
- module Net
2
- class IMAP
3
- class << self
4
- def recordings=(value)
5
- @replaying = !value.nil?
6
- @recordings = value
7
- end
8
-
9
- def recordings
10
- @recordings ||= {}
11
- end
12
-
13
- def replaying?
14
- @replaying
15
- end
16
- end
17
-
18
- alias_method :_idle, :idle
19
- alias_method :_idle_done, :idle_done
20
-
21
- def idle(&response_handler)
22
- if Net::IMAP.replaying?
23
- @idle_done_cond = new_cond
24
- @idle_done = false
25
- end
26
-
27
- response = mock_command(:_idle, 'IDLE', &response_handler)
28
-
29
- if Net::IMAP.replaying?
30
- synchronize do
31
- unless @idle_done
32
- @idle_done_cond.wait(0.1)
33
- raise('The IDLE has not done') unless @idle_done
34
- end
35
- @idle_done_cond = nil
36
- end
37
- end
38
-
39
- response
40
- end
41
-
42
- def idle_done
43
- if Net::IMAP.replaying?
44
- synchronize do
45
- if @idle_done_cond.nil?
46
- raise Net::IMAP::Error, 'not during IDLE'
47
- end
48
- @idle_done = true
49
- idle_done_cond.signal
50
- end
51
- else
52
- _idle_done
53
- end
54
- end
55
-
56
- private
57
-
58
- alias_method :_send_command, :send_command
59
-
60
- def self.force_utf8(data)
61
- case data.class.to_s
62
- when /String/
63
- data.force_encoding('utf-8')
64
- when /Hash/
65
- data.each { |k, v| data[k] = force_utf8(v) }
66
- when /Array/
67
- data.map { |s| force_utf8(s) }
68
- end
69
- end
70
-
71
- def send_command(cmd, *args, &block)
72
- mock_command(:_send_command, cmd, *args, &block)
73
- end
74
-
75
- def mock_command(method, cmd, *args, &block)
76
- # In Ruby 1.9.x, strings default to binary which causes the digest to be
77
- # different.
78
- clean_args = args.dup.each do |s|
79
- Net::IMAP.force_utf8(s)
80
- end
81
-
82
- yaml_dump = YAML.dump([cmd] + clean_args)
83
-
84
- if RUBY_VERSION =~ /^(1.9|2.0)/
85
- # From 1.9 to 2.0 to 2.1, the way YAML encodes special characters changed.
86
- # Here's what each returns for: YAML.dump(["", "%"])
87
- # 1.9.x: "---\n- ''\n- ! '%'\n"
88
- # 2.0.x: "---\n- ''\n- '%'\n"
89
- # 2.1.x: "---\n- ''\n- \"%\"\n"
90
- # The `gsub` here converts the older format into the 2.1.x.
91
- yaml_dump.gsub!(/(?:! )?'(.+)'/, '"\1"')
92
-
93
- # In 1.9 and 2.0 strings starting with `+` or `-` are not escaped in quotes, but
94
- # they are in 2.1+. This addresses that.
95
- yaml_dump.gsub!(/ ([+-](?:X-GM-\w+|FLAGS))/, ' "\1"')
96
-
97
- # In 1.9 and 2.0 strings starting with `\` are not escaped in quotes, but
98
- # they are in 2.1+. This addresses that. Yes we need all those backslashes :|
99
- yaml_dump.gsub!(/ \\(\w+)/, ' "\\\\\\\\\1"')
100
- end
101
-
102
- digest = "#{cmd}-#{Digest::MD5.hexdigest(yaml_dump)}"
103
-
104
- if Net::IMAP.replaying?
105
- recordings = Net::IMAP.recordings[digest] || []
106
- if recordings.empty?
107
- # Be lenient if LOGOUT is called but wasn't explicitly recorded. This
108
- # comes up often when called from `at_exit`.
109
- cmd == 'LOGOUT' ? return : raise('Could not find recording')
110
- end
111
-
112
- action, response, @responses, all_responses = recordings.shift
113
-
114
- if block && all_responses
115
- all_responses.each do |resp|
116
- block.call(resp)
117
- end
118
- end
119
- else
120
- action = :return
121
- all_responses = []
122
- begin
123
- response = send(method, cmd, *args) do |resp|
124
- all_responses << resp
125
- block.call(resp)
126
- end
127
- rescue => e
128
- action = :raise
129
- response = e
130
- end
131
-
132
- # @responses (the third argument here) contains untagged responses captured
133
- # via the Net::IMAP#record_response method.
134
- Net::IMAP.recordings[digest] ||= []
135
- Net::IMAP.recordings[digest] << [action, response.dup, @responses ? @responses.dup : nil, all_responses]
136
- end
137
-
138
- raise(response) if action == :raise
139
-
140
- response
141
- end
142
- end
143
- end
144
-
145
- module Spec
146
- module ImapMock
147
- # Configures RSpec with an around(:each) block to use IMAP mocks
148
- def self.configure_rspec!(config)
149
- config.around(:each) do |example|
150
- Spec::ImapMock.run_rspec_example(example)
151
- end
152
- end
153
-
154
- # Run an RSpec example using IMAP mocks
155
- def self.run_rspec_example(example)
156
- # The path is determined by the rspec `describe`s and `context`s
157
- mock_path = example.example_group.to_s
158
- .gsub(/RSpec::ExampleGroups::/, '')
159
- .gsub(/(\w)([A-Z])/, '\1_\2')
160
- .gsub(/::/, '/')
161
- .downcase
162
-
163
- # The name is determined by the description of the example.
164
- mock_name = example.description.gsub(/[^\w\-\/]+/, '_').downcase
165
-
166
- filename = File.join('spec/recordings/', mock_path, "#{mock_name}.yml")
167
-
168
- # If we've already recorded this spec load the recordings
169
- Net::IMAP.recordings = File.exist?(filename) ? YAML.load_file(filename) : nil
170
-
171
- example.run
172
-
173
- # If we haven't yet recorded the spec and there were some recordings,
174
- # write them to a file.
175
- unless File.exist?(filename) or Net::IMAP.recordings.empty?
176
- FileUtils.mkdir_p(File.dirname(filename))
177
- File.open(filename, 'w') { |f| YAML.dump(Net::IMAP.recordings, f) }
178
- end
179
- end
180
- end
181
- end
1
+ module Net
2
+ class IMAP
3
+ class << self
4
+ def recordings=(value)
5
+ @replaying = !value.nil?
6
+ @recordings = value
7
+ end
8
+
9
+ def recordings
10
+ @recordings ||= {}
11
+ end
12
+
13
+ def replaying?
14
+ @replaying
15
+ end
16
+
17
+ def force_utf8(data)
18
+ case data.class.to_s
19
+ when /String/
20
+ data.force_encoding('utf-8')
21
+ when /Hash/
22
+ data.each { |k, v| data[k] = force_utf8(v) }
23
+ when /Array/
24
+ data.map { |s| force_utf8(s) }
25
+ end
26
+ end
27
+ end
28
+
29
+ alias_method :_idle, :idle
30
+ alias_method :_idle_done, :idle_done
31
+
32
+ def idle(&response_handler)
33
+ if Net::IMAP.replaying?
34
+ @idle_done_cond = new_cond
35
+ @idle_done = false
36
+ end
37
+
38
+ response = mock_command(:_idle, 'IDLE', &response_handler)
39
+
40
+ if Net::IMAP.replaying?
41
+ synchronize do
42
+ unless @idle_done
43
+ @idle_done_cond.wait(0.1)
44
+ raise('The IDLE has not done') unless @idle_done
45
+ end
46
+ @idle_done_cond = nil
47
+ end
48
+ end
49
+
50
+ response
51
+ end
52
+
53
+ def idle_done
54
+ if Net::IMAP.replaying?
55
+ synchronize do
56
+ if @idle_done_cond.nil?
57
+ raise Net::IMAP::Error, 'not during IDLE'
58
+ end
59
+ @idle_done = true
60
+ idle_done_cond.signal
61
+ end
62
+ else
63
+ _idle_done
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ alias_method :_send_command, :send_command
70
+
71
+ def send_command(cmd, *args, &block)
72
+ mock_command(:_send_command, cmd, *args, &block)
73
+ end
74
+
75
+ def mock_command(method, cmd, *args, &block)
76
+ # In Ruby 1.9.x, strings default to binary which causes the digest to be
77
+ # different.
78
+ clean_args = args.dup.each do |s|
79
+ Net::IMAP.force_utf8(s)
80
+ end
81
+
82
+ yaml_dump = YAML.dump([cmd] + clean_args)
83
+
84
+ if RUBY_VERSION =~ /^(1.9|2.0)/
85
+ # From 1.9 to 2.0 to 2.1, the way YAML encodes special characters changed.
86
+ # Here's what each returns for: YAML.dump(["", "%"])
87
+ # 1.9.x: "---\n- ''\n- ! '%'\n"
88
+ # 2.0.x: "---\n- ''\n- '%'\n"
89
+ # 2.1.x: "---\n- ''\n- \"%\"\n"
90
+ # The `gsub` here converts the older format into the 2.1.x.
91
+ yaml_dump.gsub!(/(?:! )?'(.+)'/, '"\1"')
92
+
93
+ # In 1.9 and 2.0 strings starting with `+` or `-` are not escaped in quotes, but
94
+ # they are in 2.1+. This addresses that.
95
+ yaml_dump.gsub!(/ ([+-](?:X-GM-\w+|FLAGS))/, ' "\1"')
96
+
97
+ # In 1.9 and 2.0 strings starting with `\` are not escaped in quotes, but
98
+ # they are in 2.1+. This addresses that. Yes we need all those backslashes :|
99
+ yaml_dump.gsub!(/ \\(\w+)/, ' "\\\\\\\\\1"')
100
+ end
101
+
102
+ digest = "#{cmd}-#{Digest::MD5.hexdigest(yaml_dump)}"
103
+
104
+ if Net::IMAP.replaying?
105
+ recordings = Net::IMAP.recordings[digest] || []
106
+ if recordings.empty?
107
+ # Be lenient if LOGOUT is called but wasn't explicitly recorded. This
108
+ # comes up often when called from `at_exit`.
109
+ cmd == 'LOGOUT' ? return : raise('Could not find recording')
110
+ end
111
+
112
+ action, response, @responses, all_responses = recordings.shift
113
+
114
+ if block && all_responses
115
+ all_responses.each do |resp|
116
+ yield(resp)
117
+ end
118
+ end
119
+ else
120
+ action = :return
121
+ all_responses = []
122
+ begin
123
+ args.unshift(cmd) if method==:_send_command
124
+ response = send(method, *args) do |resp|
125
+ all_responses << resp
126
+ yield(resp)
127
+ end
128
+ rescue StandardError => e
129
+ action = :raise
130
+ response = e
131
+ end
132
+
133
+ # @responses (the third argument here) contains untagged responses captured
134
+ # via the Net::IMAP#record_response method.
135
+ Net::IMAP.recordings[digest] ||= []
136
+ Net::IMAP.recordings[digest] << [action, response.dup, @responses ? @responses.dup : nil, all_responses]
137
+ end
138
+
139
+ raise(response) if action == :raise
140
+
141
+ response
142
+ end
143
+ end
144
+ end
145
+
146
+ module Spec
147
+ module ImapMock
148
+ # Configures RSpec with an around(:each) block to use IMAP mocks
149
+ def self.configure_rspec!(config)
150
+ config.around(:each) do |example|
151
+ Spec::ImapMock.run_rspec_example(example)
152
+ end
153
+ end
154
+
155
+ # Run an RSpec example using IMAP mocks
156
+ def self.run_rspec_example(example)
157
+ # The path is determined by the rspec `describe`s and `context`s
158
+ mock_path = example.example_group.to_s
159
+ .gsub(/RSpec::ExampleGroups::/, '')
160
+ .gsub(/(\w)([A-Z])/, '\1_\2')
161
+ .gsub(/::/, '/')
162
+ .downcase
163
+
164
+ # The name is determined by the description of the example.
165
+ mock_name = example.description.gsub(/[^\w\-\/]+/, '_').downcase
166
+
167
+ filename = File.join('spec/recordings/', mock_path, "#{mock_name}.yml")
168
+
169
+ # If we've already recorded this spec load the recordings
170
+ Net::IMAP.recordings = File.exist?(filename) ? YAML.load_file(filename) : nil
171
+
172
+ example.run
173
+
174
+ # If we haven't yet recorded the spec and there were some recordings,
175
+ # write them to a file.
176
+ return if File.exist?(filename) or Net::IMAP.recordings.empty?
177
+ FileUtils.mkdir_p(File.dirname(filename))
178
+ File.open(filename, 'w') { |f| YAML.dump(Net::IMAP.recordings, f) }
179
+ end
180
+ end
181
+ end