numbers_in_words 0.1.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
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