mongify 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +9 -0
- data/Gemfile.lock +22 -24
- data/README.rdoc +20 -2
- data/lib/mongify.rb +2 -1
- data/lib/mongify/cli/application.rb +4 -1
- data/lib/mongify/cli/options.rb +2 -0
- data/lib/mongify/database/base_connection.rb +2 -2
- data/lib/mongify/database/column.rb +88 -13
- data/lib/mongify/database/no_sql_connection.rb +17 -0
- data/lib/mongify/exceptions.rb +18 -4
- data/lib/mongify/progressbar.rb +270 -0
- data/lib/mongify/status.rb +44 -11
- data/lib/mongify/translation/process.rb +54 -40
- data/lib/mongify/ui.rb +8 -0
- data/lib/mongify/version.rb +1 -1
- data/mongify.gemspec +1 -1
- data/spec/mongify/database/base_connection_spec.rb +2 -2
- data/spec/mongify/database/column_spec.rb +67 -6
- data/spec/mongify/database/no_sql_connection_spec.rb +14 -0
- data/spec/mongify/status_spec.rb +35 -0
- data/spec/mongify/translation/process_spec.rb +31 -4
- data/spec/mongify/ui_spec.rb +6 -0
- metadata +13 -9
data/CHANGELOG.rdoc
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
== 0.1.3 / 21 Feb 2011
|
2
|
+
* Made all exception stem from MongifyError and system always raises a child of MongifyError.
|
3
|
+
* Brought in the progress bar into source
|
4
|
+
* Changed behaviour on storing BigDecimal (now converts into String)
|
5
|
+
* Added ability to convert BigDecimal to integer via an :at => 'integer, :scale => 2 settings.
|
6
|
+
* Improved Progress Bar display, now it gives you a better feel for what's going on.
|
7
|
+
* Added an index on pre_mongified_id to speedup lookup times (Making it 42 times faster on import of embedded tables)
|
8
|
+
* Fixed bug in importing of polymorphic tables where associations are nil
|
9
|
+
* Fixed bug where pre_mongified_id would be a string and not an integer (causing no updates of ref_ids)
|
1
10
|
== 0.1.2 / 14 Feb 2011
|
2
11
|
* More Improvements to README and RDOC
|
3
12
|
* Added ability to :force => true on a mongodb_connection, forcing it to drop database before processing.
|
data/Gemfile.lock
CHANGED
@@ -1,31 +1,31 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
mongify (0.1.
|
4
|
+
mongify (0.1.3)
|
5
5
|
activerecord (>= 3.0.3)
|
6
6
|
activesupport (>= 3.0.3)
|
7
7
|
bson_ext (>= 1.1.5)
|
8
|
+
highline (>= 1.6.1)
|
8
9
|
mongo (>= 1.1.5)
|
9
10
|
mysql2
|
10
11
|
net-ssh (>= 2.0)
|
11
|
-
progressbar (>= 0.9)
|
12
12
|
|
13
13
|
GEM
|
14
14
|
remote: http://rubygems.org/
|
15
15
|
specs:
|
16
|
-
activemodel (3.0.
|
17
|
-
activesupport (= 3.0.
|
16
|
+
activemodel (3.0.4)
|
17
|
+
activesupport (= 3.0.4)
|
18
18
|
builder (~> 2.1.2)
|
19
19
|
i18n (~> 0.4)
|
20
|
-
activerecord (3.0.
|
21
|
-
activemodel (= 3.0.
|
22
|
-
activesupport (= 3.0.
|
20
|
+
activerecord (3.0.4)
|
21
|
+
activemodel (= 3.0.4)
|
22
|
+
activesupport (= 3.0.4)
|
23
23
|
arel (~> 2.0.2)
|
24
24
|
tzinfo (~> 0.3.23)
|
25
|
-
activesupport (3.0.
|
26
|
-
arel (2.0.
|
27
|
-
bson (1.2.
|
28
|
-
bson_ext (1.2.
|
25
|
+
activesupport (3.0.4)
|
26
|
+
arel (2.0.8)
|
27
|
+
bson (1.2.2)
|
28
|
+
bson_ext (1.2.2)
|
29
29
|
builder (2.1.2)
|
30
30
|
cucumber (0.10.0)
|
31
31
|
builder (>= 2.1.2)
|
@@ -36,26 +36,24 @@ GEM
|
|
36
36
|
diff-lcs (1.1.2)
|
37
37
|
gherkin (2.3.3)
|
38
38
|
json (~> 1.4.6)
|
39
|
+
highline (1.6.1)
|
39
40
|
i18n (0.5.0)
|
40
41
|
json (1.4.6)
|
41
|
-
mocha (0.9.
|
42
|
-
|
43
|
-
|
44
|
-
bson (>= 1.2.0)
|
42
|
+
mocha (0.9.12)
|
43
|
+
mongo (1.2.2)
|
44
|
+
bson (>= 1.2.2)
|
45
45
|
mysql (2.8.1)
|
46
46
|
mysql2 (0.2.6)
|
47
47
|
net-ssh (2.1.0)
|
48
|
-
progressbar (0.9.0)
|
49
|
-
rake (0.8.7)
|
50
48
|
rcov (0.9.9)
|
51
|
-
rspec (2.
|
52
|
-
rspec-core (~> 2.
|
53
|
-
rspec-expectations (~> 2.
|
54
|
-
rspec-mocks (~> 2.
|
55
|
-
rspec-core (2.
|
56
|
-
rspec-expectations (2.
|
49
|
+
rspec (2.5.0)
|
50
|
+
rspec-core (~> 2.5.0)
|
51
|
+
rspec-expectations (~> 2.5.0)
|
52
|
+
rspec-mocks (~> 2.5.0)
|
53
|
+
rspec-core (2.5.1)
|
54
|
+
rspec-expectations (2.5.0)
|
57
55
|
diff-lcs (~> 1.1.2)
|
58
|
-
rspec-mocks (2.
|
56
|
+
rspec-mocks (2.5.0)
|
59
57
|
sqlite3 (1.3.3)
|
60
58
|
sqlite3-ruby (1.3.3)
|
61
59
|
sqlite3 (>= 1.3.3)
|
data/README.rdoc
CHANGED
@@ -177,6 +177,8 @@ Table Options are as follow:
|
|
177
177
|
end # Moving records around, renaming records, changing values in row based on
|
178
178
|
end # some values! Checkout Mongify::Database::DataRow to learn more
|
179
179
|
|
180
|
+
More documentation can be found at {Mongify::Database::Table}
|
181
|
+
|
180
182
|
=== Columns
|
181
183
|
|
182
184
|
==== Structure
|
@@ -194,7 +196,7 @@ Before we cover the options, you need to know what types of columns are supporte
|
|
194
196
|
:key # Columns that are primary keys need to be marked as :key type
|
195
197
|
:integer # Will be converted to a integer
|
196
198
|
:float # Will be converted to a float
|
197
|
-
:decimal # Will be converted to a BigDecimal
|
199
|
+
:decimal # Will be converted to a string (due to MongoDB Ruby Drivers not supporting BigDecimal, read details in Mongify::Database::Column under Decimal Storage)
|
198
200
|
:string # Will be converted to a string
|
199
201
|
:text # Will be converted to a string
|
200
202
|
:datetime # Will be converted to a Time format (DateTime is not currently supported in the Mongo ruby driver)
|
@@ -215,6 +217,18 @@ Before we cover the options, you need to know what types of columns are supporte
|
|
215
217
|
column "surname",
|
216
218
|
:string,
|
217
219
|
:rename_to => 'last_name' # Rename_to allows you to rename the column
|
220
|
+
|
221
|
+
<em>For decimal columns you can specify a few options:</em>
|
222
|
+
column "total", # This is a default conversion setting.
|
223
|
+
:decimal,
|
224
|
+
:as => 'string'
|
225
|
+
|
226
|
+
column "total", # You can specify to convert your decimal to integer
|
227
|
+
:decimal, # specifying scale will define how many decimal places to keep
|
228
|
+
:as => 'integer', # Example: :scale => 2 will convert 123.4567 to 12346 before saving
|
229
|
+
:scale => 2
|
230
|
+
|
231
|
+
More documentation can be found at {Mongify::Database::Column}
|
218
232
|
|
219
233
|
== Notes
|
220
234
|
|
@@ -224,8 +238,12 @@ More testing will be done once the basic functionality is done
|
|
224
238
|
|
225
239
|
|
226
240
|
== TODO
|
241
|
+
* Allow deeper embedding
|
242
|
+
* Test in Ruby 1.9
|
243
|
+
* Test in different databases
|
227
244
|
|
228
|
-
|
245
|
+
== Known Issues
|
246
|
+
* Can't do anything to an embedded table
|
229
247
|
|
230
248
|
== Development
|
231
249
|
|
data/lib/mongify.rb
CHANGED
@@ -26,9 +26,12 @@ module Mongify
|
|
26
26
|
begin
|
27
27
|
cmd = @options.parse
|
28
28
|
cmd.execute(self)
|
29
|
-
rescue
|
29
|
+
rescue MongifyError => error
|
30
30
|
$stderr.puts "Error: #{error}"
|
31
31
|
report_error
|
32
|
+
rescue Exception => error
|
33
|
+
report_error
|
34
|
+
raise error
|
32
35
|
end
|
33
36
|
return @status
|
34
37
|
end
|
data/lib/mongify/cli/options.rb
CHANGED
@@ -87,6 +87,8 @@ EOB
|
|
87
87
|
# option parser, ensuring parse_options is only called once
|
88
88
|
def parse_options
|
89
89
|
@parsed = true && @parser.parse!(@argv) unless @parsed
|
90
|
+
rescue OptionParser::InvalidOption => er
|
91
|
+
raise Mongify::InvalidOption, er.message, er.backtrace
|
90
92
|
end
|
91
93
|
end
|
92
94
|
end
|
@@ -41,12 +41,12 @@ module Mongify
|
|
41
41
|
|
42
42
|
# Used to setup connection, Raises NotImplementedError because it needs to be setup in BaseConnection's children
|
43
43
|
def setup_connection_adapter
|
44
|
-
raise
|
44
|
+
raise NotImplementedMongifyError
|
45
45
|
end
|
46
46
|
|
47
47
|
# Used to test connection, Raises NotImplementedError because it needs to be setup in BaseConnection's children
|
48
48
|
def has_connection?
|
49
|
-
raise
|
49
|
+
raise NotImplementedMongifyError
|
50
50
|
end
|
51
51
|
|
52
52
|
|
@@ -19,7 +19,7 @@ module Mongify
|
|
19
19
|
# :key # Columns that are primary keys need to be marked as :key type
|
20
20
|
# :integer # Will be converted to a integer
|
21
21
|
# :float # Will be converted to a float
|
22
|
-
# :decimal # Will be converted to a
|
22
|
+
# :decimal # Will be converted to a string *(you can change default behaviour read below)
|
23
23
|
# :string # Will be converted to a string
|
24
24
|
# :text # Will be converted to a string
|
25
25
|
# :datetime # Will be converted to a Time format (DateTime is not currently supported in the Mongo ruby driver)
|
@@ -41,12 +41,46 @@ module Mongify
|
|
41
41
|
#
|
42
42
|
# column "post_id", :integer, :auto_detect => true # Will run auto detect and make this column a :references => 'posts', :on => 'post_id' for you
|
43
43
|
# # More used when reading a sql database, NOT recommended for use during processing of translation
|
44
|
+
#
|
45
|
+
# <em>For decimal columns you can specify a few options:</em>
|
46
|
+
# column "total", # This is a default conversion setting.
|
47
|
+
# :decimal,
|
48
|
+
# :as => 'string'
|
44
49
|
#
|
50
|
+
# column "total", # You can specify to convert your decimal to integer
|
51
|
+
# :decimal, # specifying scale will define how many decimal places to keep
|
52
|
+
# :as => 'integer', # Example: :scale => 2 will convert 123.4567 to 12346 in to mongodb
|
53
|
+
# :scale => 2
|
54
|
+
# ==== Decimal Storage
|
55
|
+
#
|
56
|
+
# Unfortunately MongoDB Ruby Drivers doesn't support BigDecimal, so to ensure all data is stored correctly (without losing information)
|
57
|
+
# I've chosen to store as String, however you can overwrite this functionality in one of two ways:
|
58
|
+
# <em>The reason you would want to do this, is to make this searchable via a query.</em>
|
59
|
+
#
|
60
|
+
# <b>1) You can specify :as => 'integer', :scale => 2</b>
|
61
|
+
# column "total", :decimal, :as => 'integer', :scale => 2
|
62
|
+
#
|
63
|
+
# #It would take a value of 123.456 and store it as an integer of value 12346
|
64
|
+
#
|
65
|
+
# <b>2) You can specify your own custom conversion by doing a {Mongify::Database::Table#before_save}
|
66
|
+
#
|
67
|
+
# Example:
|
68
|
+
# table "invoice" do
|
69
|
+
# column "name", :string
|
70
|
+
# column "total", :decimal
|
71
|
+
# before_save do |row|
|
72
|
+
# row.total = (BigDecimal.new(row.total) * 1000).round
|
73
|
+
# end
|
74
|
+
# end
|
75
|
+
#
|
76
|
+
# This would take 123.456789 in the total column and convert it to an interger of value 123457 (and in your app you can convert it back to a decimal)
|
77
|
+
#
|
78
|
+
# *REMEMBER* there is a limit on how big of an integer you can store in BSON/MongoDB (http://bsonspec.org/#/specification)
|
45
79
|
class Column
|
46
80
|
attr_reader :sql_name, :type, :options
|
47
81
|
|
48
82
|
#List of available options for a column
|
49
|
-
AVAILABLE_OPTIONS = ['references', 'ignore', 'rename_to']
|
83
|
+
AVAILABLE_OPTIONS = ['references', 'ignore', 'rename_to', 'as', 'scale']
|
50
84
|
|
51
85
|
# Auto detects if a column is an :key column or is a reference column
|
52
86
|
def self.auto_detect(column)
|
@@ -88,7 +122,7 @@ module Mongify
|
|
88
122
|
return {} if ignored?
|
89
123
|
case type
|
90
124
|
when :key
|
91
|
-
{"pre_mongified_id" => value}
|
125
|
+
{"pre_mongified_id" => value.to_i}
|
92
126
|
else
|
93
127
|
{"#{name}" => type_cast(value)}
|
94
128
|
end
|
@@ -115,14 +149,20 @@ module Mongify
|
|
115
149
|
def method_missing(meth, *args, &blk)
|
116
150
|
method_name = meth.to_s.gsub("=", '')
|
117
151
|
if AVAILABLE_OPTIONS.include?(method_name)
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
152
|
+
unless self.class.method_defined?(method_name.to_sym)
|
153
|
+
class_eval <<-EOF
|
154
|
+
def #{method_name}=(value)
|
155
|
+
options['#{method_name}'] = value
|
156
|
+
end
|
157
|
+
EOF
|
158
|
+
end
|
159
|
+
unless self.class.method_defined?("#{method_name}=".to_sym)
|
160
|
+
class_eval <<-EOF
|
161
|
+
def #{method_name}
|
162
|
+
options['#{method_name}']
|
163
|
+
end
|
164
|
+
EOF
|
165
|
+
end
|
126
166
|
send(meth, *args, &blk)
|
127
167
|
else
|
128
168
|
super(meth, *args, &blk)
|
@@ -154,6 +194,34 @@ module Mongify
|
|
154
194
|
!!self.ignore
|
155
195
|
end
|
156
196
|
|
197
|
+
# Used when trying to figure out how to convert a decimal value
|
198
|
+
# @return [String] passed option['as'] or defaults to 'string'
|
199
|
+
def as
|
200
|
+
options['as'] ||= 'string'
|
201
|
+
end
|
202
|
+
# Sets option['as'] to either 'string' or 'integer', defults to 'string' for unknown values
|
203
|
+
# @param [String|Symbol] value, can be either 'string' or 'integer
|
204
|
+
def as=(value)
|
205
|
+
value = value.to_s.downcase
|
206
|
+
options['as'] = ['string', 'integer'].include?(value) ? value : 'string'
|
207
|
+
end
|
208
|
+
# Returns true if :as was passed as integer
|
209
|
+
def as_integer?
|
210
|
+
self.as == 'integer'
|
211
|
+
end
|
212
|
+
|
213
|
+
# Get the scale option for decimal to integer conversion
|
214
|
+
# column 'total', :decimal, :as => 'integer', :scale => 3
|
215
|
+
# @return [integer] passed option['scale'] or 0
|
216
|
+
def scale
|
217
|
+
options['scale'] ||= 0
|
218
|
+
end
|
219
|
+
# Set the scale option for decimal to integer conversion
|
220
|
+
# column 'total', :decimal, :as => 'integer', :scale => 3
|
221
|
+
# @param [Integer] number of decimal places to round off to
|
222
|
+
def scale=(value)
|
223
|
+
options['scale'] = value.to_i
|
224
|
+
end
|
157
225
|
#######
|
158
226
|
private
|
159
227
|
#######
|
@@ -164,9 +232,15 @@ module Mongify
|
|
164
232
|
case type
|
165
233
|
when :string then value
|
166
234
|
when :text then value
|
167
|
-
when :integer then value.to_i
|
235
|
+
when :integer then value.to_i
|
168
236
|
when :float then value.to_f
|
169
|
-
when :decimal
|
237
|
+
when :decimal
|
238
|
+
value = ActiveRecord::ConnectionAdapters::Column.value_to_decimal(value)
|
239
|
+
if as_integer?
|
240
|
+
(value * (10 ** self.scale)).round.to_i
|
241
|
+
else
|
242
|
+
value.to_s
|
243
|
+
end
|
170
244
|
when :datetime then ActiveRecord::ConnectionAdapters::Column.string_to_time(value)
|
171
245
|
when :timestamp then ActiveRecord::ConnectionAdapters::Column.string_to_time(value)
|
172
246
|
when :time then ActiveRecord::ConnectionAdapters::Column.string_to_dummy_time(value)
|
@@ -176,6 +250,7 @@ module Mongify
|
|
176
250
|
else value
|
177
251
|
end
|
178
252
|
end
|
253
|
+
|
179
254
|
|
180
255
|
|
181
256
|
# runs auto detect (see {Mongify::Database::Column.auto_detect})
|
@@ -27,6 +27,7 @@ module Mongify
|
|
27
27
|
class NoSqlConnection < Mongify::Database::BaseConnection
|
28
28
|
include Mongo
|
29
29
|
|
30
|
+
|
30
31
|
#Required fields for a no sql connection
|
31
32
|
REQUIRED_FIELDS = %w{host database}
|
32
33
|
|
@@ -114,10 +115,26 @@ module Mongify
|
|
114
115
|
|
115
116
|
# Removes pre_mongified_id from all records in a given collection
|
116
117
|
def remove_pre_mongified_ids(collection_name)
|
118
|
+
drop_mongified_index(collection_name)
|
117
119
|
db[collection_name].update({}, { '$unset' => { 'pre_mongified_id' => 1} }, :multi => true)
|
118
120
|
end
|
119
121
|
|
122
|
+
# Removes pre_mongified_id from collection
|
123
|
+
# @param [String] collection_name name of collection to remove the index from
|
124
|
+
# @return True if successful
|
125
|
+
# @raise MongoDBError if index isn't found
|
126
|
+
def drop_mongified_index(collection_name)
|
127
|
+
db[collection_name].drop_index('pre_mongified_id_1') if db[collection_name].index_information.keys.include?("pre_mongified_id_1")
|
128
|
+
end
|
129
|
+
|
130
|
+
# Creates a pre_mongified_id index to ensure
|
131
|
+
# speedy lookup for collections via the pre_mongified_id
|
132
|
+
def create_pre_mongified_id_index(collection_name)
|
133
|
+
db[collection_name].create_index([['pre_mongified_id', Mongo::ASCENDING]])
|
134
|
+
end
|
135
|
+
|
120
136
|
# Asks user permission to drop the database
|
137
|
+
# @return true or false depending on user's response
|
121
138
|
def ask_to_drop_database
|
122
139
|
if UI.ask("Are you sure you want to drop #{database} database?")
|
123
140
|
drop_database
|
data/lib/mongify/exceptions.rb
CHANGED
@@ -1,20 +1,34 @@
|
|
1
1
|
module Mongify
|
2
|
+
# Base Mongify Error
|
3
|
+
class MongifyError < RuntimeError; end
|
4
|
+
|
5
|
+
# Not Implemented Error from Mongify
|
6
|
+
class NotImplementedMongifyError < MongifyError; end
|
7
|
+
|
2
8
|
# File Not Found Exception
|
3
|
-
class FileNotFound <
|
9
|
+
class FileNotFound < MongifyError; end
|
4
10
|
# Raise when configuration file is missing
|
5
11
|
class ConfigurationFileNotFound < FileNotFound; end
|
6
12
|
# Raised when Translation file is missing
|
7
13
|
class TranslationFileNotFound < FileNotFound; end
|
8
14
|
|
9
15
|
# Basic Configuration Error Exception
|
10
|
-
class ConfigurationError <
|
16
|
+
class ConfigurationError < MongifyError; end
|
11
17
|
# Raise when a sqlConnection is required but not given
|
12
18
|
class SqlConnectionRequired < ConfigurationError; end
|
19
|
+
# Raised when a SqlConfiguration is invalid?
|
20
|
+
class SqlConnectionInvalid < ConfigurationError; end
|
13
21
|
# Raised when a noSqlConnection is required but not given
|
14
22
|
class NoSqlConnectionRequired < ConfigurationError; end
|
23
|
+
# Raised when a NoSqlConfiguration is invalid?
|
24
|
+
class NoSqlConnectionInvalid < ConfigurationError; end
|
25
|
+
|
15
26
|
# Raised when a Mongify::Database::Column is expected but not given
|
16
27
|
class DatabaseColumnExpected < ConfigurationError; end
|
17
|
-
|
28
|
+
|
18
29
|
# Raised when application has no root folder set
|
19
|
-
class RootMissing <
|
30
|
+
class RootMissing < MongifyError; end
|
31
|
+
|
32
|
+
# Raised when an invalid option is passed via CLI
|
33
|
+
class InvalidOption < MongifyError; end
|
20
34
|
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
#
|
2
|
+
# Ruby/ProgressBar - a text progress bar library
|
3
|
+
#
|
4
|
+
# Copyright (C) 2001-2005 Satoru Takabayashi <satoru@namazu.org>
|
5
|
+
# All rights reserved.
|
6
|
+
# This is free software with ABSOLUTELY NO WARRANTY.
|
7
|
+
#
|
8
|
+
# You can redistribute it and/or modify it under the terms
|
9
|
+
# of Ruby's license.
|
10
|
+
#
|
11
|
+
# This has been modified by
|
12
|
+
# Andrew Kalek
|
13
|
+
# Anlek Consulting
|
14
|
+
# http://anlek.com
|
15
|
+
|
16
|
+
module Mongify
|
17
|
+
# Progress bar used to display results
|
18
|
+
class ProgressBar
|
19
|
+
#Progress bar version
|
20
|
+
VERSION = "0.9.1"
|
21
|
+
|
22
|
+
def initialize (title, total, out = STDERR)
|
23
|
+
@title = title
|
24
|
+
@total = total
|
25
|
+
@out = out
|
26
|
+
@terminal_width = 80
|
27
|
+
@bar_mark = "o"
|
28
|
+
@current = 0
|
29
|
+
@previous = 0
|
30
|
+
@finished_p = false
|
31
|
+
@start_time = Time.now
|
32
|
+
@previous_time = @start_time
|
33
|
+
@title_width = 35
|
34
|
+
@format = "%-#{@title_width}s %s %3d%% %s %s"
|
35
|
+
@format_arguments = [:title, :count, :percentage, :bar, :stat]
|
36
|
+
clear
|
37
|
+
show
|
38
|
+
end
|
39
|
+
attr_reader :title
|
40
|
+
attr_reader :current
|
41
|
+
attr_reader :total
|
42
|
+
attr_accessor :start_time
|
43
|
+
|
44
|
+
#######
|
45
|
+
private
|
46
|
+
#######
|
47
|
+
|
48
|
+
# Formatting for the actual bar
|
49
|
+
def fmt_bar
|
50
|
+
bar_width = do_percentage * @terminal_width / 100
|
51
|
+
sprintf("|%s%s|",
|
52
|
+
@bar_mark * bar_width,
|
53
|
+
" " * (@terminal_width - bar_width))
|
54
|
+
end
|
55
|
+
|
56
|
+
# Formatting for the percentage
|
57
|
+
def fmt_percentage
|
58
|
+
do_percentage
|
59
|
+
end
|
60
|
+
|
61
|
+
# Formatting for the stat (time left or time taken to complete)
|
62
|
+
def fmt_stat
|
63
|
+
if @finished_p then elapsed else eta end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Formatting for file transfer
|
67
|
+
def fmt_stat_for_file_transfer
|
68
|
+
if @finished_p then
|
69
|
+
sprintf("%s %s %s", bytes, transfer_rate, elapsed)
|
70
|
+
else
|
71
|
+
sprintf("%s %s %s", bytes, transfer_rate, eta)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Formatting for title
|
76
|
+
def fmt_title
|
77
|
+
@title[0,(@title_width - 1)] + ":"
|
78
|
+
end
|
79
|
+
|
80
|
+
# Formatting for count (x/y)
|
81
|
+
def fmt_count
|
82
|
+
sprintf('%15s', "(#{@current}/#{@total})")
|
83
|
+
end
|
84
|
+
|
85
|
+
# Converts bytes to kb, mb or gb
|
86
|
+
def convert_bytes (bytes)
|
87
|
+
if bytes < 1024
|
88
|
+
sprintf("%6dB", bytes)
|
89
|
+
elsif bytes < 1024 * 1000 # 1000kb
|
90
|
+
sprintf("%5.1fKB", bytes.to_f / 1024)
|
91
|
+
elsif bytes < 1024 * 1024 * 1000 # 1000mb
|
92
|
+
sprintf("%5.1fMB", bytes.to_f / 1024 / 1024)
|
93
|
+
else
|
94
|
+
sprintf("%5.1fGB", bytes.to_f / 1024 / 1024 / 1024)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Returns the transfer rate
|
99
|
+
# works only with file transfer
|
100
|
+
def transfer_rate
|
101
|
+
bytes_per_second = @current.to_f / (Time.now - @start_time)
|
102
|
+
sprintf("%s/s", convert_bytes(bytes_per_second))
|
103
|
+
end
|
104
|
+
|
105
|
+
# Gets current byte count
|
106
|
+
def bytes
|
107
|
+
convert_bytes(@current)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Gets formatting for time
|
111
|
+
def format_time (t)
|
112
|
+
t = t.to_i
|
113
|
+
sec = t % 60
|
114
|
+
min = (t / 60) % 60
|
115
|
+
hour = t / 3600
|
116
|
+
sprintf("%02d:%02d:%02d", hour, min, sec);
|
117
|
+
end
|
118
|
+
|
119
|
+
# ETA stands for Estimated Time of Arrival.
|
120
|
+
def eta
|
121
|
+
if @current == 0
|
122
|
+
"ETA: --:--:--"
|
123
|
+
else
|
124
|
+
elapsed = Time.now - @start_time
|
125
|
+
eta = elapsed * @total / @current - elapsed;
|
126
|
+
sprintf("ETA: %s", format_time(eta))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# Returns elapsed time
|
131
|
+
def elapsed
|
132
|
+
elapsed = Time.now - @start_time
|
133
|
+
sprintf("Time: %s", format_time(elapsed))
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns end of line
|
137
|
+
# @return [String] "\n" or "\r"
|
138
|
+
def eol
|
139
|
+
if @finished_p then "\n" else "\r" end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Calculates percentage
|
143
|
+
# @return [Number] the percentage
|
144
|
+
def do_percentage
|
145
|
+
if @total.zero?
|
146
|
+
100
|
147
|
+
else
|
148
|
+
@current * 100 / @total
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Gets the width of the terminal window
|
153
|
+
def get_width
|
154
|
+
UI.terminal_helper.output_cols
|
155
|
+
end
|
156
|
+
|
157
|
+
# Draws the bar
|
158
|
+
def show
|
159
|
+
arguments = @format_arguments.map {|method|
|
160
|
+
method = sprintf("fmt_%s", method)
|
161
|
+
send(method)
|
162
|
+
}
|
163
|
+
line = sprintf(@format, *arguments)
|
164
|
+
|
165
|
+
width = get_width
|
166
|
+
if line.length == width - 1
|
167
|
+
@out.print(line + eol)
|
168
|
+
@out.flush
|
169
|
+
elsif line.length >= width
|
170
|
+
@terminal_width = [@terminal_width - (line.length - width + 1), 0].max
|
171
|
+
if @terminal_width == 0 then @out.print(line + eol) else show end
|
172
|
+
else # line.length < width - 1
|
173
|
+
@terminal_width += width - line.length + 1
|
174
|
+
show
|
175
|
+
end
|
176
|
+
@previous_time = Time.now
|
177
|
+
end
|
178
|
+
|
179
|
+
# Checks if it's needed, shows if it's so
|
180
|
+
def show_if_needed
|
181
|
+
if @total.zero?
|
182
|
+
cur_percentage = 100
|
183
|
+
prev_percentage = 0
|
184
|
+
else
|
185
|
+
cur_percentage = (@current * 100 / @total).to_i
|
186
|
+
prev_percentage = (@previous * 100 / @total).to_i
|
187
|
+
end
|
188
|
+
|
189
|
+
# Use "!=" instead of ">" to support negative changes
|
190
|
+
if cur_percentage != prev_percentage ||
|
191
|
+
Time.now - @previous_time >= 1 || @finished_p
|
192
|
+
show
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
public
|
197
|
+
# Clear's line
|
198
|
+
def clear
|
199
|
+
@out.print "\r"
|
200
|
+
@out.print(" " * (get_width - 1))
|
201
|
+
@out.print "\r"
|
202
|
+
end
|
203
|
+
|
204
|
+
# Marks finished
|
205
|
+
def finish
|
206
|
+
@current = @total
|
207
|
+
@finished_p = true
|
208
|
+
show
|
209
|
+
end
|
210
|
+
|
211
|
+
# Returns if the bar is finished
|
212
|
+
def finished?
|
213
|
+
@finished_p
|
214
|
+
end
|
215
|
+
|
216
|
+
# Sets bar to file trasfer mode
|
217
|
+
def file_transfer_mode
|
218
|
+
@format_arguments = [:title, :percentage, :bar, :stat_for_file_transfer]
|
219
|
+
end
|
220
|
+
|
221
|
+
# Allows format to be re/defined
|
222
|
+
def format= (format)
|
223
|
+
@format = format
|
224
|
+
end
|
225
|
+
|
226
|
+
# Allows to change the arguments of items that are shown
|
227
|
+
def format_arguments= (arguments)
|
228
|
+
@format_arguments = arguments
|
229
|
+
end
|
230
|
+
|
231
|
+
# halts drawing of bar
|
232
|
+
def halt
|
233
|
+
@finished_p = true
|
234
|
+
show
|
235
|
+
end
|
236
|
+
|
237
|
+
# Incremets bar
|
238
|
+
# @return [Integer] current bar frame
|
239
|
+
def inc (step = 1)
|
240
|
+
@current += step
|
241
|
+
@current = @total if @current > @total
|
242
|
+
show_if_needed
|
243
|
+
@previous = @current
|
244
|
+
end
|
245
|
+
|
246
|
+
# Allows you to set bar frame
|
247
|
+
# @return [Integer] current bar frame
|
248
|
+
def set (count)
|
249
|
+
if count < 0 || count > @total
|
250
|
+
raise "invalid count: #{count} (total: #{@total})"
|
251
|
+
end
|
252
|
+
@current = count
|
253
|
+
show_if_needed
|
254
|
+
@previous = @current
|
255
|
+
end
|
256
|
+
|
257
|
+
# Returns string representation of this object
|
258
|
+
def inspect
|
259
|
+
"#<ProgressBar:#{@current}/#{@total}>"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# Same as progress bar but this counts the progress backwards from 100% to 0
|
264
|
+
class ReversedProgressBar < ProgressBar
|
265
|
+
# Calculates the percentage
|
266
|
+
def do_percentage
|
267
|
+
100 - super
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|