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.
- checksums.yaml +7 -0
- data/.codeclimate.yml +14 -0
- data/.gitignore +7 -0
- data/.rspec +2 -0
- data/.rubocop.yml +58 -0
- data/.travis.yml +15 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +2 -43
- data/bin/spec +2 -0
- data/lib/numbers_in_words.rb +55 -4
- data/lib/numbers_in_words/duck_punch.rb +23 -0
- data/lib/numbers_in_words/exceptional_numbers.rb +115 -0
- data/lib/numbers_in_words/fraction.rb +136 -0
- data/lib/numbers_in_words/number_group.rb +64 -0
- data/lib/numbers_in_words/parsing/fraction_parsing.rb +34 -0
- data/lib/numbers_in_words/parsing/number_parser.rb +98 -0
- data/lib/numbers_in_words/parsing/pair_parsing.rb +64 -0
- data/lib/numbers_in_words/parsing/parse_fractions.rb +45 -0
- data/lib/numbers_in_words/parsing/parse_individual_number.rb +68 -0
- data/lib/numbers_in_words/parsing/parse_status.rb +17 -0
- data/lib/numbers_in_words/parsing/special.rb +67 -0
- data/lib/numbers_in_words/parsing/to_number.rb +77 -0
- data/lib/numbers_in_words/powers_of_ten.rb +49 -0
- data/lib/numbers_in_words/to_word.rb +84 -0
- data/lib/numbers_in_words/version.rb +5 -0
- data/lib/numbers_in_words/writer.rb +69 -0
- data/numbers_in_words.gemspec +20 -27
- data/spec/exceptional_numbers_spec.rb +26 -0
- data/spec/fraction_spec.rb +152 -0
- data/spec/fractions_spec.rb +31 -0
- data/spec/non_monkey_patch_spec.rb +51 -0
- data/spec/number_group_spec.rb +17 -0
- data/spec/number_parser_spec.rb +31 -0
- data/spec/numbers_in_words_spec.rb +69 -83
- data/spec/numerical_strings_spec.rb +35 -0
- data/spec/spec_helper.rb +26 -0
- data/spec/to_word_spec.rb +18 -0
- data/spec/words_in_numbers_spec.rb +137 -119
- data/spec/writer_spec.rb +26 -0
- data/spec/years_spec.rb +27 -0
- metadata +95 -45
- data/CHANGELOG +0 -1
- data/Manifest +0 -11
- data/README +0 -84
- data/examples/display_numbers_in_words.rb +0 -22
- data/init.rb +0 -8
- data/lib/numbers.rb +0 -260
- data/lib/words.rb +0 -221
checksums.yaml
ADDED
@@ -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
|
data/.codeclimate.yml
ADDED
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
@@ -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
|
data/.travis.yml
ADDED
@@ -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
data/LICENSE.txt
ADDED
@@ -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.
|
data/README.md
ADDED
@@ -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
|
-
|
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'
|
data/bin/spec
ADDED
data/lib/numbers_in_words.rb
CHANGED
@@ -1,7 +1,58 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
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
|
-
|
6
|
-
|
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
|