german_numbers 0.1 → 0.5
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.
- checksums.yaml +4 -4
- data/.circleci/config.yml +30 -0
- data/.gitignore +2 -1
- data/Gemfile.lock +25 -1
- data/LICENSE +21 -0
- data/README.md +16 -19
- data/german_numbers.gemspec +4 -1
- data/lib/german_numbers.rb +21 -3
- data/lib/german_numbers/parser/parser.rb +95 -0
- data/lib/german_numbers/parser/small_number_parser.rb +51 -0
- data/lib/german_numbers/parser/stack_machine.rb +106 -0
- data/lib/german_numbers/state_machine.rb +95 -0
- data/lib/german_numbers/{to_words.rb → stringifier.rb} +6 -5
- data/lib/german_numbers/version.rb +1 -1
- metadata +51 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 013dae0466819b3a48241f72efbecfc135d997a0
|
|
4
|
+
data.tar.gz: 651d87269eb855b5dab6e6a43f68ae3e7ddf098d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 292e5224dceb2cfbb925d57893ae34e96ea79d207f73817468e7fa07c2209a8ed85762ff48708cfa93874d6b6338a190adc1bc66eb767e551730a250be7e3da1
|
|
7
|
+
data.tar.gz: '09ceb4a387b0334f1e1655b4632876bcbce5eb18245a7852afe413c27cd288ec8b7fc70648fcf0806b22ce93a880eb39eb083ae6e291e7f9bf3b82d76b084c7b'
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
jobs:
|
|
3
|
+
build:
|
|
4
|
+
docker:
|
|
5
|
+
- image: circleci/ruby:2.4.1
|
|
6
|
+
working_directory: ~/german_numbers
|
|
7
|
+
|
|
8
|
+
steps:
|
|
9
|
+
- checkout
|
|
10
|
+
|
|
11
|
+
- restore_cache:
|
|
12
|
+
keys:
|
|
13
|
+
- v1-dependencies-{{ checksum "Gemfile.lock" }}
|
|
14
|
+
- v1-dependencies-
|
|
15
|
+
|
|
16
|
+
- run:
|
|
17
|
+
name: install dependencies
|
|
18
|
+
command: bundle install --jobs=4 --retry=3 --path vendor/bundle
|
|
19
|
+
|
|
20
|
+
- save_cache:
|
|
21
|
+
paths:
|
|
22
|
+
- ./vendor/bundle
|
|
23
|
+
key: v1-dependencies-{{ checksum "Gemfile.lock" }}
|
|
24
|
+
|
|
25
|
+
- run:
|
|
26
|
+
name: run rubocop
|
|
27
|
+
command: bundle exec rubocop
|
|
28
|
+
- run:
|
|
29
|
+
name: run tests
|
|
30
|
+
command: bundle exec rspec
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,14 +1,25 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
german_numbers (0.
|
|
4
|
+
german_numbers (0.5)
|
|
5
5
|
|
|
6
6
|
GEM
|
|
7
7
|
remote: https://rubygems.org/
|
|
8
8
|
specs:
|
|
9
|
+
ast (2.4.0)
|
|
10
|
+
coderay (1.1.2)
|
|
9
11
|
diff-lcs (1.3)
|
|
10
12
|
docile (1.1.5)
|
|
11
13
|
json (2.1.0)
|
|
14
|
+
method_source (0.9.0)
|
|
15
|
+
parallel (1.12.1)
|
|
16
|
+
parser (2.5.0.2)
|
|
17
|
+
ast (~> 2.4.0)
|
|
18
|
+
powerpack (0.1.1)
|
|
19
|
+
pry (0.11.3)
|
|
20
|
+
coderay (~> 1.1.0)
|
|
21
|
+
method_source (~> 0.9.0)
|
|
22
|
+
rainbow (3.0.0)
|
|
12
23
|
rake (10.5.0)
|
|
13
24
|
rspec (3.7.0)
|
|
14
25
|
rspec-core (~> 3.7.0)
|
|
@@ -23,11 +34,21 @@ GEM
|
|
|
23
34
|
diff-lcs (>= 1.2.0, < 2.0)
|
|
24
35
|
rspec-support (~> 3.7.0)
|
|
25
36
|
rspec-support (3.7.0)
|
|
37
|
+
rubocop (0.52.1)
|
|
38
|
+
parallel (~> 1.10)
|
|
39
|
+
parser (>= 2.4.0.2, < 3.0)
|
|
40
|
+
powerpack (~> 0.1)
|
|
41
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
42
|
+
ruby-progressbar (~> 1.7)
|
|
43
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
|
44
|
+
ruby-prof (0.17.0)
|
|
45
|
+
ruby-progressbar (1.9.0)
|
|
26
46
|
simplecov (0.15.1)
|
|
27
47
|
docile (~> 1.1.0)
|
|
28
48
|
json (>= 1.8, < 3)
|
|
29
49
|
simplecov-html (~> 0.10.0)
|
|
30
50
|
simplecov-html (0.10.2)
|
|
51
|
+
unicode-display_width (1.3.0)
|
|
31
52
|
|
|
32
53
|
PLATFORMS
|
|
33
54
|
ruby
|
|
@@ -35,8 +56,11 @@ PLATFORMS
|
|
|
35
56
|
DEPENDENCIES
|
|
36
57
|
bundler (~> 1.16)
|
|
37
58
|
german_numbers!
|
|
59
|
+
pry (~> 0.11)
|
|
38
60
|
rake (~> 10.0)
|
|
39
61
|
rspec (~> 3.7)
|
|
62
|
+
rubocop (~> 0.52)
|
|
63
|
+
ruby-prof (~> 0.17)
|
|
40
64
|
simplecov (~> 0.15)
|
|
41
65
|
|
|
42
66
|
BUNDLED WITH
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2018 Gleb Sinyavsky
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
# GermanNumbers
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
German numbers is a ruby gem for converting numbers into German strings and vise-versa.
|
|
4
|
+
It supports numbers up to 999 999 999 999. Also it can handle malformed and invalid strings. See
|
|
5
|
+
examples below.
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
```ruby
|
|
12
|
-
gem 'german_numbers'
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
And then execute:
|
|
9
|
+
`gem install german_numbers`
|
|
16
10
|
|
|
17
|
-
|
|
11
|
+
or
|
|
18
12
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
$ gem install german_numbers
|
|
13
|
+
`gem 'german_numbers'` in your Gemfile.
|
|
22
14
|
|
|
23
15
|
## Usage
|
|
24
16
|
|
|
25
|
-
|
|
17
|
+
```ruby
|
|
18
|
+
require 'german_numbers'
|
|
19
|
+
|
|
20
|
+
GermanNumbers.stringify(213_431_983_111) # => "zweihundertdreizehn Milliarden vierhunderteinunddreißig Millionen neunhundertdreiundachtzigtausendeinhundertelf"
|
|
26
21
|
|
|
27
|
-
|
|
22
|
+
GermanNumbers.parse("zweihundertdreizehn Milliarden vierhunderteinunddreißig Millionen neunhundertdreiundachtzigtausendeinhundertelf") # => 213_431_983_111
|
|
23
|
+
GermanNumbers.parse("invalid") # => GermanNumbers::Parser::ParsingError: invalid is not a valid German number
|
|
28
24
|
|
|
29
|
-
|
|
25
|
+
GermanNumbers.valid?("zweihundertdreizehn Milliarden vierhunderteinunddreißig Millionen neunhundertdreiundachtzigtausendeinhundertelf") # => true
|
|
26
|
+
GermanNumbers.valid?("invalid") # => false
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
```
|
|
32
29
|
|
|
33
30
|
## Contributing
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
You know=)
|
data/german_numbers.gemspec
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
lib = File.expand_path('../lib', __FILE__)
|
|
5
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
6
|
-
require 'german_numbers'
|
|
6
|
+
require 'german_numbers/version'
|
|
7
7
|
|
|
8
8
|
Gem::Specification.new do |spec|
|
|
9
9
|
spec.name = 'german_numbers'
|
|
@@ -24,7 +24,10 @@ Gem::Specification.new do |spec|
|
|
|
24
24
|
spec.require_paths = ['lib']
|
|
25
25
|
|
|
26
26
|
spec.add_development_dependency 'bundler', '~> 1.16'
|
|
27
|
+
spec.add_development_dependency 'pry', '~> 0.11'
|
|
27
28
|
spec.add_development_dependency 'rake', '~> 10.0'
|
|
28
29
|
spec.add_development_dependency 'rspec', '~> 3.7'
|
|
30
|
+
spec.add_development_dependency 'rubocop', '~> 0.52'
|
|
31
|
+
spec.add_development_dependency 'ruby-prof', '~> 0.17'
|
|
29
32
|
spec.add_development_dependency 'simplecov', '~> 0.15'
|
|
30
33
|
end
|
data/lib/german_numbers.rb
CHANGED
|
@@ -1,14 +1,32 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
3
5
|
require 'german_numbers/version'
|
|
4
|
-
require 'german_numbers/
|
|
6
|
+
require 'german_numbers/state_machine'
|
|
5
7
|
|
|
6
8
|
module GermanNumbers
|
|
7
9
|
DIGITS = YAML.load_file(File.join(File.dirname(__FILE__), '..', 'data', 'de.yml'))['de']
|
|
8
10
|
|
|
9
11
|
class << self
|
|
10
|
-
def
|
|
11
|
-
|
|
12
|
+
def stringify(number)
|
|
13
|
+
GermanNumbers::Stringifier.new.words(number)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def parse(string)
|
|
17
|
+
GermanNumbers::Parser::Parser.new.parse(string)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def valid?(string)
|
|
21
|
+
GermanNumbers::Parser::Parser.new.parse(string)
|
|
22
|
+
true
|
|
23
|
+
rescue GermanNumbers::Parser::ParsingError
|
|
24
|
+
false
|
|
12
25
|
end
|
|
13
26
|
end
|
|
14
27
|
end
|
|
28
|
+
|
|
29
|
+
require 'german_numbers/stringifier'
|
|
30
|
+
require 'german_numbers/parser/small_number_parser'
|
|
31
|
+
require 'german_numbers/parser/stack_machine'
|
|
32
|
+
require 'german_numbers/parser/parser'
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GermanNumbers
|
|
4
|
+
module Parser
|
|
5
|
+
class ParsingError < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Parser
|
|
9
|
+
extend GermanNumbers::StateMachine
|
|
10
|
+
state_machine_for :order do
|
|
11
|
+
state :initial, can_be_initial: true, final: false
|
|
12
|
+
state :thousands
|
|
13
|
+
state :million_keyword, final: false
|
|
14
|
+
state :millionen_keyword, final: false
|
|
15
|
+
state :million, final: true
|
|
16
|
+
state :millions, final: true
|
|
17
|
+
state :milliarde_keyword, final: false
|
|
18
|
+
state :milliarden_keyword, final: false
|
|
19
|
+
state :billion, final: true
|
|
20
|
+
state :billions, final: true
|
|
21
|
+
|
|
22
|
+
transition from: :initial, to: %i[thousands million_keyword millionen_keyword
|
|
23
|
+
milliarde_keyword milliarden_keyword]
|
|
24
|
+
transition from: :thousands, to: %i[million_keyword millionen_keyword milliarde_keyword milliarden_keyword]
|
|
25
|
+
transition from: :million_keyword, to: :million
|
|
26
|
+
transition from: :millionen_keyword, to: :millions
|
|
27
|
+
transition from: :milliarde_keyword, to: :billion
|
|
28
|
+
transition from: :milliarden_keyword, to: :billions
|
|
29
|
+
transition from: :million, to: %i[milliarde_keyword milliarden_keyword]
|
|
30
|
+
transition from: :millions, to: %i[milliarde_keyword milliarden_keyword]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
DIGITS = GermanNumbers::DIGITS.invert
|
|
34
|
+
ERRORS = ['ein', 'sech', 'sieb', nil, ''].freeze
|
|
35
|
+
KEYWORDS = {
|
|
36
|
+
'Million' => :million_keyword,
|
|
37
|
+
'Millionen' => :millionen_keyword,
|
|
38
|
+
'Milliarde' => :milliarde_keyword,
|
|
39
|
+
'Milliarden' => :milliarden_keyword
|
|
40
|
+
}.freeze
|
|
41
|
+
|
|
42
|
+
def initialize
|
|
43
|
+
initialize_order(:initial)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parse(string)
|
|
47
|
+
raise ParsingError if ERRORS.include?(string)
|
|
48
|
+
string.split(' ').reverse.inject(0, &method(:parse_part)).tap do
|
|
49
|
+
raise ParsingError unless final_order_state?
|
|
50
|
+
end
|
|
51
|
+
rescue ParsingError, StateMachine::StateError
|
|
52
|
+
raise ParsingError, "#{string} is not a valid German number"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# rubocop:disable Metrics/AbcSize
|
|
56
|
+
# rubocop:disable Metrics/MethodLength
|
|
57
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
58
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
59
|
+
def parse_part(sum, part)
|
|
60
|
+
if order_state == :initial && KEYWORDS[part].nil?
|
|
61
|
+
self.order_state = :thousands
|
|
62
|
+
return parse_part(sum, part)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
unless (st = KEYWORDS[part]).nil?
|
|
66
|
+
self.order_state = st
|
|
67
|
+
return sum
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
return SmallNumberParser.new.parse(part) if order_state == :thousands
|
|
71
|
+
|
|
72
|
+
self.order_state = :million if order_state == :million_keyword
|
|
73
|
+
self.order_state = :millions if order_state == :millionen_keyword
|
|
74
|
+
self.order_state = :billion if order_state == :milliarde_keyword
|
|
75
|
+
self.order_state = :billions if order_state == :milliarden_keyword
|
|
76
|
+
|
|
77
|
+
if order_state == :million
|
|
78
|
+
raise ParsingError unless part == 'eine'
|
|
79
|
+
return sum + 1_000_000
|
|
80
|
+
end
|
|
81
|
+
if order_state == :billion
|
|
82
|
+
raise ParsingError unless part == 'eine'
|
|
83
|
+
return sum + 1_000_000_000
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
return sum + SmallNumberParser.new(2..999).parse(part) * 1_000_000 if order_state == :millions
|
|
87
|
+
return sum + SmallNumberParser.new(2..999).parse(part) * 1_000_000_000 if order_state == :billions
|
|
88
|
+
end
|
|
89
|
+
# rubocop:enable Metrics/AbcSize
|
|
90
|
+
# rubocop:enable Metrics/MethodLength
|
|
91
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
92
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GermanNumbers
|
|
4
|
+
module Parser
|
|
5
|
+
class SmallNumberParser
|
|
6
|
+
extend GermanNumbers::StateMachine
|
|
7
|
+
|
|
8
|
+
state_machine_for :order do
|
|
9
|
+
state :initial, can_be_initial: true, final: false
|
|
10
|
+
state :units
|
|
11
|
+
state :tausend_keyword, unique: true, final: false
|
|
12
|
+
state :thousands
|
|
13
|
+
|
|
14
|
+
transition from: :initial, to: %i[units tausend_keyword]
|
|
15
|
+
transition from: :tausend_keyword, to: :thousands
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def initialize(range = 0..999_999)
|
|
19
|
+
initialize_order(:initial)
|
|
20
|
+
@range = range
|
|
21
|
+
@k = 1
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def parse(string)
|
|
25
|
+
string.split(/(tausend)/).reverse.inject(0, &method(:parse_part))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def parse_part(sum, part)
|
|
31
|
+
if order_state == :tausend_keyword
|
|
32
|
+
self.order_state = :thousands
|
|
33
|
+
@k *= 1000
|
|
34
|
+
end
|
|
35
|
+
if part == 'tausend'
|
|
36
|
+
self.order_state = :tausend_keyword
|
|
37
|
+
return sum
|
|
38
|
+
end
|
|
39
|
+
raise ParsingError if (part == 'eins' || part == 'null') && order_state == :thousands
|
|
40
|
+
parse_number(sum, part)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def parse_number(sum, part)
|
|
44
|
+
m = StackMachine.new
|
|
45
|
+
(sum + part.split('').reverse.inject(0, &m.method(:step)) * @k).tap do |res|
|
|
46
|
+
raise ParsingError if !m.empty? || !m.final_stack_state? || !@range.cover?(res)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GermanNumbers
|
|
4
|
+
module Parser
|
|
5
|
+
class StackMachine
|
|
6
|
+
extend GermanNumbers::StateMachine
|
|
7
|
+
|
|
8
|
+
state_machine_for :stack do
|
|
9
|
+
state :initial, can_be_initial: true, final: false
|
|
10
|
+
state :null, unique: true
|
|
11
|
+
state :eins, unique: true
|
|
12
|
+
state :zehn, unique: true
|
|
13
|
+
state :short_units, unique: true
|
|
14
|
+
state :under_twenty, unique: true
|
|
15
|
+
state :dozens, unique: true
|
|
16
|
+
state :und_keyword, final: false, unique: true
|
|
17
|
+
state :units
|
|
18
|
+
state :hundert_keyword, final: false, unique: true
|
|
19
|
+
state :hundreds
|
|
20
|
+
|
|
21
|
+
transition from: :initial, to: %i[units hundert_keyword dozens null eins zehn under_twenty]
|
|
22
|
+
transition from: :dozens, to: %i[und_keyword hundert_keyword]
|
|
23
|
+
transition from: :zehn, to: %i[hundert_keyword short_units]
|
|
24
|
+
|
|
25
|
+
transition from: :und_keyword, to: :units
|
|
26
|
+
transition from: :units, to: :hundert_keyword
|
|
27
|
+
transition from: :hundert_keyword, to: :hundreds
|
|
28
|
+
transition from: :hundreds, to: :units
|
|
29
|
+
transition from: :eins, to: :hundert_keyword
|
|
30
|
+
transition from: :short_units, to: :hundert_keyword
|
|
31
|
+
transition from: :under_twenty, to: :hundert_keyword
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
SHORT = {
|
|
35
|
+
'eins' => 1,
|
|
36
|
+
'sech' => 6,
|
|
37
|
+
'sieb' => 7
|
|
38
|
+
}.freeze
|
|
39
|
+
|
|
40
|
+
KEYWORDS = {
|
|
41
|
+
'und' => :und_keyword,
|
|
42
|
+
'hundert' => :hundert_keyword
|
|
43
|
+
}.freeze
|
|
44
|
+
|
|
45
|
+
SHORT_UNITS = Set.new(%w(drei vier fünf sech sieb acht neun)).freeze
|
|
46
|
+
|
|
47
|
+
def initialize
|
|
48
|
+
initialize_stack(:initial)
|
|
49
|
+
@collector = ''
|
|
50
|
+
@k = 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# rubocop:disable Metrics/MethodLength
|
|
54
|
+
def step(result, letter)
|
|
55
|
+
@collector = letter + @collector
|
|
56
|
+
num = SHORT[@collector] || Parser::DIGITS[@collector]
|
|
57
|
+
unless (s = select_state(num, @collector)).nil?
|
|
58
|
+
self.stack_state = s
|
|
59
|
+
end
|
|
60
|
+
if stack_state == :hundert_keyword
|
|
61
|
+
self.stack_state = :hundreds
|
|
62
|
+
@k = 100
|
|
63
|
+
end
|
|
64
|
+
unless (st = KEYWORDS[@collector]).nil?
|
|
65
|
+
self.stack_state = st
|
|
66
|
+
@collector = ''
|
|
67
|
+
return result
|
|
68
|
+
end
|
|
69
|
+
return result if num.nil?
|
|
70
|
+
@collector = ''
|
|
71
|
+
result + num * @k
|
|
72
|
+
end
|
|
73
|
+
# rubocop:enable Metrics/MethodLength
|
|
74
|
+
|
|
75
|
+
def empty?
|
|
76
|
+
@collector.empty?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def select_state(num, collector)
|
|
82
|
+
if stack_state == :zehn && SHORT_UNITS.include?(collector)
|
|
83
|
+
:short_units
|
|
84
|
+
elsif collector == 'eins'
|
|
85
|
+
:eins
|
|
86
|
+
elsif collector == 'null'
|
|
87
|
+
:null
|
|
88
|
+
else
|
|
89
|
+
num_state(num)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
94
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
|
95
|
+
def num_state(num)
|
|
96
|
+
return if num.nil?
|
|
97
|
+
return :zehn if num == 10
|
|
98
|
+
return :units if num >= 1 && num <= 9
|
|
99
|
+
return :under_twenty if num >= 11 && num <= 19
|
|
100
|
+
return :dozens if num >= 20 && num <= 99
|
|
101
|
+
end
|
|
102
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
103
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module GermanNumbers
|
|
4
|
+
module StateMachine
|
|
5
|
+
class StateError < StandardError
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class State
|
|
9
|
+
attr_reader :name
|
|
10
|
+
|
|
11
|
+
def initialize(name, initial, final, unique)
|
|
12
|
+
@name = name
|
|
13
|
+
@initial = initial
|
|
14
|
+
@final = final
|
|
15
|
+
@unique = unique
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def can_be_initial?
|
|
19
|
+
@initial
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def final?
|
|
23
|
+
@final
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def unique?
|
|
27
|
+
@unique
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Machine
|
|
32
|
+
attr_reader :states, :transitions
|
|
33
|
+
|
|
34
|
+
def initialize
|
|
35
|
+
@states = {}
|
|
36
|
+
@transitions = Hash.new { [] }
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def state(state, can_be_initial: false, final: true, unique: false)
|
|
40
|
+
@states[state] = State.new(state, can_be_initial, final, unique)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def transition(from:, to:)
|
|
44
|
+
to = [to].flatten
|
|
45
|
+
validate_state!(from, *to)
|
|
46
|
+
to.each do |s|
|
|
47
|
+
@transitions[from] = @transitions[from] << s
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def validate_state!(*states)
|
|
52
|
+
states.each do |state|
|
|
53
|
+
raise StateError, "#{state} is unknown state" unless @states.include?(state)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# rubocop:disable Metrics/AbcSize
|
|
59
|
+
# rubocop:disable Metrics/MethodLength
|
|
60
|
+
def state_machine_for(field, &block)
|
|
61
|
+
m = Machine.new
|
|
62
|
+
m.instance_eval(&block)
|
|
63
|
+
var_name = "@#{field}_state"
|
|
64
|
+
set_name = "#{field}_state="
|
|
65
|
+
history_name = "@#{field}_state_history"
|
|
66
|
+
|
|
67
|
+
define_method("#{field}_state") do
|
|
68
|
+
instance_variable_get(var_name)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
define_method(set_name) do |ns|
|
|
72
|
+
state = instance_variable_get(var_name)
|
|
73
|
+
raise StateError, "#{ns} is not possible state after #{state}" unless m.transitions[state].include?(ns)
|
|
74
|
+
if instance_variable_get(history_name).include?(ns) && m.states[ns].unique?
|
|
75
|
+
raise StateError, "#{ns} is a unique state and has already been taken"
|
|
76
|
+
end
|
|
77
|
+
instance_variable_get(history_name) << ns
|
|
78
|
+
instance_variable_set(var_name, ns)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
define_method("initialize_#{field}") do |initial|
|
|
82
|
+
m.validate_state!(initial)
|
|
83
|
+
raise StateError, "#{initial} is not possible initial state" unless m.states[initial].can_be_initial?
|
|
84
|
+
instance_variable_set(history_name, Set.new)
|
|
85
|
+
instance_variable_set(var_name, initial)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
define_method("final_#{field}_state?") do
|
|
89
|
+
m.states[instance_variable_get(var_name)].final?
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
# rubocop:enable Metrics/AbcSize
|
|
93
|
+
# rubocop:enable Metrics/MethodLength
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require 'yaml'
|
|
4
|
-
|
|
5
3
|
module GermanNumbers
|
|
6
|
-
class
|
|
4
|
+
class Stringifier
|
|
5
|
+
SMALL_KEYWORDS = %w(hundert tausend).freeze
|
|
6
|
+
BIG_KEYWORDS = %w(Million Milliarde).freeze
|
|
7
|
+
|
|
7
8
|
def words(number)
|
|
8
9
|
raise ArgumentError if number > 999_999_999_999 || number.negative?
|
|
9
10
|
return postprocess(DIGITS[number]) unless DIGITS[number].nil?
|
|
@@ -35,8 +36,8 @@ module GermanNumbers
|
|
|
35
36
|
|
|
36
37
|
def postprocess(result)
|
|
37
38
|
result += 's' if result.end_with?('ein')
|
|
38
|
-
result = 'ein' + result if
|
|
39
|
-
result = 'eine ' + result if
|
|
39
|
+
result = 'ein' + result if SMALL_KEYWORDS.include?(result)
|
|
40
|
+
result = 'eine ' + result if BIG_KEYWORDS.include?(result)
|
|
40
41
|
result.strip
|
|
41
42
|
end
|
|
42
43
|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: german_numbers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: '0.
|
|
4
|
+
version: '0.5'
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gleb Sinyavsky
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2018-
|
|
11
|
+
date: 2018-02-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: bundler
|
|
@@ -24,6 +24,20 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '1.16'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: pry
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0.11'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0.11'
|
|
27
41
|
- !ruby/object:Gem::Dependency
|
|
28
42
|
name: rake
|
|
29
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,6 +66,34 @@ dependencies:
|
|
|
52
66
|
- - "~>"
|
|
53
67
|
- !ruby/object:Gem::Version
|
|
54
68
|
version: '3.7'
|
|
69
|
+
- !ruby/object:Gem::Dependency
|
|
70
|
+
name: rubocop
|
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
|
72
|
+
requirements:
|
|
73
|
+
- - "~>"
|
|
74
|
+
- !ruby/object:Gem::Version
|
|
75
|
+
version: '0.52'
|
|
76
|
+
type: :development
|
|
77
|
+
prerelease: false
|
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
+
requirements:
|
|
80
|
+
- - "~>"
|
|
81
|
+
- !ruby/object:Gem::Version
|
|
82
|
+
version: '0.52'
|
|
83
|
+
- !ruby/object:Gem::Dependency
|
|
84
|
+
name: ruby-prof
|
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - "~>"
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0.17'
|
|
90
|
+
type: :development
|
|
91
|
+
prerelease: false
|
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
+
requirements:
|
|
94
|
+
- - "~>"
|
|
95
|
+
- !ruby/object:Gem::Version
|
|
96
|
+
version: '0.17'
|
|
55
97
|
- !ruby/object:Gem::Dependency
|
|
56
98
|
name: simplecov
|
|
57
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -73,6 +115,7 @@ executables: []
|
|
|
73
115
|
extensions: []
|
|
74
116
|
extra_rdoc_files: []
|
|
75
117
|
files:
|
|
118
|
+
- ".circleci/config.yml"
|
|
76
119
|
- ".gitignore"
|
|
77
120
|
- ".overcommit.yml"
|
|
78
121
|
- ".rspec"
|
|
@@ -81,6 +124,7 @@ files:
|
|
|
81
124
|
- ".ruby-version"
|
|
82
125
|
- Gemfile
|
|
83
126
|
- Gemfile.lock
|
|
127
|
+
- LICENSE
|
|
84
128
|
- README.md
|
|
85
129
|
- Rakefile
|
|
86
130
|
- bin/console
|
|
@@ -88,7 +132,11 @@ files:
|
|
|
88
132
|
- data/de.yml
|
|
89
133
|
- german_numbers.gemspec
|
|
90
134
|
- lib/german_numbers.rb
|
|
91
|
-
- lib/german_numbers/
|
|
135
|
+
- lib/german_numbers/parser/parser.rb
|
|
136
|
+
- lib/german_numbers/parser/small_number_parser.rb
|
|
137
|
+
- lib/german_numbers/parser/stack_machine.rb
|
|
138
|
+
- lib/german_numbers/state_machine.rb
|
|
139
|
+
- lib/german_numbers/stringifier.rb
|
|
92
140
|
- lib/german_numbers/version.rb
|
|
93
141
|
homepage: https://github.com/zhulik/german_words
|
|
94
142
|
licenses:
|