ya_kansuji 1.0.0 → 1.2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: de9deb8ed6a818c9939482fc8c6408ec00659a67810dfc0eb034ae9bcc821a43
4
- data.tar.gz: 701f44b2de1b5186c1f92f8779840af3ae2ca873fd820a4120e4d653ed194888
3
+ metadata.gz: 97f2f4c063933b7c86ede7c11d8d34704e612f7eb4e82e2f60f469d8aa8ded4c
4
+ data.tar.gz: 4aa55c66f2d6b5ae168278e0b2b1a096e062ae1a3e96d31bcb118f415c184bb0
5
5
  SHA512:
6
- metadata.gz: c8c3263df051a72dc07c1a8a38acf59f1f94c8b896bb9f2d718637f0b808d7d6b0c02bf43975c892fb1c634a5affd9f02f094be4911a926d5218ab467a6ff794
7
- data.tar.gz: 69c8d4b5413a6ec864ac3d7e4e70d258c0f01ecd7684f30ff6c4a414b59751403807eb2850be44489a900df306e54e4f16e50f36555a165675c9f16c4ba33e55
6
+ metadata.gz: 0203eaef1b77450bd6ea5c9666cf695bfe72fd7fb21d966d18bb177fa38db3ca500dff690841c3f1b5d25b9406c62f7bf38a90be98f6c5983f5df68fe65df8cb
7
+ data.tar.gz: 7a420d832dbd4da1149ac293820dca4f510226c53513bc1f579d2fa889cc8b16708f717406c76be8937f21623090eaf7d073896234296598e40e36a8275091ae
data/ChangeLog CHANGED
@@ -1,3 +1,20 @@
1
+ 2026-07-14 Tatsuki Sugiura <sugi@nemui.org>
2
+
3
+ * Version: 1.2.0
4
+ * Require Ruby 2.3.0 or later
5
+ * Accept formatter and options on Integer#to_kan core extension
6
+ * Accept :kansuji as base symbol on String#to_i core extension
7
+ * Raise RangeError when formatting values outside the supported 72-digit range
8
+ * Avoid circular require warnings between the main and refinement entrypoints
9
+ * Optimize formatter chunk processing (up to ~9x faster, far fewer allocations)
10
+
11
+ 2026-07-13 Tatsuki Sugiura <sugi@nemui.org>
12
+
13
+ * Version: 1.1.0
14
+ * Support leading "マイナス" for negative numbers on parsing
15
+ * Fix regexp construction bug for single-character units
16
+ * CI maintenance (update ruby versions, tolerate Coveralls failure)
17
+
1
18
  2025-03-09 Tatsuki Sugiura <sug@nemui.org>
2
19
 
3
20
  * Version: 1.0.0
@@ -12,4 +29,3 @@
12
29
  2019-04-15 Tatsuki Sugiura <sugi@nemui.org>
13
30
 
14
31
  * Version: 0.1.0
15
-
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # YaKansuji - もう一つの (やりすぎ) ruby 漢数字ライブラリ
2
2
 
3
3
  [<img src="https://badge.fury.io/rb/ya_kansuji.svg" alt="Gem Version" />](https://badge.fury.io/rb/ya_kansuji)
4
- [<img src="https://travis-ci.org/sugi/ya_kansuji.svg?branch=master" alt="Build Status" />](https://travis-ci.org/sugi/ya_kansuji)
5
- [<img src="https://coveralls.io/repos/sugi/ya_kansuji/badge.svg?branch=master&service=github" alt="Coverage Status" />](https://coveralls.io/github/sugi/ya_kansuji?branch=master)
4
+ [<img src="https://github.com/sugi/ya_kansuji/actions/workflows/ruby.yml/badge.svg" alt="Build Status" />](https://github.com/sugi/ya_kansuji/actions/workflows/ruby.yml)
5
+ [<img src="https://coveralls.io/repos/github/sugi/ya_kansuji/badge.svg?branch=master" alt="Coverage Status" />](https://coveralls.io/github/sugi/ya_kansuji?branch=master)
6
6
  [![Maintainability](https://api.codeclimate.com/v1/badges/007f79c6f7f6e82daa20/maintainability)](https://codeclimate.com/github/sugi/ya_kansuji/maintainability)
7
7
  [![Codacy Badge](https://app.codacy.com/project/badge/Grade/048e7a2f52cb48908c4deba7ce696f08)](https://app.codacy.com/gh/sugi/ya_kansuji/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_grade)
8
8
 
@@ -21,6 +21,7 @@ YaKansuji は ruby 用の日本語の漢数字ライブラリです。 和暦ラ
21
21
  * 位取り (2億0010万 / 千〇〇二)
22
22
  * 大字 (千皕卅肆)
23
23
  * カンマ表現 (12,345 / 5億3,456万 / 二万、五十)
24
+ * 負数 (マイナス五十)
24
25
  * 数値から漢数字, 漢字混じり数値文字列フォーマット
25
26
  * 複数のビルトインフォーマッタ
26
27
  * フォーマッタプラグイン機構
@@ -46,6 +47,7 @@ gem 'ya_kansuji'
46
47
 
47
48
  ```ruby
48
49
  YaKansuji.to_i("一〇二四") # => 1024
50
+ YaKansuji.to_i("マイナス一〇二四") # => -1024
49
51
  ```
50
52
 
51
53
  ### フォーマット (数値 → 漢数字)
@@ -54,8 +56,12 @@ YaKansuji.to_i("一〇二四") # => 1024
54
56
 
55
57
  ```ruby
56
58
  YaKansuji.to_kan(12345) # => 一万二千三百四十五
59
+ YaKansuji.to_kan(-12345) # => マイナス一万二千三百四十五
57
60
  ```
58
61
 
62
+ 対応する値域は `-(10**72 - 1)` から `10**72 - 1` です。
63
+ 数値として整数化した値がこの範囲を超える場合、`RangeError` が発生します。
64
+
59
65
  このさい、2つめの引数にシンボルを渡すことにより、フォーマッタを切り替えることができます。
60
66
 
61
67
  ```ruby
@@ -78,6 +84,7 @@ YaKansuji.to_kan(12340005, :judic_h) # => "1234万0005"
78
84
 
79
85
  `YaKansuji#register_formatter` を使うと独自のフォーマッタを登録できます。
80
86
  `#call` を持つ任意のオブジェクトもしくはブロックを渡します。第1引数に数字、第2引数にオプションが渡ります。
87
+ 負数を渡した場合も、フォーマッタには絶対値 (非負整数) が渡され、符号 (「マイナス」) は `to_kan` 側で前置されます。
81
88
 
82
89
  ```ruby
83
90
  YaKansuji.register_formatter :hoge, -> (num, opts = {}) {
@@ -109,6 +116,7 @@ using YaKansuji::CoreRefine
109
116
 
110
117
  puts "二万".to_i # => 20000
111
118
  puts 20000.to_kan #= > "二万"
119
+ puts 20000.to_kan(:gov) # => "2万"
112
120
  ```
113
121
 
114
122
  ちなみに、 `to_i` に base が渡された場合は、漢数字が解釈されずにビルトインクラスのものが呼ばれます。
@@ -117,6 +125,7 @@ puts 20000.to_kan #= > "二万"
117
125
  using YaKansuji::CoreRefine
118
126
 
119
127
  puts "12万".to_i # => 120000
128
+ puts "12万".to_i(:kansuji) # => 120000
120
129
  puts "12万".to_i(16) # => 18
121
130
  ```
122
131
 
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ya_kansuji'
4
+
5
+ # Core extension by kansuji
6
+ class Integer
7
+ def to_kan(formatter = :simple, options = {})
8
+ YaKansuji.to_kan self, formatter, options
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'ya_kansuji'
4
+
5
+ # Core extension by kansuji
6
+ class String
7
+ alias _to_i_ya_kansuji_orig to_i unless defined?(_to_i_ya_kansuji_orig)
8
+
9
+ def to_i(base = nil)
10
+ if base.nil? || base == :kan || base == :kansuji
11
+ YaKansuji.to_i self
12
+ else
13
+ _to_i_ya_kansuji_orig(base)
14
+ end
15
+ end
16
+ end
@@ -1,23 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ya_kansuji'
4
-
5
- # Core extension by kansuji
6
- class String
7
- alias _to_i_ya_kansuji_orig to_i unless defined?(_to_i_ya_kansuji_orig)
8
-
9
- def to_i(base = nil)
10
- if base.nil? || base == :kan
11
- YaKansuji.to_i self
12
- else
13
- _to_i_ya_kansuji_orig(base)
14
- end
15
- end
16
- end
17
-
18
- # Core extension by kansuji
19
- class Integer
20
- def to_kan
21
- YaKansuji.to_kan self
22
- end
23
- end
3
+ require 'ya_kansuji/core_ext/string'
4
+ require 'ya_kansuji/core_ext/integer'
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'ya_kansuji'
4
-
5
3
  module YaKansuji
6
4
  # String and Integer refinements with YaKansuji
7
5
  module CoreRefine
@@ -16,9 +14,11 @@ module YaKansuji
16
14
  end
17
15
 
18
16
  refine Integer do
19
- def to_kan(formatter = :simple)
20
- YaKansuji.to_kan self, formatter
17
+ def to_kan(formatter = :simple, options = {})
18
+ YaKansuji.to_kan self, formatter, options
21
19
  end
22
20
  end
23
21
  end
24
22
  end
23
+
24
+ require 'ya_kansuji' unless YaKansuji.respond_to?(:to_i) && YaKansuji.respond_to?(:to_kan)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module YaKansuji
2
4
  module Formatter
3
5
  # Formatter for Japan government-style kansuji
@@ -8,10 +10,12 @@ module YaKansuji
8
10
  return '0' if num.zero?
9
11
 
10
12
  parts = []
11
- (UNIT_EXP4.reverse + ['']).each_with_index do |unit4, ridx4|
12
- i4 = (num / (10_000**(UNIT_EXP4.size - ridx4))).to_i % 10_000
13
+ chunks = Formatter.split_by_unit4(num)
14
+ (chunks.size - 1).downto(0) do |idx4|
15
+ i4 = chunks[idx4]
13
16
  next if i4.zero?
14
17
 
18
+ unit4 = Formatter::UNIT4_UNITS[idx4]
15
19
  if i4 == 1
16
20
  parts << "1#{unit4}"
17
21
  next
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Simple kansuji formatter
2
4
  module YaKansuji
3
5
  module Formatter
@@ -8,12 +10,14 @@ module YaKansuji
8
10
  def call(num, _options = {})
9
11
  return '0' if num.zero?
10
12
 
11
- ret = ''
13
+ ret = +''
12
14
  head = true
13
- (UNIT_EXP4.reverse + ['']).each_with_index do |unit4, ridx4|
14
- i4 = (num / (10_000**(UNIT_EXP4.size - ridx4))).to_i % 10_000
15
+ chunks = Formatter.split_by_unit4(num)
16
+ (chunks.size - 1).downto(0) do |idx4|
17
+ i4 = chunks[idx4]
15
18
  next if i4.zero?
16
19
 
20
+ unit4 = Formatter::UNIT4_UNITS[idx4]
17
21
  if head
18
22
  ret << (i4.to_s + unit4)
19
23
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Simple kansuji formatter
2
4
  module YaKansuji
3
5
  module Formatter
@@ -8,12 +10,14 @@ module YaKansuji
8
10
  def call(num, _options = {})
9
11
  return '〇' if num.zero?
10
12
 
11
- ret = ''
13
+ ret = +''
12
14
  head = true
13
- (UNIT_EXP4.reverse + ['']).each_with_index do |unit4, ridx4|
14
- i4 = (num / (10_000**(UNIT_EXP4.size - ridx4))).to_i % 10_000
15
+ chunks = Formatter.split_by_unit4(num)
16
+ (chunks.size - 1).downto(0) do |idx4|
17
+ i4 = chunks[idx4]
15
18
  next if i4.zero?
16
19
 
20
+ unit4 = Formatter::UNIT4_UNITS[idx4]
17
21
  if head
18
22
  ret << (i4.to_s + unit4)
19
23
  else
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Simple kansuji formatter
2
4
  module YaKansuji
3
5
  module Formatter
@@ -8,11 +10,13 @@ module YaKansuji
8
10
  def call(num, _options = {})
9
11
  return '0' if num.zero?
10
12
 
11
- ret = ''
12
- (UNIT_EXP4.reverse + ['']).each_with_index do |unit4, ridx4|
13
- i4 = (num / (10_000**(UNIT_EXP4.size - ridx4))).to_i % 10_000
13
+ ret = +''
14
+ chunks = Formatter.split_by_unit4(num)
15
+ (chunks.size - 1).downto(0) do |idx4|
16
+ i4 = chunks[idx4]
14
17
  next if i4.zero?
15
18
 
19
+ unit4 = Formatter::UNIT4_UNITS[idx4]
16
20
  if i4 == 1
17
21
  ret << "1#{unit4}"
18
22
  next
@@ -4,33 +4,43 @@ module YaKansuji
4
4
  module Formatter
5
5
  # Simple kansuji formatter
6
6
  module Simple
7
+ DIGITS = %w(一 二 三 四 五 六 七 八 九).freeze
8
+ UNITS = ([''] + UNIT_EXP3).freeze
9
+
7
10
  module_function
8
11
 
9
12
  def call(num, _options = {})
10
13
  return '零' if num.zero?
11
14
 
12
- ret = ''
13
- (UNIT_EXP4.reverse + ['']).each_with_index do |unit4, ridx4|
14
- i4 = (num / (10_000**(UNIT_EXP4.size - ridx4))).to_i % 10_000
15
+ ret = nil
16
+ chunks = Formatter.split_by_unit4(num)
17
+ (chunks.size - 1).downto(0) do |idx4|
18
+ i4 = chunks[idx4]
15
19
  next if i4.zero?
16
20
 
21
+ ret ||= +''
22
+ unit4 = Formatter::UNIT4_UNITS[idx4]
17
23
  if i4 == 1
18
- ret += "一#{unit4}"
24
+ ret << '一' << unit4
19
25
  next
20
26
  end
21
- (UNIT_EXP3.reverse + ['']).each_with_index do |unit3, ridx3|
22
- i3 = (i4 / (10**(UNIT_EXP3.size - ridx3))).to_i % 10
27
+
28
+ divisor = 1000
29
+ 3.downto(0) do |idx3|
30
+ i3 = (i4 / divisor) % 10
31
+ divisor /= 10
23
32
  next if i3.zero?
24
33
 
25
- if i3 == 1 && unit3 != ''
26
- ret += unit3
34
+ unit3 = UNITS[idx3]
35
+ if i3 == 1 && !unit3.empty?
36
+ ret << unit3
27
37
  else
28
- ret += i3.to_s.tr('123456789', '一二三四五六七八九') + unit3
38
+ ret << DIGITS[i3 - 1] << unit3
29
39
  end
30
40
  end
31
- ret += unit4
41
+ ret << unit4
32
42
  end
33
- ret
43
+ ret || ''
34
44
  end
35
45
  YaKansuji.register_formatter :simple, self
36
46
  end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module YaKansuji
4
+ # Shared helpers for built-in formatters.
5
+ module Formatter
6
+ UNIT4_BASE = 10_000
7
+ UNIT4_CHUNK_LIMIT = UNIT_EXP4.size + 1
8
+ UNIT4_UNITS = ([''] + UNIT_EXP4).freeze
9
+
10
+ module_function
11
+
12
+ def split_by_unit4(num)
13
+ chunks = []
14
+ UNIT4_CHUNK_LIMIT.times do
15
+ num, chunk = num.divmod(UNIT4_BASE)
16
+ chunks << chunk
17
+ break if num.zero?
18
+ end
19
+ chunks
20
+ end
21
+ end
22
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module YaKansuji
2
- VERSION = '1.0.0'.freeze
4
+ VERSION = '1.2.0'
3
5
  end
data/lib/ya_kansuji.rb CHANGED
@@ -1,25 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'ya_kansuji/version'
4
- require 'ya_kansuji/core_refine'
5
4
 
6
5
  # Yet another Kansuji library for ruby.
7
6
  module YaKansuji
8
7
  UNIT_EXP3 = %w(十 百 千).freeze
9
8
  UNIT_EXP4 = %w(万 億 兆 京 垓 𥝱 穣 溝 澗 正 載 極 恒河沙 阿僧祇 那由他 不可思議 無量大数).freeze
9
+ MAX_VALUE = (10_000**(UNIT_EXP4.size + 1)) - 1
10
10
  NUM_ALT_CHARS = '〇一二三四五六七八九0123456789零壱壹弌弐貳貮参參弎肆伍陸漆質柒捌玖拾什陌佰阡仟萬秭'
11
11
  NUM_NORMALIZED_CHARS = '01234567890123456789011122233345677789十十百百千千万𥝱'
12
12
  # rubocop:disable Lint/DuplicateRegexpCharacterClassElement
13
13
  REGEXP_PART = %r{
14
14
  [
15
15
  #{(NUM_ALT_CHARS + NUM_NORMALIZED_CHARS).chars.uniq.join}
16
- #{(UNIT_EXP3 + UNIT_EXP4).find_all { |u| u.length == 1 }}
16
+ #{(UNIT_EXP3 + UNIT_EXP4).find_all { |u| u.length == 1 }.join}
17
17
  卄廿卅丗卌皕
18
18
  ] |
19
19
  #{UNIT_EXP4.find_all { |u| u.length > 1 }.join('|')}
20
20
  }x.freeze
21
21
  # rubocop:enable Lint/DuplicateRegexpCharacterClassElement
22
- REGEXP = /(?:#{REGEXP_PART})+/.freeze
22
+ REGEXP = /(?:マイナス)?(?:#{REGEXP_PART})+/.freeze
23
23
  @@formatters = {}
24
24
 
25
25
  module_function
@@ -77,7 +77,8 @@ module YaKansuji
77
77
  ret3 += curnum
78
78
  curnum = nil
79
79
  end
80
- ret4 + ret3
80
+ ret = ret4 + ret3
81
+ matched[0].start_with?('マイナス') ? -ret : ret
81
82
  end
82
83
 
83
84
  def register_formatter(sym, proc = nil, &block)
@@ -100,6 +101,12 @@ module YaKansuji
100
101
 
101
102
  def to_kan(num, formatter = :simple, options = {})
102
103
  num.respond_to?(:_to_i_ya_kansuji_orig) ? num = num._to_i_ya_kansuji_orig : num = num.to_i
104
+ if num.abs > MAX_VALUE
105
+ raise RangeError, "Value must be between #{-MAX_VALUE} and #{MAX_VALUE}"
106
+ end
107
+
108
+ return "マイナス#{to_kan(-num, formatter, options)}" if num.negative?
109
+
103
110
  if formatter.respond_to? :call
104
111
  formatter.call num, options
105
112
  elsif @@formatters[formatter]
@@ -110,8 +117,10 @@ module YaKansuji
110
117
  end
111
118
  end
112
119
 
120
+ require 'ya_kansuji/formatter'
113
121
  require 'ya_kansuji/formatter/simple'
114
122
  require 'ya_kansuji/formatter/gov'
115
123
  require 'ya_kansuji/formatter/lawyer'
116
124
  require 'ya_kansuji/formatter/judic_v'
117
125
  require 'ya_kansuji/formatter/judic_h'
126
+ require 'ya_kansuji/core_refine' unless defined?(YaKansuji::CoreRefine)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ya_kansuji
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tatsuki Sugiura
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-09 00:00:00.000000000 Z
11
+ date: 2026-07-13 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Pure ruby library of Japanese kansuji.
@@ -24,7 +24,10 @@ files:
24
24
  - README.md
25
25
  - lib/ya_kansuji.rb
26
26
  - lib/ya_kansuji/core_ext.rb
27
+ - lib/ya_kansuji/core_ext/integer.rb
28
+ - lib/ya_kansuji/core_ext/string.rb
27
29
  - lib/ya_kansuji/core_refine.rb
30
+ - lib/ya_kansuji/formatter.rb
28
31
  - lib/ya_kansuji/formatter/gov.rb
29
32
  - lib/ya_kansuji/formatter/judic_h.rb
30
33
  - lib/ya_kansuji/formatter/judic_v.rb
@@ -44,7 +47,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
44
47
  requirements:
45
48
  - - ">="
46
49
  - !ruby/object:Gem::Version
47
- version: 2.0.0
50
+ version: 2.3.0
48
51
  required_rubygems_version: !ruby/object:Gem::Requirement
49
52
  requirements:
50
53
  - - ">="