numbers_in_words 0.1.1 → 0.5.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +14 -0
  3. data/.gitignore +7 -0
  4. data/.rspec +2 -0
  5. data/.rubocop.yml +58 -0
  6. data/.travis.yml +15 -0
  7. data/Gemfile +8 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +47 -0
  10. data/Rakefile +2 -43
  11. data/bin/spec +2 -0
  12. data/lib/numbers_in_words.rb +55 -4
  13. data/lib/numbers_in_words/duck_punch.rb +23 -0
  14. data/lib/numbers_in_words/exceptional_numbers.rb +115 -0
  15. data/lib/numbers_in_words/fraction.rb +136 -0
  16. data/lib/numbers_in_words/number_group.rb +64 -0
  17. data/lib/numbers_in_words/parsing/fraction_parsing.rb +34 -0
  18. data/lib/numbers_in_words/parsing/number_parser.rb +98 -0
  19. data/lib/numbers_in_words/parsing/pair_parsing.rb +64 -0
  20. data/lib/numbers_in_words/parsing/parse_fractions.rb +45 -0
  21. data/lib/numbers_in_words/parsing/parse_individual_number.rb +68 -0
  22. data/lib/numbers_in_words/parsing/parse_status.rb +17 -0
  23. data/lib/numbers_in_words/parsing/special.rb +67 -0
  24. data/lib/numbers_in_words/parsing/to_number.rb +77 -0
  25. data/lib/numbers_in_words/powers_of_ten.rb +49 -0
  26. data/lib/numbers_in_words/to_word.rb +84 -0
  27. data/lib/numbers_in_words/version.rb +5 -0
  28. data/lib/numbers_in_words/writer.rb +69 -0
  29. data/numbers_in_words.gemspec +20 -27
  30. data/spec/exceptional_numbers_spec.rb +26 -0
  31. data/spec/fraction_spec.rb +152 -0
  32. data/spec/fractions_spec.rb +31 -0
  33. data/spec/non_monkey_patch_spec.rb +51 -0
  34. data/spec/number_group_spec.rb +17 -0
  35. data/spec/number_parser_spec.rb +31 -0
  36. data/spec/numbers_in_words_spec.rb +69 -83
  37. data/spec/numerical_strings_spec.rb +35 -0
  38. data/spec/spec_helper.rb +26 -0
  39. data/spec/to_word_spec.rb +18 -0
  40. data/spec/words_in_numbers_spec.rb +137 -119
  41. data/spec/writer_spec.rb +26 -0
  42. data/spec/years_spec.rb +27 -0
  43. metadata +95 -45
  44. data/CHANGELOG +0 -1
  45. data/Manifest +0 -11
  46. data/README +0 -84
  47. data/examples/display_numbers_in_words.rb +0 -22
  48. data/init.rb +0 -8
  49. data/lib/numbers.rb +0 -260
  50. data/lib/words.rb +0 -221
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5a69b94b2d308dbf461d439c03e57ab585f6bd14760fbe01fbfb72ab7c87bf49
4
+ data.tar.gz: e8eaf38c1c28fa6b6d6c055ad2e73a0a446f418f984314682c5cc22eda6a64a3
5
+ SHA512:
6
+ metadata.gz: a62e3d4ee01401dc3fd9ed1a694722fc4033954ea4c3e4ffc0f321f2c11777b91b465407873a80477986ef4c91d6bbc1aaa64dd6b0d9ab35ff45435d687b09a4
7
+ data.tar.gz: f068adee24754761e40e8b969fd432f478ec6c0d921e686594a33c29d06c4384644b2cb9a18a82c385e225e090c0a3db2fb379154d5885ced72523efb9f1776c
@@ -0,0 +1,14 @@
1
+ ---
2
+ engines:
3
+ fixme:
4
+ enabled: true
5
+ rubocop:
6
+ enabled: true
7
+ ratings:
8
+ paths:
9
+ - Gemfile.lock
10
+ - "**.rb"
11
+ exclude_paths:
12
+ - spec/**/*
13
+ - .codeclimate.yml
14
+ - .rubocop.yml
@@ -0,0 +1,7 @@
1
+ *.swp
2
+ *.swo
3
+ *.html
4
+ tmp
5
+ pkg/
6
+ Gemfile.lock
7
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
@@ -0,0 +1,58 @@
1
+ # The behavior of RuboCop can be controlled via the .rubocop.yml
2
+ # configuration file. It makes it possible to enable/disable
3
+ # certain cops (checks) and to alter their behavior if they accept
4
+ # any parameters. The file can be placed either in your home
5
+ # directory or in some project directory.
6
+ #
7
+ # RuboCop will start looking for the configuration file in the directory
8
+ # where the inspected file is and continue its way up to the root directory.
9
+ #
10
+ # See https://docs.rubocop.org/rubocop/configuration
11
+ Layout/EmptyLinesAroundAttributeAccessor:
12
+ Enabled: true
13
+ Layout/SpaceAroundMethodCallOperator:
14
+ Enabled: true
15
+ Lint/DeprecatedOpenSSLConstant:
16
+ Enabled: true
17
+ Lint/MixedRegexpCaptureTypes:
18
+ Enabled: true
19
+ Lint/RaiseException:
20
+ Enabled: true
21
+ Lint/StructNewOverride:
22
+ Enabled: true
23
+ Style/ExponentialNotation:
24
+ Enabled: true
25
+ Style/HashEachMethods:
26
+ Enabled: true
27
+ Style/HashTransformKeys:
28
+ Enabled: true
29
+ Style/HashTransformValues:
30
+ Enabled: true
31
+ Style/RedundantFetchBlock:
32
+ Enabled: true
33
+ Style/RedundantRegexpCharacterClass:
34
+ Enabled: true
35
+ Style/RedundantRegexpEscape:
36
+ Enabled: true
37
+ Style/SlicingWithRange:
38
+ Enabled: true
39
+ Lint/DuplicateElsifCondition:
40
+ Enabled: true
41
+ Style/AccessorGrouping:
42
+ Enabled: true
43
+ Style/ArrayCoercion:
44
+ Enabled: true
45
+ Style/BisectedAttrAccessor:
46
+ Enabled: true
47
+ Style/CaseLikeIf:
48
+ Enabled: true
49
+ Style/Documentation:
50
+ Enabled: false
51
+ Style/HashAsLastArrayItem:
52
+ Enabled: true
53
+ Style/HashLikeCase:
54
+ Enabled: true
55
+ Style/RedundantAssignment:
56
+ Enabled: true
57
+ Style/RedundantFileExtensionInRequire:
58
+ Enabled: true
@@ -0,0 +1,15 @@
1
+ env:
2
+ global:
3
+ - CC_TEST_REPORTER_ID=9a548b48c1c0804d8fb9083ae8e9693a5d5197eb86e8cf1b63a89d18f33e0464
4
+ sudo: false
5
+ language: ruby
6
+ rvm:
7
+ - "2.6.6"
8
+ - "2.7.1"
9
+ before_script:
10
+ - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
11
+ - chmod +x ./cc-test-reporter
12
+ - ./cc-test-reporter before-build
13
+ script: ./bin/spec
14
+ after_script:
15
+ - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gem 'byebug'
6
+ gem 'simplecov', '< 0.18.0', require: false, group: :test
7
+ # Specify your gem's dependencies in numbers_in_words.gemspec
8
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Mark Burns
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,47 @@
1
+ [![Build Status](http://img.shields.io/travis/markburns/numbers_in_words.svg)](https://travis-ci.org/markburns/numbers_in_words)
2
+ [![Maintainability](https://api.codeclimate.com/v1/badges/a51210488896b798af20/maintainability)](https://codeclimate.com/github/markburns/numbers_in_words/maintainability)
3
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/a51210488896b798af20/test_coverage)](https://codeclimate.com/github/markburns/numbers_in_words/test_coverage)
4
+ [![Gem Version](http://img.shields.io/gem/v/numbers_in_words.svg)](https://rubygems.org/gems/numbers_in_words)
5
+ [![License](http://img.shields.io/:license-mit-blue.svg)](http://markburns.mit-license.org)
6
+
7
+ Installation
8
+ ============
9
+
10
+ ```ruby
11
+ gem 'numbers_in_words'
12
+ ```
13
+
14
+ Usage
15
+ =========
16
+
17
+ ```ruby
18
+ require 'numbers_in_words'
19
+
20
+ NumbersInWords.in_words(112)
21
+ #=> one hundred and twelve
22
+
23
+ NumbersInWords.in_numbers("one googol")
24
+ #=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
25
+
26
+ NumbersInWords.in_numbers("Seventy million, five-hundred and fifty six thousand point eight nine three")
27
+ #=> 70556000.893
28
+
29
+ NumbersInWords.in_numbers("nineteen sixty five")
30
+ #=> 1965
31
+ ```
32
+
33
+
34
+ Monkey patch version
35
+
36
+ ```ruby
37
+ require 'numbers_in_words'
38
+ require 'numbers_in_words/duck_punch'
39
+ 112.in_words
40
+ #=> one hundred and twelve
41
+
42
+ "one googol".in_numbers
43
+ #=>10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
44
+
45
+ "Seventy million, five-hundred and fifty six thousand point eight nine three".in_numbers
46
+ #=> 70556000.893
47
+ ```
data/Rakefile CHANGED
@@ -1,44 +1,3 @@
1
- require 'rubygems'
2
- require 'rake'
3
- require 'echoe'
4
- require 'metric_fu'
5
-
6
- Echoe.new('numbers_in_words','0.1.1') do |e|
7
- e.description = "#in_words method for integers and #in_numbers for strings"
8
- e.summary = "Example: 123.in_words #=> \"one hundred and twenty three\", \"seventy-five point eight\".in_numbers #=> 75.8"
9
- e.url = "http://rubygems.org/gems/numbers_in_words"
10
- e.author = "Mark Burns"
11
- e.email = "markthedeveloper@gmail.com"
12
- e.ignore_pattern = ["tmp/*",".git/*"]
13
- e.dependencies = ['active_support']
14
- end
15
-
16
-
17
- MetricFu::Configuration.run do |config|
18
- #define which metrics you want to use
19
- config.metrics = [:churn, :saikuro, :stats, :flog, :flay, :reek, :roodi, :rcov]
20
- config.graphs = [:flog, :flay, :reek, :roodi, :rcov]
21
- config.flay = { :dirs_to_flay => ['lib'], :minimum_score => 100 }
22
- config.flog = { :dirs_to_flog => [ 'lib'] }
23
- config.reek = { :dirs_to_reek => ['lib'] }
24
- config.roodi = { :dirs_to_roodi => ['lib'] }
25
- config.saikuro = { :output_directory => 'scratch_directory/saikuro',
26
- :input_directory => ['lib'],
27
- :cyclo => "",
28
- :filter_cyclo => "0",
29
- :warn_cyclo => "5",
30
- :error_cyclo => "7",
31
- :formater => "text"} #this needs to be set to "text"
32
- config.churn = { :start_date => "1 year ago", :minimum_churn_count => 10}
33
- config.rcov = { :environment => 'test',
34
- :test_files => ['spec/**/*_spec.rb'],
35
- :rcov_opts => ["--sort coverage",
36
- "--no-html",
37
- "--text-coverage",
38
- "--no-color",
39
- "--profile",
40
- "--rails",
41
- "--exclude /gems/,/Library/,spec"]}
42
- config.graph_engine = :bluff
43
- end
1
+ # frozen_string_literal: true
44
2
 
3
+ require 'bundler/gem_tasks'
@@ -0,0 +1,2 @@
1
+ bundle exec rspec spec/non_monkey_patch_spec.rb
2
+ bundle exec rspec spec
@@ -1,7 +1,58 @@
1
- require 'rubygems'
1
+ # frozen_string_literal: true
2
2
 
3
- $LOAD_PATH.unshift File.expand_path(File.join(File.dirname(__FILE__)))
3
+ require 'numbers_in_words/version'
4
+ require 'numbers_in_words/exceptional_numbers'
5
+ require 'numbers_in_words/parsing/number_parser'
6
+ require 'numbers_in_words/to_word'
7
+ require 'numbers_in_words/parsing/to_number'
4
8
 
5
- require 'numbers'
6
- require 'words'
9
+ module NumbersInWords
10
+ LENGTH_OF_GOOGOL = 101 # length of the string i.e. one with 100 zeros
11
+ Error = ::Class.new(::StandardError)
12
+ AmbiguousParsingError = ::Class.new(Error)
13
+ DivideByZeroError = ::Class.new(Error)
14
+ InvalidNumber = ::Class.new(Error)
7
15
 
16
+ class << self
17
+ extend Forwardable
18
+ def_delegators :exceptional_numbers, :fraction
19
+
20
+ def in_words(num, fraction: false)
21
+ ToWord.new(num).in_words(fraction: fraction)
22
+ end
23
+
24
+ def in_numbers(words, only_compress: false)
25
+ ToNumber.new(words, only_compress).call
26
+ end
27
+
28
+ def exceptional_numbers
29
+ @exceptional_numbers ||= ExceptionalNumbers.new
30
+ end
31
+
32
+ def lookup(number)
33
+ exceptional_numbers.lookup(number)
34
+ end
35
+
36
+ def exceptional_number(text)
37
+ exceptional_numbers_to_i[text]
38
+ end
39
+
40
+ def power_of_ten(text)
41
+ powers_of_ten_to_i[text]
42
+ end
43
+
44
+ private
45
+
46
+ def exceptional_numbers_to_i
47
+ @exceptional_numbers_to_i ||= swap_keys exceptional_numbers.to_h
48
+ end
49
+
50
+ def powers_of_ten_to_i
51
+ @powers_of_ten_to_i ||= swap_keys POWERS_OF_TEN
52
+ end
53
+
54
+ def swap_keys(hash)
55
+ hash.each_with_object({}) { |(k, v), h| h[v] = k }
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module NumbersInWords
4
+ module NumericExtension
5
+ def in_words(fraction: false)
6
+ NumbersInWords::ToWord.new(self).in_words(fraction: fraction)
7
+ end
8
+ end
9
+
10
+ module StringExtension
11
+ def in_numbers(only_compress: false)
12
+ NumbersInWords::ToNumber.new(self, only_compress).call
13
+ end
14
+ end
15
+ end
16
+
17
+ class String
18
+ include NumbersInWords::StringExtension
19
+ end
20
+
21
+ class Numeric
22
+ include NumbersInWords::NumericExtension
23
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'forwardable'
4
+
5
+ require_relative 'fraction'
6
+ require_relative 'powers_of_ten'
7
+
8
+ module NumbersInWords
9
+ class ExceptionalNumbers
10
+ extend Forwardable
11
+
12
+ DEFINITIONS = {
13
+ 0 => { number: 'zero', ordinal: 'zeroth' },
14
+ 1 => { number: 'one', ordinal: 'first' },
15
+ 2 => { number: 'two', ordinal: 'second', fraction: { singular: 'half', plural: 'halves' } },
16
+ 3 => { number: 'three', ordinal: 'third' },
17
+ 4 => { number: 'four', ordinal: 'fourth', fraction: { singular: 'quarter', plural: 'quarters' } },
18
+ 5 => { number: 'five', ordinal: 'fifth' },
19
+ 6 => { number: 'six' },
20
+ 7 => { number: 'seven' },
21
+ 8 => { number: 'eight', ordinal: 'eighth' },
22
+ 9 => { number: 'nine', ordinal: 'ninth' },
23
+ 10 => { number: 'ten' },
24
+ 11 => { number: 'eleven' },
25
+ 12 => { number: 'twelve', ordinal: 'twelfth' },
26
+ 13 => { number: 'thirteen' },
27
+ 14 => { number: 'fourteen' },
28
+ 15 => { number: 'fifteen' },
29
+ 16 => { number: 'sixteen' },
30
+ 17 => { number: 'seventeen' },
31
+ 18 => { number: 'eighteen' },
32
+ 19 => { number: 'nineteen' },
33
+ 20 => { number: 'twenty', ordinal: 'twentieth' },
34
+ 30 => { number: 'thirty', ordinal: 'thirtieth' },
35
+ 40 => { number: 'forty', ordinal: 'fortieth' },
36
+ 50 => { number: 'fifty', ordinal: 'fiftieth' },
37
+ 60 => { number: 'sixty', ordinal: 'sixtieth' },
38
+ 70 => { number: 'seventy', ordinal: 'seventieth' },
39
+ 80 => { number: 'eighty', ordinal: 'eightieth' },
40
+ 90 => { number: 'ninety', ordinal: 'ninetieth' }
41
+ }.freeze
42
+
43
+ def fraction_names
44
+ @fraction_names ||= determine_fraction_names
45
+ end
46
+
47
+ def lookup_fraction(words)
48
+ fraction_lookup[words]
49
+ end
50
+
51
+ def fraction_lookup
52
+ @fraction_lookup ||= generate_fraction_lookup
53
+ end
54
+
55
+ def lookup(number)
56
+ to_h[number]
57
+ end
58
+
59
+ def fraction(denominator: nil, numerator: nil, word: nil)
60
+ raise unless denominator || word
61
+
62
+ numerator ||= 1
63
+
64
+ denominator ||= NumbersInWords.in_numbers(word)
65
+
66
+ Fraction.new(denominator: denominator, numerator: numerator, attributes: DEFINITIONS[denominator])
67
+ end
68
+
69
+ def to_h
70
+ @to_h ||= DEFINITIONS.transform_values do |h|
71
+ h[:number]
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def generate_fraction_lookup
78
+ named_fractions.each_with_object({}) do |f, result|
79
+ f.lookup_keys.each do |k|
80
+ key = k.split(' ').last
81
+ result[key] = 1.0 / f.denominator.to_f
82
+ end
83
+ end
84
+ end
85
+
86
+ def named_fractions
87
+ @named_fractions ||= numbers.flat_map do |n|
88
+ [
89
+ Fraction.new(denominator: n, numerator: 1),
90
+ Fraction.new(denominator: n, numerator: 2)
91
+ ]
92
+ end
93
+ end
94
+
95
+ def numbers
96
+ (2..100).to_a + powers_of_ten_skipping_googolplex.map { |p| 10**p }
97
+ end
98
+
99
+ def powers_of_ten_skipping_googolplex
100
+ POWERS_OF_TEN.keys[0..-2]
101
+ end
102
+
103
+ def determine_fraction_names
104
+ names = named_fractions.map(&:in_words)
105
+
106
+ words = names.map(&:split).map(&:last)
107
+ words += strip_punctuation(words)
108
+ words.uniq
109
+ end
110
+
111
+ def strip_punctuation(words)
112
+ words.map { |w| w.gsub(/^a-z/, ' ') }
113
+ end
114
+ end
115
+ end