rangu 0.3.0 → 1.0.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: 8a9c2af7fa78a5071c99ea0e30ad4f903a2c963912aa0f7f0d2c3a5564e64c60
4
- data.tar.gz: 453dba103348dfd8aabb4be616cebebed250d81e9db7955ba49cf40d7d96c76c
3
+ metadata.gz: '09dd594057ccb584243d53f15ad5506e4dcb0ca155423f97c5e1b08f56981596'
4
+ data.tar.gz: e1591c9cb6a351994aa7eea1f3c22b06e7325871081eaaecd21c001ebe8eefa3
5
5
  SHA512:
6
- metadata.gz: df2235642f28da6a01314c65438e34e25deb2659386819ace855ac4e97f3150df0328f4fbed06bb1b18035e3036c9888b23f1af99cdb50664b1a5c8c82eda0b7
7
- data.tar.gz: d32dc0e5d8885b189201083fb479e2a7aa6b7cafcd60eff540c104051137336dc16cdf2bd2306e5ec1b8ff53e69caa06ddbea18645a7a28284ee10edc67fc6dc
6
+ metadata.gz: a9052b246105a9455ddb18ecd6a376667e3bbd5897ffade8ca37ffa72b13826c7b5e4df9e010502cab98d051daa05e144e84862907544ca847fef31ad89f34d7
7
+ data.tar.gz: 8b1400f450b06cfe71b5118b07de1b78f19051acc193c77b6a23aa785adebfdc2bee900e2de96634e90ba4846cf533f013d34b23745a1e02701ea70e08318fc6
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ # Changelog
2
+
3
+ All notable changes to Rangu are documented in this file.
4
+
5
+ ## 1.0.0 - 2026-07-19
6
+
7
+ ### Added
8
+
9
+ - Added the `rangu` command-line interface with text, file, and standard-input modes.
10
+ - Added GitHub Actions coverage for Ruby 3.3, 3.4, and 4.0.
11
+ - Added grouped monthly Dependabot updates for Bundler and GitHub Actions.
12
+
13
+ ### Changed
14
+
15
+ - Updated Rangu's spacing rules to match pangu.py 4.0.6.1 and added corresponding Minitest coverage.
16
+ - Made `Rangu.spacing` and `Rangu.spacing_text` immutable text-only APIs.
17
+ - Made `Rangu.spacing_file` the only file-reading API and preserved file contents.
18
+ - Replaced the RSpec suite with Minitest 6 and modernized the development toolchain to Bundler 4, Rake 13, and
19
+ RuboCop 1.88.
20
+ - Raised the minimum supported Ruby version to 3.3.
21
+ - Enabled MFA-required RubyGems releases and restricted the gem push host to RubyGems.org.
22
+
23
+ ### Breaking changes from 0.3.0
24
+
25
+ - CJK-adjacent ASCII punctuation is now normalized to full-width punctuation, and middle-dot variants are normalized
26
+ to `・`.
27
+ - `Rangu.spacing(existing_path)` no longer reads the file at that path; call `Rangu.spacing_file(path)` instead.
28
+ - Input strings are no longer modified in place.
29
+ - Ruby versions older than 3.3 are no longer supported.
data/README.md CHANGED
@@ -1,47 +1,107 @@
1
1
  # Rangu
2
2
 
3
- [![Build Status](https://travis-ci.org/bugtender/rangu.svg?branch=master)](https://travis-ci.org/bugtender/rangu)
3
+ [![CI](https://github.com/bugtender/rangu/actions/workflows/ci.yml/badge.svg)](https://github.com/bugtender/rangu/actions/workflows/ci.yml)
4
+ [![Gem Version](https://img.shields.io/gem/v/rangu.svg)](https://rubygems.org/gems/rangu)
4
5
 
5
- Paranoid text spacing for good readability, to automatically insert whitespace between CJK (Chinese, Japanese, Korean) and half-width characters (alphabetical letters, numerical digits and symbols).
6
+ Rangu inserts readable spacing between CJK characters and half-width letters, numbers, and symbols. It provides an
7
+ immutable Ruby API and a command-line interface.
6
8
 
7
9
  This is the Ruby copy version from [Vinta](https://github.com/vinta)'s [pangu.py](https://github.com/vinta/pangu.py).
8
10
 
11
+ Rangu requires Ruby 3.3 or newer and has no runtime dependencies.
12
+
9
13
  ## Installation
10
14
 
11
- Add this line to your application's Gemfile:
15
+ Add Rangu to your bundle:
12
16
 
13
- ```ruby
14
- gem 'rangu'
17
+ ```shell
18
+ bundle add rangu
15
19
  ```
16
20
 
17
- And then execute:
18
-
19
- $ bundle
21
+ Or install the gem directly:
20
22
 
21
- Or install it yourself as:
22
-
23
- $ gem install rangu
23
+ ```shell
24
+ gem install rangu
25
+ ```
24
26
 
25
- ## Usage
27
+ ## Ruby API
26
28
 
27
29
  ```ruby
28
30
  require "rangu"
29
31
 
30
32
  Rangu.spacing("當你凝視著bug,bug也凝視著你")
31
- => "當你凝視著 bug,bug 也凝視著你"
33
+ # => "當你凝視著 bug,bug 也凝視著你"
34
+
35
+ Rangu.spacing_text("Ruby中文")
36
+ # => "Ruby 中文"
32
37
 
33
- Rangu.spacing("path/to/file.txt")
34
- => "與 PM 戰鬥的人,應當小心自己不要成為 PM"
38
+ Rangu.spacing_file("notes.txt")
39
+ # => the spaced UTF-8 contents of notes.txt
35
40
  ```
36
41
 
37
- ## Contributing
42
+ `Rangu.spacing` is the primary text API, and `Rangu.spacing_text` is an alias. Both always treat their argument as
43
+ text, leave the original string unchanged, accept frozen strings, and return a distinct string. `Rangu.spacing_file`
44
+ is the only file-reading API; it reads UTF-8 and never writes the file. File and encoding errors are passed through to
45
+ the caller.
38
46
 
39
- Bug reports and pull requests are welcome on GitHub at https://github.com/bugtender/rangu. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
47
+ When CJK text is present, Rangu also normalizes adjacent ASCII punctuation such as `~!;:,.?` to full-width forms and
48
+ normalizes `·`, `•`, and `‧` to `・`. Leading and trailing whitespace is removed from CJK results.
40
49
 
41
- ## License
50
+ ## Command-line interface
51
+
52
+ ```shell
53
+ rangu "當你凝視著bug,bug也凝視著你"
54
+ rangu --text "Ruby中文"
55
+ rangu --file notes.txt
56
+ printf "中文Ruby" | rangu
57
+ rangu --version
58
+ rangu --help
59
+ ```
60
+
61
+ Text, file, and positional modes are mutually exclusive. Explicit input takes priority over standard input, and stdin
62
+ is read only when no explicit input is provided. Results go to stdout; usage, option, and file errors go to stderr with
63
+ a non-zero exit status. The CLI never edits a file in place.
64
+
65
+ ## Upgrading from 0.3.0
66
+
67
+ Rangu 1.0.0 has four intentional breaking changes:
42
68
 
43
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
69
+ - ASCII punctuation adjacent to CJK text is now normalized to full-width punctuation, including middle-dot conversion.
70
+ - `Rangu.spacing(existing_path)` no longer detects or reads files. Use `Rangu.spacing_file(path)` explicitly.
71
+ - Spacing APIs no longer modify the input string and always return a separate string.
72
+ - The minimum supported Ruby version is now 3.3.
44
73
 
45
- ## Code of Conduct
74
+ ## Development
75
+
76
+ Install Ruby 3.3 or newer and Bundler 4, then run:
77
+
78
+ ```shell
79
+ bin/setup
80
+ bundle exec rake
81
+ ```
82
+
83
+ The default Rake task runs the Minitest suite and RuboCop. Individual checks and the package build are also available:
84
+
85
+ ```shell
86
+ bundle exec rake test
87
+ bundle exec rubocop
88
+ bundle exec rake build
89
+ ```
90
+
91
+ Bug reports and pull requests are welcome in the [issue tracker](https://github.com/bugtender/rangu/issues). By
92
+ participating, you agree to follow the [Code of Conduct](CODE_OF_CONDUCT.md).
93
+
94
+ ## Manual release
95
+
96
+ 1. Update `Rangu::VERSION` and `CHANGELOG.md` in a release commit.
97
+ 2. Run `bundle exec rake` and `bundle exec rake build`.
98
+ 3. Inspect `pkg/rangu-VERSION.gem`, then install it into a temporary `GEM_HOME` for a smoke test.
99
+ 4. Create and push an annotated `vVERSION` tag.
100
+ 5. Publish with `gem push --host https://rubygems.org --otp OTP pkg/rangu-VERSION.gem`.
101
+
102
+ RubyGems publishing is restricted to `https://rubygems.org`, and all owners must have multi-factor authentication
103
+ enabled. Releases are manual; CI does not publish gems.
104
+
105
+ ## License
46
106
 
47
- Everyone interacting in the Rangu project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/bugtender/rangu/blob/master/CODE_OF_CONDUCT.md).
107
+ Rangu is available under the [MIT License](LICENSE).
data/exe/rangu ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
5
+ require "rangu/cli"
6
+
7
+ exit Rangu::CLI.run(ARGV)
data/lib/rangu/cli.rb ADDED
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+ require "rangu"
5
+
6
+ module Rangu
7
+ class CLI
8
+ SUCCESS = 0
9
+ ERROR = 1
10
+ USAGE_ERROR = 2
11
+
12
+ def self.run(arguments, stdin: $stdin, stdout: $stdout, stderr: $stderr)
13
+ new(stdin:, stdout:, stderr:).run(arguments)
14
+ end
15
+
16
+ def initialize(stdin:, stdout:, stderr:)
17
+ @stdin = stdin
18
+ @stdout = stdout
19
+ @stderr = stderr
20
+ end
21
+
22
+ def run(arguments)
23
+ options = {}
24
+ parser = option_parser(options)
25
+ positional = parser.parse(arguments.dup)
26
+ return print_help(parser) if options[:help]
27
+ return print_version if options[:version]
28
+
29
+ source = resolve_source(options, positional, parser)
30
+ source ? execute(source) : USAGE_ERROR
31
+ rescue OptionParser::ParseError => e
32
+ report_usage(e.message, parser)
33
+ USAGE_ERROR
34
+ end
35
+
36
+ private
37
+
38
+ def option_parser(options)
39
+ OptionParser.new do |parser|
40
+ parser.banner = "Usage: rangu [TEXT] | --text TEXT | --file PATH"
41
+ parser.summary_width = 24
42
+
43
+ parser.on("-t", "--text TEXT", "space the provided text") { |text| options[:text] = text }
44
+ parser.on("-f", "--file PATH", "read and space a UTF-8 file") { |path| options[:file] = path }
45
+ parser.on("-v", "--version", "print the version") { options[:version] = true }
46
+ parser.on("-h", "--help", "show this help") { options[:help] = true }
47
+
48
+ parser.separator("")
49
+ parser.separator("With no explicit input, rangu reads standard input. Files are never modified.")
50
+ end
51
+ end
52
+
53
+ def resolve_source(options, positional, parser)
54
+ return report_usage("expected at most one TEXT argument", parser) if positional.length > 1
55
+
56
+ sources = explicit_sources(options, positional)
57
+ return report_usage("text and file inputs are mutually exclusive", parser) if sources.length > 1
58
+ return sources.first unless sources.empty?
59
+
60
+ standard_input_source(parser)
61
+ end
62
+
63
+ def explicit_sources(options, positional)
64
+ sources = []
65
+ sources << [:text, options[:text]] if options.key?(:text)
66
+ sources << [:file, options[:file]] if options.key?(:file)
67
+ sources << [:text, positional.first] unless positional.empty?
68
+ sources
69
+ end
70
+
71
+ def standard_input_source(parser)
72
+ input = @stdin.read
73
+ return report_usage("no input provided", parser) if input.empty?
74
+
75
+ [:text, input]
76
+ end
77
+
78
+ def execute(source)
79
+ mode, value = source
80
+ result = mode == :file ? Rangu.spacing_file(value) : Rangu.spacing(value)
81
+ @stdout.puts(result)
82
+ SUCCESS
83
+ rescue SystemCallError, IOError, EncodingError, ArgumentError => e
84
+ @stderr.puts("rangu: #{e.message}")
85
+ ERROR
86
+ end
87
+
88
+ def print_help(parser)
89
+ @stdout.puts(parser)
90
+ SUCCESS
91
+ end
92
+
93
+ def print_version
94
+ @stdout.puts(VERSION)
95
+ SUCCESS
96
+ end
97
+
98
+ def report_usage(message, parser)
99
+ @stderr.puts("rangu: #{message}")
100
+ @stderr.puts(parser.banner)
101
+ nil
102
+ end
103
+ end
104
+ end
data/lib/rangu/version.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Rangu
2
- VERSION = "0.3.0"
4
+ VERSION = "1.0.0"
3
5
  end
data/lib/rangu.rb CHANGED
@@ -1,68 +1,138 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "rangu/version"
2
4
 
3
5
  module Rangu
4
- CJK_QUOTE = /([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])(["\'])/i
5
- QUOTE_CJK = /(["\'])([\u3040-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/i
6
+ # Spacing rules adapted from pangu.py 4.0.6.1.
7
+ CJK = "\\u2e80-\\u2eff\\u2f00-\\u2fdf\\u3040-\\u309f\\u30a0-\\u30fa\\u30fc-\\u30ff" \
8
+ "\\u3100-\\u312f\\u3200-\\u32ff\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff"
6
9
 
7
- FIX_QUOTE = /(["\'\(\[\{<\u201c]+)(\s*)(.+?)(\s*)(["\'\)\]\}>\u201d]+)/i
8
- FIX_SINGLE_QUOTE = /([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])( )(\')([A-Za-zΑ-Ωα-ω])/i
10
+ ANY_CJK = /[#{CJK}]/
9
11
 
10
- CJK_HASH = /([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])(#(\S+))/i
11
- HASH_CJK = /((\S+)#)([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/i
12
+ CONVERT_TO_FULLWIDTH_CJK_SYMBOLS_CJK = /([#{CJK}])( *(?::+|\.) *)([#{CJK}])/
13
+ CONVERT_TO_FULLWIDTH_CJK_SYMBOLS = /([#{CJK}]) *([~!;,?]+) */
14
+ DOTS_CJK = /(\.{2,}|…)([#{CJK}])/
15
+ FIX_CJK_COLON_ANS = /([#{CJK}]):([A-Z0-9()])/
12
16
 
13
- CJK_OPERATOR_ANS = /([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([\+\-\*\/=&\\|<>])([A-Za-zΑ-Ωα-ω0-9])/i
14
- ANS_OPERATOR_CJK = /([A-Za-zΑ-Ωα-ω0-9])([\+\-\*\/=&\\|<>])([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/i
17
+ CJK_QUOTE = /([#{CJK}])([`"״])/
18
+ QUOTE_CJK = /([`"״])([#{CJK}])/
19
+ FIX_QUOTE_ANY_QUOTE = /([`"״]+)(\s*)(.+?)(\s*)([`"״]+)/
15
20
 
16
- CJK_BRACKET_CJK = /([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([\(\[\{<\u201c]+(.*?)[\)\]\}>\u201d]+)([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/i
17
- CJK_BRACKET = /([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([\(\[\{<\u201c>])/i
18
- BRACKET_CJK = /([\)\]\}>\u201d<])([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/i
19
- FIX_BRACKET = /([\(\[\{<\u201c]+)(\s*)(.+?)(\s*)([\)\]\}>\u201d]+)/i
21
+ CJK_SINGLE_QUOTE_BUT_POSSESSIVE = /([#{CJK}])('[^s])/
22
+ SINGLE_QUOTE_CJK = /(')([#{CJK}])/
23
+ FIX_POSSESSIVE_SINGLE_QUOTE = /([#{CJK}A-Za-z0-9])( )('s)/
20
24
 
21
- FIX_SYMBOL = /([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([~!;:,\.\?\u2026])([A-Za-zΑ-Ωα-ω0-9])/i
25
+ HASH_ANS_CJK_HASH = /([#{CJK}])(#)([#{CJK}]+)(#)([#{CJK}])/
26
+ CJK_HASH = /([#{CJK}])(#([^ ]))/
27
+ HASH_CJK = /(([^ ])#)([#{CJK}])/
22
28
 
23
- CJK_ANS = /([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])([A-Za-zΑ-Ωα-ω0-9`\$%\^&\*\-=\+\\\|\/@\u00a1-\u00ff\u2022\u2027\u2150-\u218f])/i
24
- ANS_CJK = /([A-Za-zΑ-Ωα-ω0-9`~\$%\^&\*\-=\+\\\|\/!;:,\.\?\u00a1-\u00ff\u2022\u2026\u2027\u2150-\u218f])([\u2e80-\u2eff\u2f00-\u2fdf\u3040-\u309f\u30a0-\u30ff\u3100-\u312f\u3200-\u32ff\u3400-\u4dbf\u4e00-\u9fff\uf900-\ufaff])/i
29
+ CJK_OPERATOR_ANS = %r{([#{CJK}])([+\-*/=&|<>])([A-Za-z0-9])}
30
+ ANS_OPERATOR_CJK = %r{([A-Za-z0-9])([+\-*/=&|<>])([#{CJK}])}
25
31
 
26
- def self.spacing_text(text)
27
- text.gsub!(CJK_QUOTE, "\\1 \\2")
28
- text.gsub!(QUOTE_CJK, "\\1 \\2")
32
+ FIX_SLASH_AS = %r{(/) ([a-z\-_./]+)}
33
+ FIX_SLASH_AS_SLASH = %r{([/.])([A-Za-z\-_./]+) (/)}
29
34
 
30
- text.gsub!(FIX_QUOTE, "\\1\\3\\5")
31
- text.gsub!(FIX_SINGLE_QUOTE, "\\1\\3\\4")
35
+ CJK_LEFT_BRACKET = /([#{CJK}])([\(\[\{<>“])/
36
+ RIGHT_BRACKET_CJK = /([)\]}<>”])([#{CJK}])/
37
+ FIX_LEFT_BRACKET_ANY_RIGHT_BRACKET = /([\(\[\{<“]+)(\s*)(.+?)(\s*)([)\]}>”]+)/
38
+ ANS_CJK_LEFT_BRACKET_ANY_RIGHT_BRACKET = /([A-Za-z0-9#{CJK}]) *(“)([A-Za-z0-9#{CJK}\-_ ]+)(”)/
39
+ LEFT_BRACKET_ANY_RIGHT_BRACKET_ANS_CJK = /(“)([A-Za-z0-9#{CJK}\-_ ]+)(”) *([A-Za-z0-9#{CJK}])/
32
40
 
33
- text.gsub!(CJK_HASH, "\\1 \\2")
34
- text.gsub!(HASH_CJK, "\\1 \\3")
41
+ AN_LEFT_BRACKET = /([A-Za-z0-9])([\(\[\{])/
42
+ RIGHT_BRACKET_AN = /([)\]}])([A-Za-z0-9])/
35
43
 
36
- text.gsub!(CJK_OPERATOR_ANS, "\\1 \\2 \\3")
37
- text.gsub!(ANS_OPERATOR_CJK, "\\1 \\2 \\3")
44
+ CJK_ANS = %r{([#{CJK}])([A-Za-z\u0370-\u03ff0-9@$%\^&*\-+\\=|/\u00a1-\u00ff\u2150-\u218f\u2700—\u27bf])}
45
+ ANS_CJK = %r{([A-Za-z\u0370-\u03ff0-9~!$%\^&*\-+\\=|;:,./?\u00a1-\u00ff\u2150-\u218f\u2700—\u27bf])([#{CJK}])}
38
46
 
39
- old_text = text
40
- text.gsub!(CJK_BRACKET_CJK, "\\1 \\2 \\4")
41
- if old_text == text
42
- text.gsub!(CJK_BRACKET, "\\1 \\2")
43
- text.gsub!(BRACKET_CJK, "\\1 \\2")
44
- end
45
- text.gsub!(FIX_BRACKET, "\\1\\3\\5")
47
+ S_A = /(%)([A-Za-z])/
48
+ MIDDLE_DOT = /( *)([·•‧])( *)/
46
49
 
47
- text.gsub!(FIX_SYMBOL, "\\1\\2 \\3")
50
+ TILDES = /~+/
51
+ EXCLAMATION_MARKS = /!+/
52
+ SEMICOLONS = /;+/
53
+ COLONS = /:+/
54
+ COMMAS = /,+/
55
+ PERIODS = /\.+/
56
+ QUESTION_MARKS = /\?+/
48
57
 
49
- text.gsub!(CJK_ANS, "\\1 \\2")
50
- text.gsub!(ANS_CJK, "\\1 \\2")
51
- text
58
+ def self.spacing(text)
59
+ return text.dup if text.length <= 1 || !ANY_CJK.match?(text)
60
+
61
+ new_text = normalize_fullwidth_symbols_between_cjk(text.dup)
62
+ new_text = normalize_fullwidth_symbols_after_cjk(new_text)
63
+
64
+ new_text = new_text.gsub(DOTS_CJK, "\\1 \\2")
65
+ new_text = new_text.gsub(FIX_CJK_COLON_ANS, "\\1:\\2")
66
+
67
+ new_text = new_text.gsub(CJK_QUOTE, "\\1 \\2")
68
+ new_text = new_text.gsub(QUOTE_CJK, "\\1 \\2")
69
+ new_text = new_text.gsub(FIX_QUOTE_ANY_QUOTE, "\\1\\3\\5")
70
+
71
+ new_text = new_text.gsub(CJK_SINGLE_QUOTE_BUT_POSSESSIVE, "\\1 \\2")
72
+ new_text = new_text.gsub(SINGLE_QUOTE_CJK, "\\1 \\2")
73
+ new_text = new_text.gsub(FIX_POSSESSIVE_SINGLE_QUOTE, "\\1's")
74
+
75
+ new_text = new_text.gsub(HASH_ANS_CJK_HASH, "\\1 \\2\\3\\4 \\5")
76
+ new_text = new_text.gsub(CJK_HASH, "\\1 \\2")
77
+ new_text = new_text.gsub(HASH_CJK, "\\1 \\3")
78
+
79
+ new_text = new_text.gsub(CJK_OPERATOR_ANS, "\\1 \\2 \\3")
80
+ new_text = new_text.gsub(ANS_OPERATOR_CJK, "\\1 \\2 \\3")
81
+
82
+ new_text = new_text.gsub(FIX_SLASH_AS, "\\1\\2")
83
+ new_text = new_text.gsub(FIX_SLASH_AS_SLASH, "\\1\\2\\3")
84
+
85
+ new_text = new_text.gsub(CJK_LEFT_BRACKET, "\\1 \\2")
86
+ new_text = new_text.gsub(RIGHT_BRACKET_CJK, "\\1 \\2")
87
+ new_text = new_text.gsub(FIX_LEFT_BRACKET_ANY_RIGHT_BRACKET, "\\1\\3\\5")
88
+ new_text = new_text.gsub(ANS_CJK_LEFT_BRACKET_ANY_RIGHT_BRACKET, "\\1 \\2\\3\\4")
89
+ new_text = new_text.gsub(LEFT_BRACKET_ANY_RIGHT_BRACKET_ANS_CJK, "\\1\\2\\3 \\4")
90
+
91
+ new_text = new_text.gsub(AN_LEFT_BRACKET, "\\1 \\2")
92
+ new_text = new_text.gsub(RIGHT_BRACKET_AN, "\\1 \\2")
93
+
94
+ new_text = new_text.gsub(CJK_ANS, "\\1 \\2")
95
+ new_text = new_text.gsub(ANS_CJK, "\\1 \\2")
96
+ new_text = new_text.gsub(S_A, "\\1 \\2")
97
+ new_text.gsub(MIDDLE_DOT, "・").strip
98
+ end
99
+
100
+ class << self
101
+ alias spacing_text spacing
52
102
  end
53
103
 
54
104
  def self.spacing_file(path)
55
- file = File.open(path, "r")
56
- data = file.read
57
- file.close
58
- spacing_text(data)
105
+ spacing(File.read(path, encoding: Encoding::UTF_8))
59
106
  end
60
107
 
61
- def self.spacing(text_or_path)
62
- if File.file?(File.absolute_path(text_or_path))
63
- spacing_file(text_or_path)
64
- else
65
- spacing_text(text_or_path)
108
+ def self.normalize_fullwidth_symbols_between_cjk(text)
109
+ while (matched = CONVERT_TO_FULLWIDTH_CJK_SYMBOLS_CJK.match(text))
110
+ text[matched.begin(0)...matched.end(0)] = "#{matched[1]}#{convert_to_fullwidth(matched[2])}#{matched[3]}"
66
111
  end
112
+
113
+ text
114
+ end
115
+ private_class_method :normalize_fullwidth_symbols_between_cjk
116
+
117
+ def self.normalize_fullwidth_symbols_after_cjk(text)
118
+ while (matched = CONVERT_TO_FULLWIDTH_CJK_SYMBOLS.match(text))
119
+ prefix = text[0, matched.begin(0) + 1].strip
120
+ suffix = text[matched.end(0)..].strip
121
+ text = "#{prefix}#{convert_to_fullwidth(matched[2])}#{suffix}"
122
+ end
123
+
124
+ text
125
+ end
126
+ private_class_method :normalize_fullwidth_symbols_after_cjk
127
+
128
+ def self.convert_to_fullwidth(symbols)
129
+ symbols = symbols.gsub(TILDES, "~")
130
+ symbols = symbols.gsub(EXCLAMATION_MARKS, "!")
131
+ symbols = symbols.gsub(SEMICOLONS, ";")
132
+ symbols = symbols.gsub(COLONS, ":")
133
+ symbols = symbols.gsub(COMMAS, ",")
134
+ symbols = symbols.gsub(PERIODS, "。")
135
+ symbols.gsub(QUESTION_MARKS, "?").strip
67
136
  end
137
+ private_class_method :convert_to_fullwidth
68
138
  end
metadata CHANGED
@@ -1,84 +1,39 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rangu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vincent Lin
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2019-01-21 00:00:00.000000000 Z
12
- dependencies:
13
- - !ruby/object:Gem::Dependency
14
- name: bundler
15
- requirement: !ruby/object:Gem::Requirement
16
- requirements:
17
- - - "~>"
18
- - !ruby/object:Gem::Version
19
- version: '2.0'
20
- type: :development
21
- prerelease: false
22
- version_requirements: !ruby/object:Gem::Requirement
23
- requirements:
24
- - - "~>"
25
- - !ruby/object:Gem::Version
26
- version: '2.0'
27
- - !ruby/object:Gem::Dependency
28
- name: rake
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - "~>"
32
- - !ruby/object:Gem::Version
33
- version: '10.0'
34
- type: :development
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - "~>"
39
- - !ruby/object:Gem::Version
40
- version: '10.0'
41
- - !ruby/object:Gem::Dependency
42
- name: rspec
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - "~>"
46
- - !ruby/object:Gem::Version
47
- version: '3.2'
48
- type: :development
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - "~>"
53
- - !ruby/object:Gem::Version
54
- version: '3.2'
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies: []
55
12
  description: Insert a white space between FullWidth and HalfWidth characters.
56
13
  email:
57
14
  - bugtender@gmail.com
58
- executables: []
15
+ executables:
16
+ - rangu
59
17
  extensions: []
60
18
  extra_rdoc_files: []
61
19
  files:
62
- - ".gitignore"
63
- - ".rspec"
64
- - ".rubocop.yml"
65
- - ".travis.yml"
20
+ - CHANGELOG.md
66
21
  - CODE_OF_CONDUCT.md
67
- - Gemfile
68
- - Gemfile.lock
69
22
  - LICENSE
70
23
  - README.md
71
- - Rakefile
72
- - bin/console
73
- - bin/setup
24
+ - exe/rangu
74
25
  - lib/rangu.rb
26
+ - lib/rangu/cli.rb
75
27
  - lib/rangu/version.rb
76
- - rangu.gemspec
77
28
  homepage: https://github.com/bugtender/rangu
78
29
  licenses:
79
30
  - MIT
80
- metadata: {}
81
- post_install_message:
31
+ metadata:
32
+ allowed_push_host: https://rubygems.org
33
+ bug_tracker_uri: https://github.com/bugtender/rangu/issues
34
+ changelog_uri: https://github.com/bugtender/rangu/blob/master/CHANGELOG.md
35
+ rubygems_mfa_required: 'true'
36
+ source_code_uri: https://github.com/bugtender/rangu
82
37
  rdoc_options: []
83
38
  require_paths:
84
39
  - lib
@@ -86,16 +41,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
86
41
  requirements:
87
42
  - - ">="
88
43
  - !ruby/object:Gem::Version
89
- version: '0'
44
+ version: '3.3'
90
45
  required_rubygems_version: !ruby/object:Gem::Requirement
91
46
  requirements:
92
47
  - - ">="
93
48
  - !ruby/object:Gem::Version
94
49
  version: '0'
95
50
  requirements: []
96
- rubyforge_project:
97
- rubygems_version: 2.7.3
98
- signing_key:
51
+ rubygems_version: 3.6.9
99
52
  specification_version: 4
100
53
  summary: Paranoid text spacing in Ruby
101
54
  test_files: []
data/.gitignore DELETED
@@ -1,12 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /_yardoc/
4
- /coverage/
5
- /doc/
6
- /pkg/
7
- /spec/reports/
8
- /tmp/
9
-
10
- # rspec failure tracking
11
- .rspec_status
12
- .vscode/
data/.rspec DELETED
@@ -1,3 +0,0 @@
1
- --format documentation
2
- --color
3
- --require spec_helper