runoff 1.0.1 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -2,83 +2,62 @@ require 'zip'
2
2
  require 'fileutils'
3
3
 
4
4
  module Runoff
5
+ # Public: Writes data received from the database to text files.
5
6
  class FileWriter
6
- attr_accessor :selected_entries
7
-
8
- def initialize(db_handler)
9
- @db_handler = db_handler
10
- @selected_entries = []
7
+ # Public: Initializes a FileWriter object.
8
+ #
9
+ # options - A Hash with commandline options.
10
+ def initialize(options)
11
+ @export_path = Location.get_export_path options
12
+ @adapter = Object.const_get("Runoff::Adapters::#{options[:adapter]}").new
13
+ @current_file_name = nil
14
+ @buffer = []
11
15
  end
12
16
 
13
- # Public: Exports all the chats from the database.
17
+ # Public: Writes a single row of data to a text file.
18
+ #
19
+ # messages - An Array of data received from the database.
14
20
  #
15
- # data_format - an object that defines how the data should be parsed.
16
- # export_path - a string that points to the directory where exported files must be saved.
17
- def export_database(data_format, export_path, create_archive)
18
- @export_path = export_path
21
+ # Examples
22
+ #
23
+ # write [{ chatname: "#first_user/$second_user;d3d86c6b0e3b8320" ... }, ...]
24
+ def write(messages)
25
+ messages.each_with_index do |m, i|
26
+ file_name = @adapter.get_file_name m[Runoff::COLUMNS[1]]
19
27
 
20
- schema = data_format.get_schema
21
- dataset = @db_handler[schema[:table]]
22
- dataset = dataset.select(*schema[:columns])
28
+ dump unless @current_file_name.nil? || @current_file_name == file_name
23
29
 
24
- dataset.each do |row|
25
- write data_format.build_entry(row)
26
- end
30
+ @current_file_name = file_name
27
31
 
28
- archive unless create_archive == false
32
+ @buffer << @adapter.build_entry(m)
33
+ dump if i == (messages.count - 1)
34
+ end
29
35
  end
30
36
 
31
- # Public: Exports specific chats from the database.
32
- #
33
- # data_format - an object that defines how the data should be parsed.
34
- # export_path - a string that points to the directory where exported files must be saved.
35
- def export_database_partially(data_format, export_path, create_archive, &block)
36
- @export_path = export_path
37
-
38
- schema = data_format.get_schema
39
- dataset = @db_handler[schema[:table]]
40
- indices = block.call self, dataset
41
- clean_indices = indices.split(',').map { |e| e.strip }
42
-
43
- clean_indices.each do |i|
44
- chatname = @selected_entries[i.to_i]
45
- approximation = data_format.denormalize chatname
46
- dataset = @db_handler[schema[:table]]
47
- dataset = dataset.where(Sequel.like(:chatname, "#{approximation}%"))
37
+ # Public: Saves all the exported files in a Zip archive.
38
+ def archive
39
+ archive_name = "#{@export_path}_#{Time.now.to_i}.zip"
48
40
 
49
- dataset.each { |row| write data_format.build_entry(row) }
41
+ Zip::File.open archive_name, Zip::File::CREATE do |archive|
42
+ Dir[File.join(@export_path, '**', '**')].each do |file|
43
+ archive.add File.basename(file), file
44
+ end
50
45
  end
51
46
 
52
- archive unless create_archive == false
47
+ FileUtils.rm_rf @export_path # Delete the folder.
53
48
  end
54
49
 
55
50
  private
56
51
 
57
- # Internal: Appends a new entry to a file.
58
- #
59
- # entry - a hash containing "filename" and "content" keys.
60
- #
61
- # Examples
62
- #
63
- # write { filename: "test.txt", content: "test content" }
64
- def write(entry)
65
- path = "#@export_path/#{entry[:filename]}"
66
-
67
- File.open(path, "a+") { |file| file.write entry[:content] }
68
- end
52
+ # Internal: Dumps the content buffer to a file.
53
+ def dump
54
+ content = @adapter.format_file_content @buffer
69
55
 
70
- # Internal: Creates a Zip file of the destination directory.
71
- def archive
72
- zip_file_name = "#@export_path-#{Time.now.strftime "%Y%m%d%H%S%S"}.zip"
73
-
74
- Zip::File.open(zip_file_name, Zip::File::CREATE) do |zf|
75
- Dir[File.join(@export_path, '**', '**')].each do |f|
76
- zf.add(f.sub("#@export_path/", ''), f)
77
- end
56
+ File.open("#{@export_path}/#@current_file_name", "w") do |file|
57
+ file.puts content
78
58
  end
79
59
 
80
- # Delete the destination directory because it is no longer needed.
81
- FileUtils.rm_r @export_path
60
+ @buffer.clear
82
61
  end
83
62
  end
84
63
  end
@@ -5,13 +5,14 @@ module Runoff
5
5
  #
6
6
  # Examples
7
7
  #
8
- # Location::default_skype_data_location skype_username
8
+ # Location.default_skype_data_location skype_username
9
9
  # # => /home/user/.Skype/skype_username/main.db
10
10
  class Location
11
- # Public: Gets a path to the Skype's main.db file.
11
+ # Public: Gets a path to the Skype main.db file.
12
12
  #
13
- # username - A String containing Skype username
14
- # options - A hash containing command-line options passed to the command.
13
+ # args - An Array of commandline arguments, which might contain a String
14
+ # with the Skype username.
15
+ # options - A hash containing commandline options passed to the command.
15
16
  # If the username is empty, then the hash must contain :from key.
16
17
  #
17
18
  # Examples
@@ -20,14 +21,16 @@ module Runoff
20
21
  # # => Path to the default Skype database location depending on the operating system.
21
22
  #
22
23
  # get_database_path('', { from: '~/Desktop/main.db' })
23
- # # => '~/Desktop/main.db'
24
+ # # => '/Users/username/Desktop/main.db'
24
25
  #
25
26
  # Returns a String
26
- def self.get_database_path(username, options)
27
- if options.from
28
- options.from
27
+ def self.get_database_path(args, options)
28
+ if options.key?(:from)
29
+ options[:from]
30
+ elsif args.count > 0
31
+ self.default_skype_data_location args[0]
29
32
  else
30
- self.default_skype_data_location username
33
+ raise ArgumentError, 'No username or database file path provided.'
31
34
  end
32
35
  end
33
36
 
@@ -40,12 +43,16 @@ module Runoff
40
43
  #
41
44
  # On Linux:
42
45
  # default_skype_data_location skype_username
43
- # # => /home/user/.Skype/skype_username/mai.db
46
+ # # => /home/user/.Skype/skype_username/main.db
44
47
  #
45
48
  # On Windows:
46
49
  # default_skype_data_location skype_username
47
50
  # # => /Users/user/AppData/Roaming/Skype/skype_username/main.db
48
51
  #
52
+ # On Mac OS:
53
+ # default_skype_data_location skype_username
54
+ # # => /Users/user/Library/Application\ Support/Skype/skype_username/main.db
55
+ #
49
56
  # Returns a String that contains the path to the Skype database file.
50
57
  def self.default_skype_data_location(skype_username)
51
58
  case RbConfig::CONFIG['host_os']
@@ -64,25 +71,21 @@ module Runoff
64
71
 
65
72
  # Public: Composes a path where the exported files must be saved.
66
73
  #
67
- # options - an object with command line options passed to the runoff executable.
74
+ # options - A Hash with command line options passed to the runoff executable.
68
75
  #
69
76
  # Returns a string with a directory path.
70
77
  def self.get_export_path(options)
71
- path = options.destination || "#{ENV['HOME']}"
78
+ path = options[:destination] || "#{ENV['HOME']}"
72
79
  path = "#{path}/skype_chat_history"
73
80
 
74
- unless File.exist?(path)
75
- FileUtils::mkdir_p path
76
- end
81
+ FileUtils::mkdir_p path unless File.exist?(path)
77
82
 
78
83
  path
79
84
  end
80
85
 
81
- private
82
-
83
- # Internal: Replaces backslashes with forward slashes and removes drive letter.
86
+ # Public: Replaces backslashes with forward slashes and removes drive letter.
84
87
  #
85
- # path - String containing a directory path.
88
+ # path - A String containing a directory path.
86
89
  #
87
90
  # Examples
88
91
  #
@@ -95,7 +98,7 @@ module Runoff
95
98
  path.gsub(/^[a-zA-Z]:/, '')
96
99
  end
97
100
 
98
- # Internal: Composes the default Skype database location for the Windows 8 operating system.
101
+ # Public: Composes the default Skype database location for the Windows 8 operating system.
99
102
  #
100
103
  # skype_username - A String that contains a username of the Skype account,
101
104
  # which database we want to access.
@@ -1,3 +1,3 @@
1
1
  module Runoff
2
- VERSION = '1.0.1'
2
+ VERSION = '2.0.0'
3
3
  end
data/lib/runoff.rb CHANGED
@@ -1,7 +1,15 @@
1
1
  require 'runoff/version'
2
2
  require 'runoff/location'
3
- require 'runoff/skype_data_format'
3
+ require 'runoff/adapters/adapter'
4
+ require 'runoff/adapters/txt_adapter'
5
+ require 'runoff/adapters/json_adapter'
4
6
  require 'runoff/file_writer'
5
- require 'runoff/commands/command'
6
- require 'runoff/commands/all'
7
- require 'runoff/commands/some'
7
+ require 'runoff/chat'
8
+
9
+ module Runoff
10
+ # Public: A String containing the name of the table, where all the chat messages are stored.
11
+ TABLE = :Messages
12
+
13
+ # Public: An Array with all the important column names.
14
+ COLUMNS = [ :convo_id, :chatname, :timestamp, :from_dispname, :body_xml ]
15
+ end
data/runoff.gemspec CHANGED
@@ -19,7 +19,6 @@ Gem::Specification.new do |gem|
19
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
20
  gem.require_paths = ["lib"]
21
21
 
22
- gem.add_dependency 'commander'
23
22
  gem.add_dependency 'sequel'
24
23
  gem.add_dependency 'rubyzip'
25
24
  gem.add_dependency 'sqlite3'
@@ -0,0 +1,90 @@
1
+ require 'minitest/autorun'
2
+ require 'runoff/commandline/all'
3
+ require 'colorize'
4
+ require 'fileutils'
5
+
6
+ class CommandAllTest < MiniTest::Unit::TestCase
7
+ def setup
8
+ @current_directory = File.expand_path(File.dirname(__FILE__))
9
+
10
+ @default_output_dir = "#@current_directory/data"
11
+ @test_database_location = "#@current_directory/data/test_main.db"
12
+ @expected_message = 'Exporting...'.colorize(:green) + "\n" + 'Finished.'.colorize(:green) + "\n"
13
+ end
14
+
15
+ def teardown
16
+ Dir["#@default_output_dir/*"].each do |f|
17
+ if File.extname(f) == '.zip'
18
+ File.delete f
19
+ elsif File.directory?(f) && f !~ /custom_location/
20
+ FileUtils.rm_rf f
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_it_raises_a_ArgumentError_if_called_with_an_empty_list_of_arguments
26
+ command = Runoff::Commandline::All.new( archive: true, adapter: 'TxtAdapter' )
27
+
28
+ assert_raises ArgumentError do
29
+ assert_output 'Exporting...' do
30
+ command.execute []
31
+ end
32
+ end
33
+ end
34
+
35
+ def test_it_exports_all_chat_history_as_a_zip_archive_and_saves_it_in_the_default_location
36
+ command = Runoff::Commandline::All.new( archive: true, adapter: 'TxtAdapter' )
37
+ ENV['HOME'] = @default_output_dir
38
+
39
+ Runoff::Location.stub :default_skype_data_location, @test_database_location do
40
+ assert_output @expected_message do
41
+ command.execute ['white_mike']
42
+ end
43
+ end
44
+
45
+ assert_equal 1, Dir["#@default_output_dir/*.zip"].count
46
+ end
47
+
48
+ def test_it_exports_all_chat_history_as_a_folder_in_the_default_location_if_a_no_archive_option_is_set
49
+ command = Runoff::Commandline::All.new( archive: false, adapter: 'TxtAdapter' )
50
+ ENV['HOME'] = @default_output_dir
51
+
52
+ Runoff::Location.stub :default_skype_data_location, @test_database_location do
53
+ assert_output @expected_message do
54
+ command.execute ['white_mike']
55
+ end
56
+ end
57
+
58
+ assert_equal 1, Dir["#@default_output_dir/skype_chat_history"].count;
59
+ end
60
+
61
+ def test_it_exports_all_chat_history_as_a_zip_archive_from_a_custom_location_and_saves_it_in_the_default_location
62
+ test_custom_database_location = "#@current_directory/data/custom_location/test_main.db"
63
+
64
+ command = Runoff::Commandline::All.new( archive: true, adapter: 'TxtAdapter', from: test_custom_database_location )
65
+ ENV['HOME'] = @default_output_dir
66
+
67
+ Runoff::Location.stub :default_skype_data_location, @test_database_location do
68
+ assert_output @expected_message do
69
+ command.execute ['white_mike']
70
+ end
71
+ end
72
+
73
+ assert_equal 1, Dir["#@default_output_dir/*.zip"].count
74
+ end
75
+
76
+ def test_it_exports_all_chat_history_as_a_zip_archive_and_saves_it_in_a_custom_location
77
+ custom_output_location = "#@current_directory/data/custom_output_location"
78
+
79
+ command = Runoff::Commandline::All.new( archive: true, adapter: 'TxtAdapter', destination: custom_output_location )
80
+ ENV['HOME'] = @default_output_dir
81
+
82
+ Runoff::Location.stub :default_skype_data_location, @test_database_location do
83
+ assert_output @expected_message do
84
+ command.execute ['white_mike']
85
+ end
86
+ end
87
+
88
+ assert_equal 1, Dir["#@default_output_dir/custom_output_location/*.zip"].count
89
+ end
90
+ end
@@ -0,0 +1,49 @@
1
+ require 'minitest/autorun'
2
+ require 'runoff/commandline/some'
3
+ require 'colorize'
4
+ require 'fileutils'
5
+
6
+ class CommandSomeTest < MiniTest::Unit::TestCase
7
+ def setup
8
+ @current_directory = File.expand_path(File.dirname(__FILE__))
9
+
10
+ @default_output_dir = "#@current_directory/data"
11
+ @test_database_location = "#@current_directory/data/test_main.db"
12
+ @expected_message = 'Exporting...'.colorize(:green) + "\n" + 'Finished.'.colorize(:green) + "\n"
13
+ end
14
+
15
+ def teardown
16
+ Dir["#@default_output_dir/*"].each do |f|
17
+ if File.extname(f) == '.zip'
18
+ File.delete f
19
+ elsif File.directory?(f) && f !~ /custom_location/
20
+ FileUtils.rm_rf f
21
+ end
22
+ end
23
+ end
24
+
25
+ def test_it_raises_a_ArgumentError_if_called_with_an_empty_list_of_arguments
26
+ command = Runoff::Commandline::Some.new( archive: true, adapter: 'TxtAdapter' )
27
+
28
+ assert_raises ArgumentError do
29
+ assert_output 'Exporting...' do
30
+ command.execute []
31
+ end
32
+ end
33
+ end
34
+
35
+ def test_it_exports_specified_chat_history_as_a_zip_archive_and_saves_it_in_the_default_location
36
+ command = Runoff::Commandline::Some.new( archive: true, adapter: 'TxtAdapter' )
37
+ ENV['HOME'] = @default_output_dir
38
+
39
+ Runoff::Location.stub :default_skype_data_location, @test_database_location do
40
+ assert_output @expected_message do
41
+ command.stub :prompt_for_chatnames, [1] do
42
+ command.execute ['white_mike']
43
+ end
44
+ end
45
+ end
46
+
47
+ assert_equal 1, Dir["#@default_output_dir/*.zip"].count
48
+ end
49
+ end
Binary file
@@ -0,0 +1,78 @@
1
+ require 'minitest/autorun'
2
+ require 'runoff'
3
+
4
+ describe Runoff::Location do
5
+ it 'must return the default Skype database location on Windows 8.1' do
6
+ skype_username = 'skype_username'
7
+ path = "/AppData/Local/Packages/Microsoft.SkypeApp_124nb4j3654/LocalState/#{skype_username}/main.db"
8
+
9
+ Dir.stub :[], ['/AppData/Local/Packages/Microsoft.SkypeApp_124nb4j3654'] do
10
+ Runoff::Location.get_default_skype_data_location_on_windows_8(skype_username)
11
+ .must_equal(path)
12
+ end
13
+ end
14
+
15
+ it 'must convert a path string from Windows style to Unix style' do
16
+ original_path = 'C:\Users\Neo\main.db'
17
+ expected_path = '/Users/Neo/main.db'
18
+
19
+ Runoff::Location.format_windows_path(original_path).must_equal(expected_path)
20
+ end
21
+
22
+ it 'must build a path for a directory, where the files are supposed to be saved, if the destination option is specified' do
23
+ path = '/Users/Neo/backups'
24
+ options = { destination: path }
25
+
26
+ File.stub :exist?, true do
27
+ Runoff::Location.get_export_path(options).must_equal("#{path}/skype_chat_history")
28
+ end
29
+ end
30
+
31
+ it 'must build a path for a directory, where the files are supposed to be saved, if the destination option is not specified' do
32
+ options = { destination: nil }
33
+ ENV['HOME'] = '/Users/Neo'
34
+
35
+ File.stub :exist?, true do
36
+ Runoff::Location.get_export_path(options).must_equal("#{ENV['HOME']}/skype_chat_history")
37
+ end
38
+ end
39
+
40
+ it 'must return the default Skype database location for Windows Desktop app' do
41
+ RbConfig::CONFIG['host_os'] = 'mingw'
42
+ ENV['APPDATA'] = 'C:\Users\Neo\AppData\Roaming'
43
+
44
+ skype_username = 'skype_username'
45
+ expected_path = "/Users/Neo/AppData/Roaming/Skype/#{skype_username}/main.db"
46
+
47
+ File.stub :exist?, true do
48
+ Runoff::Location.default_skype_data_location(skype_username).must_equal(expected_path)
49
+ end
50
+ end
51
+
52
+ it 'must return the default Skype database location on Linux' do
53
+ RbConfig::CONFIG['host_os'] = 'linux'
54
+ ENV['HOME'] = '/home/Neo'
55
+
56
+ skype_username = 'skype_username'
57
+ expected_path = "#{ENV['HOME']}/.Skype/#{skype_username}/main.db"
58
+
59
+ Runoff::Location.default_skype_data_location(skype_username).must_equal(expected_path)
60
+ end
61
+
62
+ it 'must return the default Skype database location on Mac OS' do
63
+ RbConfig::CONFIG['host_os'] = 'darvin'
64
+ ENV['HOME'] = '/Users/Neo'
65
+
66
+ skype_username = 'skype_username'
67
+ expected_path = "#{ENV['HOME']}/Library/Application Support/Skype/#{skype_username}/main.db"
68
+
69
+ Runoff::Location.default_skype_data_location(skype_username).must_equal(expected_path)
70
+ end
71
+
72
+ it 'must return the path that are specified in the from option' do
73
+ path = '/Users/Neo/Desktop/main.db'
74
+ options = { from: path }
75
+
76
+ Runoff::Location.get_database_path('Neo', options).must_equal(path)
77
+ end
78
+ end
metadata CHANGED
@@ -1,29 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: runoff
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Aigars Dzerviniks
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-04 00:00:00.000000000 Z
11
+ date: 2014-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: commander
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - ">="
18
- - !ruby/object:Gem::Version
19
- version: '0'
20
- type: :runtime
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - ">="
25
- - !ruby/object:Gem::Version
26
- version: '0'
27
13
  - !ruby/object:Gem::Dependency
28
14
  name: sequel
29
15
  requirement: !ruby/object:Gem::Requirement
@@ -96,16 +82,23 @@ files:
96
82
  - Rakefile
97
83
  - bin/runoff
98
84
  - lib/runoff.rb
99
- - lib/runoff/commands/all.rb
100
- - lib/runoff/commands/command.rb
101
- - lib/runoff/commands/some.rb
85
+ - lib/runoff/adapters/adapter.rb
86
+ - lib/runoff/adapters/json_adapter.rb
87
+ - lib/runoff/adapters/txt_adapter.rb
88
+ - lib/runoff/chat.rb
89
+ - lib/runoff/commandline/all.rb
90
+ - lib/runoff/commandline/command.rb
91
+ - lib/runoff/commandline/none.rb
92
+ - lib/runoff/commandline/some.rb
102
93
  - lib/runoff/file_writer.rb
103
94
  - lib/runoff/location.rb
104
- - lib/runoff/skype_data_format.rb
105
95
  - lib/runoff/version.rb
106
96
  - runoff.gemspec
107
- - test/skype_data_format_test.rb
108
- - test/test_db.sqlite
97
+ - test/command_all_test.rb
98
+ - test/command_some_test.rb
99
+ - test/data/custom_location/test_main.db
100
+ - test/data/test_main.db
101
+ - test/location_test.rb
109
102
  homepage: https://github.com/aigarsdz/runoff
110
103
  licenses:
111
104
  - MIT
@@ -126,10 +119,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
119
  version: '0'
127
120
  requirements: []
128
121
  rubyforge_project:
129
- rubygems_version: 2.0.3
122
+ rubygems_version: 2.2.2
130
123
  signing_key:
131
124
  specification_version: 4
132
125
  summary: Tool to export Skype chat history from the SQLite database to text files
133
126
  test_files:
134
- - test/skype_data_format_test.rb
135
- - test/test_db.sqlite
127
+ - test/command_all_test.rb
128
+ - test/command_some_test.rb
129
+ - test/data/custom_location/test_main.db
130
+ - test/data/test_main.db
131
+ - test/location_test.rb
@@ -1,22 +0,0 @@
1
- require 'sequel'
2
-
3
- module Runoff
4
- # Commands that can be executed by the application.
5
- module Commands
6
- class All < Command
7
- # Public: Exports all Skyoe chat history.
8
- #
9
- # args - an array containing skype username
10
- # options - a hash containing user provided options
11
- #
12
- # Examples
13
- #
14
- # All.process ['username'], { from: '~/main.db' }
15
- def self.process(args, options = {})
16
- file_writer, export_path = self.get_file_writer_components args, options
17
-
18
- file_writer.export_database Runoff::SkypeDataFormat.new, export_path, options.archive
19
- end
20
- end
21
- end
22
- end
@@ -1,19 +0,0 @@
1
- module Runoff
2
- # Commands that can be executed by the application.
3
- module Commands
4
- # The base class for every runoff command.
5
- class Command
6
- def self.get_file_writer_components(args, options)
7
- if args.empty? && !options.from
8
- raise ArgumentError.new 'Error: You must specify the Skype username or a --from option'
9
- end
10
-
11
- main_db_path = Runoff::Location.get_database_path args[0], options
12
- export_path = Runoff::Location.get_export_path options
13
- db_handler = Sequel.sqlite main_db_path
14
-
15
- return Runoff::FileWriter.new(db_handler), export_path
16
- end
17
- end
18
- end
19
- end
@@ -1,38 +0,0 @@
1
- require 'sequel'
2
- require 'set'
3
-
4
- module Runoff
5
- # Commands that can be executed by the application.
6
- module Commands
7
- class Some < Command
8
- # Public: Exports a specified part of Skyoe chat history.
9
- #
10
- # args - an array containing skype username
11
- # options - a hash containing user provided options
12
- #
13
- # Examples
14
- #
15
- # Some.process ['username'], { from: '~/main.db' }
16
- def self.process(args, options = {})
17
- file_writer, export_path = self.get_file_writer_components args, options
18
- format = Runoff::SkypeDataFormat.new
19
-
20
- file_writer.export_database_partially format, export_path, options.archive do |w, d|
21
- chatname_dataset = d.select(:chatname)
22
- chatnames = Set.new # Set is necessary to filter out duplicate chatnames.
23
-
24
- chatname_dataset.each { |row| chatnames.add format.normalize(row[:chatname]) }
25
-
26
- # In order to use indices we need an array.
27
- w.selected_entries = chatnames.to_a
28
-
29
- w.selected_entries.each_with_index { |e, i| puts "[#{i}] #{e}" }
30
-
31
- puts # Ensure a blank line.
32
- # Return the user response back to the FileWriter class.
33
- next ask("Specify which chats to export separating each number with a comma:")
34
- end
35
- end
36
- end
37
- end
38
- end