runoff 0.2.0 → 0.3.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.
@@ -1,132 +1,53 @@
1
- require 'zip/zip'
2
-
3
- module Runoff
4
- # Public: Methods used for writing to files.
5
- module FileWriter
6
- # Public: Saves a single chat message to a file.
7
- #
8
- # chat_record - a Hash containing data about a single chat message.
9
- # output_directory - A String with the path to the directory, wher the file will be saved.
10
- #
11
- # Examples
12
- #
13
- # save_to_file record, '/home/username/skype_backup'
14
- def save_to_file(chat_record, output_directory)
15
- filename = "#{output_directory}/#{parse_chatname chat_record[:chatname]}.txt"
16
-
17
- Dir.mkdir(output_directory) unless File.exists?(output_directory)
18
- File.open(filename, 'a') do |file|
19
- file.puts build_entry(chat_record)
20
- end
21
-
22
- filename
23
- rescue StandardError
24
- puts 'An error occured while saving a file'
25
- end
26
-
27
- # Public: Creates a String with one entry from the chat.
28
- #
29
- # chat_record - a Hash containing data about a single chat message.
30
- #
31
- # Examples
32
- #
33
- # build_entry { timestamp: 213213232, from_dispname: 'aidzis', body_xml: 'This is text.' }
34
- # # => [2013-04-13 14:23:57] aidzis: This is text.
35
- #
36
- # Returns a String with a chat entry.
37
- def build_entry(chat_record)
38
- datetime = Time.at chat_record[:timestamp]
39
- output_record = "[#{datetime.strftime "%Y-%m-%d %H:%M:%S"}] "
40
- output_record << "#{chat_record[:from_dispname]}: #{parse_body_xml chat_record[:body_xml]}"
41
-
42
- output_record
43
- end
44
-
45
- # Public: Converts chatname from database to a valid file name.
46
- #
47
- # raw_chatname - A String with a chatname read from the database.
48
- #
49
- # Examples
50
- #
51
- # parse_chatname '#someone/$someone_else;521357125362'
52
- # # => someone-someone_else.txt
53
- #
54
- # Returns a String with a valid file name.
55
- def parse_chatname(raw_chatname)
56
- match = raw_chatname.match(/#(.+)\/\$(.+);|#(.+)\/\$/)
57
- first_part, second_part, third_part = match.captures
58
- chatname = "#{first_part}-#{second_part}-#{third_part}"
59
-
60
- trim_dashes chatname
61
- end
62
-
63
- # Public: Removes extra characters from the end of a chatname.
64
- #
65
- # raw_chatname - A String with a chatname read from the database
66
- #
67
- # Examples
68
- #
69
- # partly_parse_chatname '#someone/$someone_else;521357125362'
70
- # # => #someone/$someone_else;
71
- #
72
- # Returns a String with a chatname without extra characters at the end.
73
- def partly_parse_chatname(raw_chatname)
74
- match = raw_chatname.match(/(#.+\/\$.+;)|(#.+\/\$)/)
75
- first_group, second_group = match.captures
76
-
77
- first_group || second_group
78
- end
79
-
80
- # Public: Removes unnecessary dashes from the begining and the end of the string.
81
- #
82
- # string - A String possibly containing dashes at the beggining or the end
83
- #
84
- # Examples
85
- #
86
- # str = '--example-'
87
- # trim_dashes str
88
- # # => example
89
- #
90
- # Returns a string without leading and ending dashes.
91
- def trim_dashes(string)
92
- clean_string = string.gsub(/^-+/, '')
93
- clean_string.gsub(/-+$/, '')
94
- end
95
-
96
- # Public: Remove Skype emotion tags.
97
- #
98
- # text - String containing XML data
99
- #
100
- # Examples
101
- #
102
- # parse_body_xml "Some text <ss type="laugh">:D</ss>"
103
- # # => "Some text :D"
104
- #
105
- # Returns the duplicated String.
106
- def parse_body_xml(text)
107
- clean_text = text.gsub(/<ss type=".+">/, '')
108
- clean_text.gsub(/<\/ss>/, '')
109
- end
110
-
111
- # Public: Compresses all the exported files into a Zip archive.
112
- #
113
- # output_directory - A String with the path to the directory, wher the file will be saved.
114
- #
115
- # Examples
116
- #
117
- # archive '/home/username/skype-backup'
118
- def archive(output_directory)
119
- timestamp = Time.now.strftime "%Y%m%d%H%M%S"
120
-
121
- Zip::ZipFile.open "#{output_directory}-#{timestamp}.zip", Zip::ZipFile::CREATE do |zipfile|
122
- Dir.entries(output_directory).each do |file|
123
- if File.file?("#{output_directory}/#{file}")
124
- zipfile.add file, "#{output_directory}/#{file}"
125
- end
126
- end
127
- end
128
-
129
- FileUtils.rm_rf output_directory
130
- end
131
- end
132
- end
1
+ require 'zip/zip'
2
+ require 'fileutils'
3
+
4
+ module Runoff
5
+ # Public: Methods used for writing to files.
6
+ class FileWriter
7
+ # Public: Initialize a FileWriter object.
8
+ #
9
+ # format - An object containing necessary methods for data formating.
10
+ def initialize(format)
11
+ @format = format
12
+ end
13
+ # Public: Saves a single chat message to a file.
14
+ #
15
+ # record - a Hash containing data about a single chat message.
16
+ # output_directory - A String with the path to the directory, wher the file will be saved.
17
+ #
18
+ # Examples
19
+ #
20
+ # save_to_file record, '/home/username/skype_backup'
21
+ def save_to_file(record, output_directory)
22
+ filename = "#{output_directory}/#{@format.get_filename(record)}"
23
+
24
+ Dir.mkdir(output_directory) unless File.exists?(output_directory)
25
+ File.open(filename, 'a') do |file|
26
+ file.puts @format.build_entry(record)
27
+ end
28
+
29
+ filename
30
+ end
31
+
32
+ # Public: Compresses all the exported files into a Zip archive.
33
+ #
34
+ # output_directory - A String with the path to the directory, wher the file will be saved.
35
+ #
36
+ # Examples
37
+ #
38
+ # archive '/home/username/skype-backup'
39
+ def self.archive(output_directory)
40
+ timestamp = Time.now.strftime "%Y%m%d%H%M%S"
41
+
42
+ Zip::ZipFile.open "#{output_directory}-#{timestamp}.zip", Zip::ZipFile::CREATE do |zipfile|
43
+ Dir.entries(output_directory).each do |file|
44
+ if File.file?("#{output_directory}/#{file}")
45
+ zipfile.add file, "#{output_directory}/#{file}"
46
+ end
47
+ end
48
+ end
49
+
50
+ FileUtils.rm_rf output_directory
51
+ end
52
+ end
53
+ end
@@ -1,61 +1,40 @@
1
- module Runoff
2
-
3
- # Public: Contains class methods for finding out the appropriate file paths.
4
- #
5
- # Examples
6
- #
7
- # Location::default_skype_data_location skype_username
8
- # # => /home/user/.Skype/skype_username/main.db
9
- class Location
10
-
11
- # Public: Composes the default Skype database location depending on the operating system.
12
- #
13
- # skype_username - A String that contains a username of the Skype account,
14
- # which database we want to access.
15
- #
16
- # Examples
17
- #
18
- # On Linux:
19
- # default_skype_data_location skype_username
20
- # # => /home/user/.Skype/skype_username/mai.db
21
- #
22
- # On Windows:
23
- # default_skype_data_location skype_username
24
- # # => /Users/user/AppData/Roaming/Skype/skype_username/main.db
25
- #
26
- # Returns a String that contains the path to the Skype database file.
27
- def self.default_skype_data_location(skype_username)
28
- if RbConfig::CONFIG['host_os'] =~ /mingw/
29
- location = "#{ENV['APPDATA']}\\Skype\\#{skype_username}\\main.db"
30
-
31
- location.gsub!(/\\/, '/')
32
- location.gsub(/^[a-zA-Z]:/, '')
33
- elsif RbConfig::CONFIG['host_os'] =~ /linux/
34
- "#{ENV['HOME']}/.Skype/#{skype_username}/main.db"
35
- else
36
- "#{ENV['HOME']}/Library/Application Support/Skype/#{skype_username}/main.db"
37
- end
38
- end
39
-
40
- # Public: Clarifies the path to the user's home directory depending on the operating system
41
- #
42
- # Examples
43
- #
44
- # On Linux:
45
- # home_path
46
- # # => /home/user
47
- #
48
- # On Windows:
49
- # home_path
50
- # # => C:\Users\user
51
- #
52
- # Returns a String that contains the path to the user's home directory.
53
- def self.home_path
54
- if RbConfig::CONFIG['host_os'] =~ /mingw/
55
- ENV['USERPROFILE']
56
- else
57
- ENV['HOME']
58
- end
59
- end
60
- end
1
+ module Runoff
2
+
3
+ # Public: Contains class methods for finding out the appropriate file paths.
4
+ #
5
+ # Examples
6
+ #
7
+ # Location::default_skype_data_location skype_username
8
+ # # => /home/user/.Skype/skype_username/main.db
9
+ class Location
10
+
11
+ # Public: Composes the default Skype database location depending on the operating system.
12
+ #
13
+ # skype_username - A String that contains a username of the Skype account,
14
+ # which database we want to access.
15
+ #
16
+ # Examples
17
+ #
18
+ # On Linux:
19
+ # default_skype_data_location skype_username
20
+ # # => /home/user/.Skype/skype_username/mai.db
21
+ #
22
+ # On Windows:
23
+ # default_skype_data_location skype_username
24
+ # # => /Users/user/AppData/Roaming/Skype/skype_username/main.db
25
+ #
26
+ # Returns a String that contains the path to the Skype database file.
27
+ def self.default_skype_data_location(skype_username)
28
+ if RbConfig::CONFIG['host_os'] =~ /mingw/
29
+ location = "#{ENV['APPDATA']}\\Skype\\#{skype_username}\\main.db"
30
+
31
+ location.gsub!(/\\/, '/')
32
+ location.gsub(/^[a-zA-Z]:/, '')
33
+ elsif RbConfig::CONFIG['host_os'] =~ /linux/
34
+ "#{ENV['HOME']}/.Skype/#{skype_username}/main.db"
35
+ else
36
+ "#{ENV['HOME']}/Library/Application Support/Skype/#{skype_username}/main.db"
37
+ end
38
+ end
39
+ end
61
40
  end
@@ -0,0 +1,104 @@
1
+ module Runoff
2
+ # Public: Deals with Skype specific formats.
3
+ class SkypeDataFormat
4
+ #Public: Builds an appropriate filename.
5
+ #
6
+ # chat_record - A Hash containing necessary data.
7
+ #
8
+ # Examples
9
+ #
10
+ # get_filename { chatname: 'demo-chat' }
11
+ # # => demo-chat.txt
12
+ #
13
+ # Retruns a filename as a String.
14
+ def get_filename(chat_record)
15
+ "#{parse_chatname chat_record[:chatname]}.txt"
16
+ end
17
+
18
+ # Public: Creates a String with one entry from the chat.
19
+ #
20
+ # chat_record - a Hash containing data about a single chat message.
21
+ #
22
+ # Examples
23
+ #
24
+ # build_entry { timestamp: 213213232, from_dispname: 'aidzis', body_xml: 'This is text.' }
25
+ # # => [2013-04-13 14:23:57] aidzis: This is text.
26
+ #
27
+ # Returns a String with a chat entry.
28
+ def build_entry(chat_record)
29
+ datetime = Time.at chat_record[:timestamp]
30
+ output_record = "[#{datetime.strftime "%Y-%m-%d %H:%M:%S"}] "
31
+ output_record << "#{chat_record[:from_dispname]}: #{parse_body_xml chat_record[:body_xml]}"
32
+
33
+ output_record
34
+ rescue StandardError
35
+ end
36
+
37
+ # Public: Converts chatname from database to a valid file name.
38
+ #
39
+ # raw_chatname - A String with a chatname read from the database.
40
+ #
41
+ # Examples
42
+ #
43
+ # parse_chatname '#someone/$someone_else;521357125362'
44
+ # # => someone-someone_else.txt
45
+ #
46
+ # Returns a String with a valid file name.
47
+ def parse_chatname(raw_chatname)
48
+ match = raw_chatname.match(/#(.+)\/\$(.+);|#(.+)\/\$/)
49
+ first_part, second_part, third_part = match.captures
50
+ chatname = "#{first_part}-#{second_part}-#{third_part}"
51
+
52
+ trim_dashes chatname
53
+ end
54
+
55
+ # Public: Removes extra characters from the end of a chatname.
56
+ #
57
+ # raw_chatname - A String with a chatname read from the database
58
+ #
59
+ # Examples
60
+ #
61
+ # partly_parse_chatname '#someone/$someone_else;521357125362'
62
+ # # => #someone/$someone_else;
63
+ #
64
+ # Returns a String with a chatname without extra characters at the end.
65
+ def partly_parse_chatname(raw_chatname)
66
+ match = raw_chatname.match(/(#.+\/\$.+;)|(#.+\/\$)/)
67
+ first_group, second_group = match.captures
68
+
69
+ first_group || second_group
70
+ end
71
+
72
+ private
73
+ # Internal: Remove Skype emotion tags.
74
+ #
75
+ # text - String containing XML data
76
+ #
77
+ # Examples
78
+ #
79
+ # parse_body_xml "Some text <ss type="laugh">:D</ss>"
80
+ # # => "Some text :D"
81
+ #
82
+ # Returns the duplicated String.
83
+ def parse_body_xml(text)
84
+ clean_text = text.gsub(/<ss type=".+">/, '')
85
+ clean_text.gsub(/<\/ss>/, '')
86
+ end
87
+
88
+ # Internal: Removes unnecessary dashes from the begining and the end of the string.
89
+ #
90
+ # string - A String possibly containing dashes at the beggining or the end
91
+ #
92
+ # Examples
93
+ #
94
+ # str = '--example-'
95
+ # trim_dashes str
96
+ # # => example
97
+ #
98
+ # Returns a string without leading and ending dashes.
99
+ def trim_dashes(string)
100
+ clean_string = string.gsub(/^-+/, '')
101
+ clean_string.gsub(/-+$/, '')
102
+ end
103
+ end
104
+ end
@@ -1,3 +1,3 @@
1
- module Runoff
2
- VERSION = '0.2.0'
1
+ module Runoff
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/runoff.rb CHANGED
@@ -1,7 +1,10 @@
1
- $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
-
3
- require "runoff/version"
4
- require "runoff/location"
5
- require "runoff/file_writer"
6
- require "runoff/composition"
7
- require "runoff/source"
1
+ $:.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
2
+
3
+ require "runoff/version"
4
+ require "runoff/location"
5
+ require "runoff/skype_data_format"
6
+ require "runoff/file_writer"
7
+ require "runoff/composition"
8
+ require "runoff/commands/command"
9
+ require "runoff/commands/all"
10
+ require "runoff/commands/chat"
data/runoff.gemspec CHANGED
@@ -1,26 +1,26 @@
1
- # -*- encoding: utf-8 -*-
2
- lib = File.expand_path('../lib', __FILE__)
3
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
-
5
- require 'runoff/version'
6
-
7
- Gem::Specification.new do |gem|
8
- gem.name = "runoff"
9
- gem.version = Runoff::VERSION
10
- gem.authors = ["Aigars Dzerviniks"]
11
- gem.email = ["dzerviniks.aigars@outlook.com"]
12
- gem.description = %q{runoff provides functionality to export all the Skype chat history or only specified chats from the Skype SQLite database file to text files}
13
- gem.summary = %q{Tool to export Skype chat history from the SQLite database to text files}
14
- gem.homepage = 'https://github.com/aidzis/runoff'
15
- gem.license = 'MIT'
16
-
17
- gem.files = `git ls-files`.split($/)
18
- gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
- gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
- gem.require_paths = ["lib"]
21
-
22
- gem.add_dependency 'thor'
23
- gem.add_dependency 'sqlite3'
24
- gem.add_dependency 'sequel'
25
- gem.add_dependency 'rubyzip'
26
- end
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ require 'runoff/version'
6
+
7
+ Gem::Specification.new do |gem|
8
+ gem.name = "runoff"
9
+ gem.version = Runoff::VERSION
10
+ gem.authors = ["Aigars Dzerviniks"]
11
+ gem.email = ["dzerviniks.aigars@outlook.com"]
12
+ gem.description = %q{runoff provides functionality to export all the Skype chat history or only specified chats from the Skype SQLite database file to text files}
13
+ gem.summary = %q{Tool to export Skype chat history from the SQLite database to text files}
14
+ gem.homepage = 'https://github.com/aidzis/runoff'
15
+ gem.license = 'MIT'
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+
22
+ gem.add_dependency 'commander'
23
+ gem.add_dependency 'sequel'
24
+ gem.add_dependency 'rubyzip'
25
+ gem.add_dependency 'sqlite3'
26
+ end
data/test/all_test.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'runoff'
4
+
5
+ describe Runoff::Commands::All do
6
+ it 'must output an error message if no username or --from option is provided' do
7
+ ->{ Runoff::Commands::All.process [] }.must_output "You must specify a username or a --from option\n"
8
+ end
9
+
10
+ it 'must output result of the command' do
11
+ ->{ Runoff::Commands::All.process [], {
12
+ from: 'test/test_db.sqlite', destination: 'test/tmp' }
13
+ }.must_output "Finished: 2 files were exported\n"
14
+ end
15
+
16
+ it 'must put exported files into an archive' do
17
+ capture_io { Runoff::Commands::All.process [], { from: 'test/test_db.sqlite', destination: 'test/tmp' } }
18
+
19
+ Dir["test/*.zip"].length.must_equal 1
20
+
21
+ Dir.glob('test/*.zip').each do |archive|
22
+ File.delete archive
23
+ end
24
+ end
25
+ end
data/test/chat_test.rb ADDED
@@ -0,0 +1,9 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'runoff'
4
+
5
+ describe Runoff::Commands::Chat do
6
+ it 'must output an error message if no username or --from option is provided' do
7
+ ->{ Runoff::Commands::Chat.process [] }.must_output "You must specify a username or a --from option\n"
8
+ end
9
+ end
@@ -0,0 +1,29 @@
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'runoff'
4
+
5
+ describe Runoff::Commands::Command do
6
+ it 'must create a Composition object based on the Skype username or a specific path' do
7
+ Runoff::Commands::Command.send(:get_composition, nil, 'test/test_db.sqlite').must_be_instance_of Runoff::Composition
8
+ end
9
+
10
+ it 'must return a default destination path if no optional path is specified' do
11
+ Runoff::Commands::Command.send(:get_destination, nil).must_equal "#{ENV['HOME']}/skype-backup"
12
+ end
13
+
14
+ it 'must return a custom path if an optional path variable is specified' do
15
+ path = Runoff::Commands::Command.send(:get_destination, 'test/test_db.sqlite')
16
+ path.must_equal 'test/test_db.sqlite'
17
+ end
18
+
19
+ it 'must output correct message based on the exported items count' do
20
+ ->{ Runoff::Commands::Command.send(:print_result, 1) }.must_output "Finished: 1 file was exported\n"
21
+ ->{ Runoff::Commands::Command.send(:print_result, 2) }.must_output "Finished: 2 files were exported\n"
22
+ end
23
+
24
+ it 'must output a list of available chatnames' do
25
+ chatnames = ['first-chatname', 'second-chatname']
26
+
27
+ ->{ Runoff::Commands::Command.send(:list_chatnames, chatnames) }.must_output "[0] first-chatname\n[1] second-chatname\n\n"
28
+ end
29
+ end
@@ -1,56 +1,53 @@
1
- require 'minitest/spec'
2
- require 'minitest/autorun'
3
- require 'runoff'
4
-
5
- describe Runoff::Composition do
6
- before { @composition = Runoff::Composition.new 'test/test_db.sqlite' }
7
-
8
- it "must raise an IOError if the file that is passed to the constructor doesn't exist" do
9
- ->{ composition = Runoff::Composition.new 'not_existing.db' }.must_raise IOError
10
- end
11
-
12
- it 'must have a getter method for exported filenames' do
13
- @composition.must_respond_to :exported_filenames
14
- end
15
-
16
- it 'must return parsed chatnames together with partly parsed chatnames' do
17
- chatnames, raw_chatnames = @composition.get_chatnames
18
-
19
- chatnames.must_equal ['something-more', 'something-else']
20
- raw_chatnames.must_equal ['#something/$more;', '#something/$else;']
21
- end
22
-
23
- it 'must have a save_to_file method' do
24
- @composition.must_respond_to :save_to_file
25
- end
26
-
27
- it 'must return a count of the exported filenames' do
28
- file_count = @composition.send(
29
- :run_export,
30
- [{
31
- chatname: '#test/$one;7687623',
32
- timestamp: 123123213,
33
- from_dispname: 'Aidzis',
34
- body_xml: ''
35
- }],
36
- 'test/tmp'
37
- )
38
-
39
- file_count.must_equal 1
40
- FileUtils.rm_rf 'test/tmp/.'
41
- end
42
-
43
- it 'must return a count of the exported filenames when called for all chats' do
44
- file_count = @composition.export 'test/tmp'
45
-
46
- file_count.must_equal 2
47
- FileUtils.rm_rf 'test/tmp/.'
48
- end
49
-
50
- it 'must return a count of the exported filenames when called for specific chats' do
51
- file_count = @composition.export_chats ['#something/$more;', '#something/$else;'], 'test/tmp'
52
-
53
- file_count.must_equal 2
54
- FileUtils.rm_rf 'test/tmp/.'
55
- end
1
+ require 'minitest/spec'
2
+ require 'minitest/autorun'
3
+ require 'runoff'
4
+ require 'fileutils'
5
+
6
+ describe Runoff::Composition do
7
+ before { @composition = Runoff::Composition.new 'test/test_db.sqlite' }
8
+
9
+ it "must raise an IOError if the file that is passed to the constructor doesn't exist" do
10
+ ->{ composition = Runoff::Composition.new 'not_existing.db' }.must_raise IOError
11
+ end
12
+
13
+ it 'must have a getter method for exported filenames' do
14
+ @composition.must_respond_to :exported_filenames
15
+ end
16
+
17
+ it 'must return parsed chatnames together with partly parsed chatnames' do
18
+ chatnames, raw_chatnames = @composition.get_chatnames
19
+
20
+ chatnames.must_equal ['something-more', 'something-else']
21
+ raw_chatnames.must_equal ['#something/$more;', '#something/$else;']
22
+ end
23
+
24
+ it 'must return a count of the exported filenames' do
25
+ file_count = @composition.send(
26
+ :run_export,
27
+ [{
28
+ chatname: '#test/$one;7687623',
29
+ timestamp: 123123213,
30
+ from_dispname: 'Aidzis',
31
+ body_xml: ''
32
+ }],
33
+ 'test/tmp'
34
+ )
35
+
36
+ file_count.must_equal 1
37
+ FileUtils.rm_rf 'test/tmp'
38
+ end
39
+
40
+ it 'must return a count of the exported filenames when called for all chats' do
41
+ file_count = @composition.export 'test/tmp'
42
+
43
+ file_count.must_equal 2
44
+ FileUtils.rm_rf 'test/tmp'
45
+ end
46
+
47
+ it 'must return a count of the exported filenames when called for specific chats' do
48
+ file_count = @composition.export_chats ['#something/$more;', '#something/$else;'], 'test/tmp'
49
+
50
+ file_count.must_equal 2
51
+ FileUtils.rm_rf 'test/tmp'
52
+ end
56
53
  end