number_humanizer 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '097ea7f57eb7163f62075393544113f2da8b3f7c446096a8a6981c60f3ce8266'
4
- data.tar.gz: c714c21473683be8c1c89e2edfc1efb1bc739ba691668d83906cfae8df3569a4
3
+ metadata.gz: 1945b1dea89c43d38dec1285471f801ea99e7012d2e906f5c5ee8c2c79ef9408
4
+ data.tar.gz: 54d390fa1d89b7a1fd1941ff73e51518036c6ad7b8eac415bf0aa14fa306e723
5
5
  SHA512:
6
- metadata.gz: b71e359fcc6fe0653d170bc9cd15f6f8102044954a93e19ebdf1e36c0553d8dbaebe1714c2cac0028e48805faa81e72711c87e9885298800d0cf0470ceb187d6
7
- data.tar.gz: 041112bb61cf75822b0917938dae7e8272da73859b36ff84cba41c0da6f6d31bba5822dddab13904e8a33914e300595b2427e6e2c12b6dcc3b5b40a76af75e05
6
+ metadata.gz: dc4d5cc8c66aabc6f3bc882183c0af137d301028fa78cc400433f836a19783ac84bc5027ee094b0bd8fa776652294064206b663c209f71f3bd29b8e691b8ad6a
7
+ data.tar.gz: 96955a95f3f5373c52c20a0393a1a6ec6a0fab6def578f2b8179e930f0f09ae65999515bb0e96e5a1c879d4dbd0b01a7cc93d78897a44607f07881ef7d3c481e
data/CHANGELOG.md CHANGED
@@ -1,2 +1,13 @@
1
+ ## [0.1.0] - 2022-05-28
2
+ - Release the gem with basic functionalities
3
+
4
+
1
5
  ## [0.1.1] - 2022-06-03
2
6
  - Bug fixes and specs added
7
+
8
+
9
+ ## [0.2.0] - 2022-06-04
10
+ - Enhance and refactor languages structure to support multiple languages.
11
+ - Add English language humanizer and its specs.
12
+ - Setup configurations structure and basic configs.
13
+ - Fix issues.
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- number_humanizer (0.1.0)
4
+ number_humanizer (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
- # NumberHumanizer
1
+ # Number Humanizer
2
2
 
3
- A ruby gem to convert numbers into Arabic words. It has been designed to support multiple languages and currently support only Arabic language.
3
+ A ruby gem to convert numbers into words. It has been designed to support multiple languages and currently support only Arabic and English languages.
4
4
 
5
5
  ## Installation
6
6
 
@@ -53,6 +53,53 @@ NumberHumanizer::Manager.new(1500, currency: 'ريال').call.result
53
53
 
54
54
  Note that sub currency is also supported but not fully functional so if you need to use it then you can pass amount and sub amount separately to the service and you need to combine.
55
55
 
56
+ ## Configurations
57
+ You have to way for setting configurations in number humanizer gem:
58
+ - Set configurations globally: this will be used in all conversions. You can add the following snippet in initializers folder or any file that is loaded automatically. Note that this configuration can be changed at any time by calling this code.
59
+
60
+ ```ruby
61
+ # initializers/number_humanizer.rb
62
+ require 'number_humanizer'
63
+
64
+ NumberHumanizer.setup do |config|
65
+ # default language is arabic
66
+ config.language = :arabic
67
+
68
+ # currency is not set by default and you can set from here
69
+ config.currency = 'ريال'
70
+
71
+ # all upcoming configs will be placed here
72
+ end
73
+ ```
74
+
75
+ - Pass configuratoins instantly whether when using service object or monkey patched methods so that global configurations will be ignored
76
+
77
+ ```ruby
78
+ require 'number_humanizer'
79
+
80
+ NumberHumanizer::Manager.new(1500, language: :arabic, currency: 'ريال').call.result
81
+ #=> "ألف و خمسة مائة ريال"
82
+
83
+ 1500.to_word(language: :arabic, currency: 'ريال')
84
+ #=> "ألف و خمسة مائة ريال"
85
+ ```
86
+
87
+ ## Supported Languages
88
+ Initially, gem was supporting Arabic language only but now English language is supported too. Here are the details needed for each language:
89
+
90
+ - **Arabic Language**
91
+
92
+ This is the default one and you don't need to specify it but if you set other language and want to have arabic word for given number then you can pass it to the service or as args in `to_word` monkey patched method as explained in configurations section.
93
+
94
+ *Note that accepted values are: `arabic` and `ar`*.
95
+
96
+ - **English Language**
97
+
98
+ Like arabic language, english language can be configured globally or instantly as described in configuration section. Here you just need to set `language` config.
99
+
100
+ *Note that accepted values are: `english` and `en`*.
101
+
102
+
56
103
  ## Development
57
104
 
58
105
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
@@ -0,0 +1,32 @@
1
+ module NumberHumanizer
2
+ class Configuration < Module
3
+ def initialize(**attributes)
4
+ attributes.each do |name, default|
5
+ define_reader(name, default)
6
+ define_writer(name)
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def define_reader(name, default)
13
+ variable = :"@#{name}"
14
+
15
+ define_method(name) do
16
+ if instance_variable_defined?(variable)
17
+ instance_variable_get(variable)
18
+ else
19
+ default
20
+ end
21
+ end
22
+ end
23
+
24
+ def define_writer(name)
25
+ variable = :"@#{name}"
26
+
27
+ define_method("#{name}=") do |value|
28
+ instance_variable_set(variable, value)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -22,7 +22,7 @@ module NumberHumanizer::Languages
22
22
  19 => 'تيفليون',
23
23
  20 => 'تيفليار',
24
24
  21 => 'ديشليون',
25
- 22 => 'ديشلي',
25
+ 22 => 'ديشلي'
26
26
  }.freeze
27
27
 
28
28
  NUMBERS_DICTIONARY = {
@@ -59,87 +59,8 @@ module NumberHumanizer::Languages
59
59
  2000 => 'ألفان'
60
60
  }.freeze
61
61
 
62
- private
63
-
64
- attr_reader :processed_real_part, :processed_fraction_part
65
-
66
- def process_result
67
- @processed_real_part = process_real_part
68
- @processed_fraction_part = process_fraction_part
69
- final_result = [
70
- [processed_real_part, currency].compact.join(' '),
71
- ]
72
- final_result << [processed_fraction_part, sub_currency].compact.join(' ') if processed_fraction_part.present?
73
- final_result.compact.join(decimal_point_connector).insert(0, process_minus)
74
- end
75
-
76
- def and_connector; " و "; end
77
- def decimal_point_connector; " فاصل "; end
78
- def minus_word; "سالب "; end
79
- def unit(unit_key); UNITS_DICTIONARY[unit_key]; end
80
- def word_lookup(segment); NUMBERS_DICTIONARY[segment]; end
81
-
82
- def process_minus
83
- return minus_word if number.negative?
84
- ''
85
- end
86
-
87
- def process_real_part
88
- number_word = word_lookup(real_part)
89
- return number_word if number_word.present?
90
- process_number(real_part)
91
- end
92
-
93
- def process_fraction_part
94
- return if fraction_part.nil? || fraction_part.zero?
95
- fraction_part
96
- # process_number(fraction_part)
97
- end
98
-
99
- def process_number(number)
100
- segments = get_number_segments(number)
101
- segments_size = segments.size
102
- "#{segments.map!.with_index { |segment, i| process_segment(segment, unit_key: segments_size - i) }.compact.reject{ |sw| sw == '' }.join(and_connector)}"
103
- end
104
-
105
- def get_number_segments(number)
106
- return [0] if number.zero?
107
- segments = []
108
- while(number != 0)
109
- segments.insert(0, number % 1000)
110
- number /= 1000
111
- end
112
- segments
113
- end
114
-
115
- def process_segment(segment, unit_key:)
116
- word = "#{segment_to_word(segment, unit_key)}"
117
- word << " #{unit(unit_key)}" if unit_key != 1 && segment > 0
118
- word.squish!
119
- end
120
-
121
- def segment_to_word(segment, unit_key)
122
- return '' if segment.zero? || (segment == 1 && unit_key != 1)
123
- return word_lookup(segment) if word_lookup(segment).present?
124
- return process_words_for_tens(segment) if segment < 100
125
- return process_words_for_hundreds(segment) if segment < 1000
126
- end
127
-
128
- def process_words_for_tens(segment)
129
- return word_lookup(segment) if word_lookup(segment).present?
130
- "#{word_lookup(segment % 10)}#{segment / 10 == 1 ? ' ' : and_connector}#{word_lookup(segment / 10 * 10)}"
131
- end
132
-
133
- def process_words_for_hundreds(segment)
134
- word = "#{process_hundred_part(segment)}"
135
- word << "#{and_connector}#{process_words_for_tens(segment % 100)}" if segment % 100 > 0
136
- word
137
- end
138
-
139
- def process_hundred_part(segment)
140
- hundred_digit = segment / 100
141
- return '' if hundred_digit.zero?
142
- (1..2).cover?(hundred_digit) ? word_lookup(hundred_digit * 100) : "#{word_lookup(hundred_digit)} #{word_lookup(100)}"
143
- end
62
+ AND_CONNECTOR = " و ".freeze
63
+ DECIMAL_POINT_CONNECTOR = " فاصل ".freeze
64
+ MINUS_WORD = "سالب ".freeze
144
65
  end
145
66
  end
@@ -5,8 +5,8 @@ module NumberHumanizer::Languages
5
5
  def initialize(number, **args)
6
6
  @number = number
7
7
  @real_part, @fraction_part = exctract_real_and_fraction_parts
8
- @currency = args[:currency]
9
- @sub_currency = args[:sub_currency]
8
+ @currency = args[:currency] || NumberHumanizer.currency
9
+ @sub_currency = args[:sub_currency] || NumberHumanizer.sub_currency
10
10
  end
11
11
 
12
12
  def call
@@ -16,10 +16,90 @@ module NumberHumanizer::Languages
16
16
 
17
17
  private
18
18
 
19
- attr_reader :number, :real_part, :fraction_part, :currency, :sub_currency
19
+ attr_reader :number, :real_part, :fraction_part, :currency, :sub_currency,
20
+ :processed_real_part, :processed_fraction_part
20
21
 
21
22
  def exctract_real_and_fraction_parts
22
23
  number.abs.to_s.split('.').map(&:to_i)
23
24
  end
25
+
26
+ def process_result
27
+ @processed_real_part = process_real_part
28
+ @processed_fraction_part = process_fraction_part
29
+ final_result = [
30
+ [processed_real_part, currency].compact.join(' '),
31
+ ]
32
+ final_result << [processed_fraction_part, sub_currency].compact.join(' ') if processed_fraction_part.present?
33
+ final_result.compact.join(decimal_point_connector).insert(0, process_minus)
34
+ end
35
+
36
+ def unit(unit_key); self.class::UNITS_DICTIONARY[unit_key]; end
37
+ def word_lookup(segment); self.class::NUMBERS_DICTIONARY[segment]; end
38
+ def and_connector; self.class::AND_CONNECTOR; end
39
+ def decimal_point_connector; self.class::DECIMAL_POINT_CONNECTOR; end
40
+ def minus_word; self.class::Minus_word; end
41
+
42
+ def process_minus
43
+ return minus_word if number.negative?
44
+ ''
45
+ end
46
+
47
+ def process_real_part
48
+ number_word = word_lookup(real_part)
49
+ return number_word if number_word.present?
50
+ process_number(real_part)
51
+ end
52
+
53
+ def process_fraction_part
54
+ return if fraction_part.nil? || fraction_part.zero?
55
+ fraction_part
56
+ # process_number(fraction_part)
57
+ end
58
+
59
+ def process_number(number)
60
+ segments = get_number_segments(number)
61
+ segments_size = segments.size
62
+ "#{segments.map!.with_index { |segment, i| process_segment(segment, unit_key: segments_size - i) }.compact.reject{ |sw| sw == '' }.join(and_connector)}"
63
+ end
64
+
65
+ def get_number_segments(number)
66
+ return [0] if number.zero?
67
+ segments = []
68
+ while(number != 0)
69
+ segments.insert(0, number % 1000)
70
+ number /= 1000
71
+ end
72
+ segments
73
+ end
74
+
75
+ def process_segment(segment, unit_key:)
76
+ word = "#{segment_to_word(segment, unit_key)}"
77
+ word << " #{unit(unit_key)}" if unit_key != 1 && segment > 0
78
+ word.squish!
79
+ end
80
+
81
+ def segment_to_word(segment, unit_key)
82
+ return '' if segment.zero? || (segment == 1 && unit_key != 1)
83
+ return word_lookup(segment) if word_lookup(segment).present?
84
+ return process_words_for_tens(segment) if segment < 100
85
+ return process_words_for_hundreds(segment) if segment < 1000
86
+ end
87
+
88
+ def process_words_for_tens(segment)
89
+ return word_lookup(segment) if word_lookup(segment).present?
90
+ "#{word_lookup(segment % 10)}#{segment / 10 == 1 ? ' ' : and_connector}#{word_lookup(segment / 10 * 10)}"
91
+ end
92
+
93
+ def process_words_for_hundreds(segment)
94
+ word = "#{process_hundred_part(segment)}"
95
+ word << "#{and_connector}#{process_words_for_tens(segment % 100)}" if segment % 100 > 0
96
+ word
97
+ end
98
+
99
+ def process_hundred_part(segment)
100
+ hundred_digit = segment / 100
101
+ return '' if hundred_digit.zero?
102
+ (1..2).cover?(hundred_digit) ? word_lookup(hundred_digit * 100) : "#{word_lookup(hundred_digit)} #{word_lookup(100)}"
103
+ end
24
104
  end
25
105
  end
@@ -0,0 +1,84 @@
1
+ module NumberHumanizer::Languages
2
+ class English < Base
3
+ UNITS_DICTIONARY = {
4
+ 1 => '',
5
+ 2 => 'thousand',
6
+ 3 => 'million',
7
+ 4 => 'billion',
8
+ 5 => 'trillion',
9
+ 6 => 'quadrillion',
10
+ 7 => 'quintillion',
11
+ 8 => 'sextillion',
12
+ 9 => 'septillion',
13
+ 10 => 'octillion',
14
+ 11 => 'nonillion',
15
+ 12 => 'decillion',
16
+ 13 => 'undecillion',
17
+ 14 => 'duodecillion',
18
+ 15 => 'tredecillion',
19
+ 16 => 'quattuordecillion',
20
+ 17 => 'quindecillion',
21
+ 18 => 'sexdecillion',
22
+ 19 => 'septemdecillion',
23
+ 20 => 'octodecillion',
24
+ 21 => 'novemdecillion',
25
+ 22 => 'vigintillion'
26
+ }.freeze
27
+
28
+ NUMBERS_DICTIONARY = {
29
+ 0 => 'zero',
30
+ 1 => 'one',
31
+ 2 => 'two',
32
+ 3 => 'three',
33
+ 4 => 'four',
34
+ 5 => 'five',
35
+ 6 => 'six',
36
+ 7 => 'seven',
37
+ 8 => 'eight',
38
+ 9 => 'nine',
39
+ 10 => 'ten',
40
+ 11 => 'eleven',
41
+ 12 => 'twelve',
42
+ 13 => 'thirteen',
43
+ 14 => 'fourteen',
44
+ 15 => 'fifteen',
45
+ 16 => 'sixteen',
46
+ 17 => 'seventeen',
47
+ 18 => 'eighteen',
48
+ 19 => 'nineteen',
49
+ 20 => 'twenty',
50
+ 30 => 'thirty',
51
+ 40 => 'forty',
52
+ 50 => 'fifty',
53
+ 60 => 'sixty',
54
+ 70 => 'seventy',
55
+ 80 => 'eighty',
56
+ 90 => 'ninety',
57
+ 100 => 'hundred',
58
+ 200 => 'two hundred'
59
+ }.freeze
60
+
61
+ AND_CONNECTOR = " and ".freeze
62
+ DECIMAL_POINT_CONNECTOR = " point ".freeze
63
+ MINUS_WORD = "minus ".freeze
64
+
65
+ private
66
+
67
+ def process_words_for_hundreds(segment)
68
+ word = "#{process_hundred_part(segment)}"
69
+ word << "#{and_connector}#{process_words_for_tens(segment % 100)}" if segment % 100 > 0
70
+ word
71
+ end
72
+
73
+ def process_words_for_tens(segment)
74
+ return word_lookup(segment) if word_lookup(segment).present?
75
+ "#{word_lookup(segment / 10 * 10)} #{word_lookup(segment % 10)}"
76
+ end
77
+
78
+ def process_hundred_part(segment)
79
+ hundred_digit = segment / 100
80
+ return '' if hundred_digit.zero?
81
+ (1..2).cover?(hundred_digit) ? word_lookup(hundred_digit * 100) : "#{word_lookup(hundred_digit)} #{word_lookup(100)}"
82
+ end
83
+ end
84
+ end
@@ -1,7 +1,9 @@
1
1
  module NumberHumanizer
2
2
  module StringExtensions
3
3
  def squish!
4
- self.strip.gsub(/s+/, ' ')
4
+ self.strip!
5
+ self.gsub!(/\s+/, ' ')
6
+ self
5
7
  end
6
8
 
7
9
  def blank?
@@ -1,3 +1,3 @@
1
1
  module NumberHumanizer
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,15 +1,30 @@
1
1
  require "number_humanizer/version"
2
2
  require "number_humanizer/monkey_patchers"
3
+ require "number_humanizer/configuration"
3
4
  require "number_humanizer/languages/base"
4
5
  require "number_humanizer/languages/arabic"
6
+ require "number_humanizer/languages/english"
5
7
 
6
8
  module NumberHumanizer
9
+ extend NumberHumanizer::Configuration.new(
10
+ language: :arabic,
11
+ currency: nil,
12
+ sub_currency: nil
13
+ )
14
+
15
+ def self.setup
16
+ yield self
17
+ end
18
+
19
+ class LanguageNotSupportedError < StandardError
20
+ end
21
+
7
22
  class Manager
8
23
  attr_reader :result, :number, :language, :args
9
24
 
10
- def initialize(number, language: :arabic, **args)
25
+ def initialize(number, **args)
11
26
  @number = number
12
- @language = language
27
+ @language = args[:language] || NumberHumanizer.language
13
28
  @args = args
14
29
  end
15
30
 
@@ -20,12 +35,12 @@ module NumberHumanizer
20
35
 
21
36
  private
22
37
 
23
-
24
38
  def language_service_class
25
39
  @language_service_class ||= \
26
- case language
40
+ case language.downcase.to_sym
27
41
  when :arabic, :ar then NumberHumanizer::Languages::Arabic
28
- else raise(StandardError, 'Language not supported')
42
+ when :english, :en then NumberHumanizer::Languages::English
43
+ else raise(LanguageNotSupportedError, "Language specified (#{language}) not supported")
29
44
  end
30
45
  end
31
46
  end
@@ -8,14 +8,14 @@ Gem::Specification.new do |spec|
8
8
  spec.authors = ["Ahmed Ba Haggag"]
9
9
  spec.email = ["ahmedbahaggag@gmail.com"]
10
10
 
11
- spec.summary = %q{Number to word humanizer for Arabic Language}
12
- spec.description = %q{Number to word humanizer for Arabic Language}
11
+ spec.summary = %q{Number to word humanizer for Arabic and English Language}
12
+ spec.description = %q{Number to word humanizer for Arabic and English Language}
13
13
  spec.homepage = "https://github.com/abahaggag/number_humanizer"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.metadata["homepage_uri"] = spec.homepage
17
17
  spec.metadata["source_code_uri"] = spec.homepage
18
- spec.metadata["changelog_uri"] = spec.homepage
18
+ spec.metadata["changelog_uri"] = "#{spec.homepage}/blob/main/CHANGELOG.md"
19
19
 
20
20
  # Specify which files should be added to the gem when it is released.
21
21
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: number_humanizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmed Ba Haggag
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-03 00:00:00.000000000 Z
11
+ date: 2022-06-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -72,7 +72,7 @@ dependencies:
72
72
  - - ">="
73
73
  - !ruby/object:Gem::Version
74
74
  version: '0'
75
- description: Number to word humanizer for Arabic Language
75
+ description: Number to word humanizer for Arabic and English Language
76
76
  email:
77
77
  - ahmedbahaggag@gmail.com
78
78
  executables: []
@@ -92,8 +92,10 @@ files:
92
92
  - bin/console
93
93
  - bin/setup
94
94
  - lib/number_humanizer.rb
95
+ - lib/number_humanizer/configuration.rb
95
96
  - lib/number_humanizer/languages/arabic.rb
96
97
  - lib/number_humanizer/languages/base.rb
98
+ - lib/number_humanizer/languages/english.rb
97
99
  - lib/number_humanizer/monkey_patchers.rb
98
100
  - lib/number_humanizer/version.rb
99
101
  - number_humanizer.gemspec
@@ -103,7 +105,7 @@ licenses:
103
105
  metadata:
104
106
  homepage_uri: https://github.com/abahaggag/number_humanizer
105
107
  source_code_uri: https://github.com/abahaggag/number_humanizer
106
- changelog_uri: https://github.com/abahaggag/number_humanizer
108
+ changelog_uri: https://github.com/abahaggag/number_humanizer/blob/main/CHANGELOG.md
107
109
  post_install_message:
108
110
  rdoc_options: []
109
111
  require_paths:
@@ -122,5 +124,5 @@ requirements: []
122
124
  rubygems_version: 3.1.2
123
125
  signing_key:
124
126
  specification_version: 4
125
- summary: Number to word humanizer for Arabic Language
127
+ summary: Number to word humanizer for Arabic and English Language
126
128
  test_files: []