human_number 0.1.1 → 0.1.3
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/.rubocop.yml +4 -0
- data/BUILD.md +246 -0
- data/CHANGELOG.md +1 -1
- data/LICENSE +1 -1
- data/Makefile +142 -0
- data/README.md +12 -12
- data/Rakefile +135 -0
- data/human_number.gemspec +40 -0
- data/lib/human_number/formatters/number.rb +42 -56
- data/lib/human_number/rails/helpers.rb +15 -41
- data/lib/human_number/version.rb +1 -1
- data/lib/human_number.rb +34 -14
- data/scripts/build_and_publish.sh +204 -0
- metadata +11 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a9d2eb9cac5e189aec1c4c4ad86e0c3a248f466bac8a1af782fc53ff918a43a9
|
4
|
+
data.tar.gz: f5e4e6a026ff264ab89583cbef216cdbdf872d967ad7b74859b7cd011c5f7373
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 32ada67f4efa6c18d358a327c18a913b7b18945db2f70f1ca35de0230250f089885e85425cb40547dfeb4aa16a4c0ddd5eb35ea8b65fb5b27919fb2dd5516e8b
|
7
|
+
data.tar.gz: d549da397b45f84dff15e15851ef54d1dfb241462ab041d1c0b384dc9ec5811eb744f0c54c28e7dd10087ceeade9b39346708b28cef5d4d09d0fb9e0594b97d8
|
data/.rubocop.yml
CHANGED
data/BUILD.md
ADDED
@@ -0,0 +1,246 @@
|
|
1
|
+
# Build and Publish Guide
|
2
|
+
|
3
|
+
This project provides three different ways to build and publish the gem, choose the one that best fits your workflow:
|
4
|
+
|
5
|
+
## 🔧 Option 1: Rake Tasks (Recommended for Ruby developers)
|
6
|
+
|
7
|
+
The most Ruby-native approach using rake tasks.
|
8
|
+
|
9
|
+
### Available Tasks
|
10
|
+
```bash
|
11
|
+
# List all gem-related tasks
|
12
|
+
rake -T gem
|
13
|
+
|
14
|
+
# Run validations only
|
15
|
+
rake gem:validate
|
16
|
+
|
17
|
+
# Build gem with full validations
|
18
|
+
rake gem:build
|
19
|
+
|
20
|
+
# Quick build without validations (for testing)
|
21
|
+
rake gem:build_quick
|
22
|
+
|
23
|
+
# Publish to RubyGems with full validations
|
24
|
+
rake gem:publish
|
25
|
+
|
26
|
+
# Show gem information
|
27
|
+
rake gem:info
|
28
|
+
|
29
|
+
# Clean build artifacts
|
30
|
+
rake gem:clean
|
31
|
+
|
32
|
+
# Bump version interactively and commit
|
33
|
+
rake gem:bump
|
34
|
+
|
35
|
+
# Convenient aliases
|
36
|
+
rake build # Same as gem:build
|
37
|
+
rake release # Same as gem:publish
|
38
|
+
```
|
39
|
+
|
40
|
+
### Examples
|
41
|
+
```bash
|
42
|
+
# Full build and publish workflow
|
43
|
+
rake gem:validate # Run tests, linting, git checks
|
44
|
+
rake gem:build # Build the gem
|
45
|
+
rake gem:publish # Publish to RubyGems
|
46
|
+
|
47
|
+
# Or use the all-in-one command
|
48
|
+
rake gem:publish # Does validation + build + publish
|
49
|
+
|
50
|
+
# Quick development testing
|
51
|
+
rake gem:build_quick # Build without validations
|
52
|
+
```
|
53
|
+
|
54
|
+
## 🐚 Option 2: Shell Script (Cross-platform compatible)
|
55
|
+
|
56
|
+
A bash script that works on any Unix-like system.
|
57
|
+
|
58
|
+
### Available Commands
|
59
|
+
```bash
|
60
|
+
# Show help
|
61
|
+
./scripts/build_and_publish.sh help
|
62
|
+
|
63
|
+
# Run validations only
|
64
|
+
./scripts/build_and_publish.sh validate
|
65
|
+
|
66
|
+
# Build gem with full validations
|
67
|
+
./scripts/build_and_publish.sh build
|
68
|
+
|
69
|
+
# Quick build without validations (for testing)
|
70
|
+
./scripts/build_and_publish.sh build-quick
|
71
|
+
|
72
|
+
# Publish to RubyGems with full validations
|
73
|
+
./scripts/build_and_publish.sh publish
|
74
|
+
|
75
|
+
# Show gem information
|
76
|
+
./scripts/build_and_publish.sh info
|
77
|
+
|
78
|
+
# Clean build artifacts
|
79
|
+
./scripts/build_and_publish.sh clean
|
80
|
+
|
81
|
+
# Bump version interactively and commit
|
82
|
+
./scripts/build_and_publish.sh bump
|
83
|
+
```
|
84
|
+
|
85
|
+
### Examples
|
86
|
+
```bash
|
87
|
+
# Full build and publish workflow
|
88
|
+
./scripts/build_and_publish.sh validate # Run all checks
|
89
|
+
./scripts/build_and_publish.sh build # Build the gem
|
90
|
+
./scripts/build_and_publish.sh publish # Publish to RubyGems
|
91
|
+
|
92
|
+
# Quick development testing
|
93
|
+
./scripts/build_and_publish.sh build-quick
|
94
|
+
```
|
95
|
+
|
96
|
+
## ⚙️ Option 3: Makefile (Traditional build tool)
|
97
|
+
|
98
|
+
Standard make targets for those who prefer Makefiles.
|
99
|
+
|
100
|
+
### Available Targets
|
101
|
+
```bash
|
102
|
+
# Show help
|
103
|
+
make help
|
104
|
+
|
105
|
+
# Run individual validations
|
106
|
+
make test # Run tests only
|
107
|
+
make lint # Run RuboCop only
|
108
|
+
make validate # Run all validations
|
109
|
+
|
110
|
+
# Build gem with full validations
|
111
|
+
make build
|
112
|
+
|
113
|
+
# Quick build without validations (for testing)
|
114
|
+
make build-quick
|
115
|
+
|
116
|
+
# Publish to RubyGems with full validations
|
117
|
+
make publish
|
118
|
+
|
119
|
+
# Show gem information
|
120
|
+
make info
|
121
|
+
|
122
|
+
# Clean build artifacts
|
123
|
+
make clean
|
124
|
+
|
125
|
+
# Bump version interactively and commit
|
126
|
+
make bump
|
127
|
+
|
128
|
+
# Install dependencies
|
129
|
+
make install
|
130
|
+
```
|
131
|
+
|
132
|
+
### Examples
|
133
|
+
```bash
|
134
|
+
# Full build and publish workflow
|
135
|
+
make validate # Run tests, linting, git checks
|
136
|
+
make build # Build the gem
|
137
|
+
make publish # Publish to RubyGems
|
138
|
+
|
139
|
+
# Quick development testing
|
140
|
+
make build-quick
|
141
|
+
```
|
142
|
+
|
143
|
+
## 🔍 What Each Validation Includes
|
144
|
+
|
145
|
+
All three build systems perform the same comprehensive validations:
|
146
|
+
|
147
|
+
### ✅ Test Validation
|
148
|
+
- Runs the complete RSpec test suite
|
149
|
+
- Ensures 100% test coverage
|
150
|
+
- Validates all functionality works correctly
|
151
|
+
|
152
|
+
### ✅ Code Quality Validation
|
153
|
+
- Runs RuboCop linting
|
154
|
+
- Enforces consistent code style
|
155
|
+
- Checks for potential issues
|
156
|
+
|
157
|
+
### ✅ Git Validation
|
158
|
+
- Ensures working directory is clean (no uncommitted changes)
|
159
|
+
- Warns if not on `main` branch (with option to continue)
|
160
|
+
- Prevents accidental releases from dirty state
|
161
|
+
|
162
|
+
### ✅ Build Validation
|
163
|
+
- Verifies gemspec is valid
|
164
|
+
- Checks all required files are included
|
165
|
+
- Ensures gem can be built successfully
|
166
|
+
|
167
|
+
## 📦 Build Artifacts
|
168
|
+
|
169
|
+
All build methods create gems in the `pkg/` directory:
|
170
|
+
- `pkg/human_number-<version>.gem` - The built gem file
|
171
|
+
|
172
|
+
Use `make clean`, `rake gem:clean`, or `./scripts/build_and_publish.sh clean` to remove build artifacts.
|
173
|
+
|
174
|
+
## 🚀 Publishing Process
|
175
|
+
|
176
|
+
When you run the publish command (any of the three options), it will:
|
177
|
+
|
178
|
+
1. **Validate**: Run all tests, linting, and git checks
|
179
|
+
2. **Build**: Create the gem file
|
180
|
+
3. **Tag**: Create a git tag for the version
|
181
|
+
4. **Push**: Push the tag to the remote repository
|
182
|
+
5. **Publish**: Upload the gem to RubyGems.org
|
183
|
+
|
184
|
+
## 🔖 Version Management
|
185
|
+
|
186
|
+
All three build systems include interactive version bumping functionality:
|
187
|
+
|
188
|
+
### Version Bump Commands
|
189
|
+
|
190
|
+
| Tool | Command | Description |
|
191
|
+
|------|---------|-------------|
|
192
|
+
| **Rake** | `rake gem:bump` | Interactive version bump with commit |
|
193
|
+
| **Shell Script** | `./scripts/build_and_publish.sh bump` | Interactive version bump with commit |
|
194
|
+
| **Make** | `make bump` | Interactive version bump with commit |
|
195
|
+
|
196
|
+
### Version Bump Process
|
197
|
+
|
198
|
+
When you run any version bump command, it will:
|
199
|
+
|
200
|
+
1. **Display Current Version**: Shows the current version from `lib/human_number/version.rb`
|
201
|
+
2. **Interactive Input**: Prompts you to enter the new version
|
202
|
+
3. **Validation**: Ensures semantic versioning format (e.g., `1.2.3` or `1.2.3-alpha.1`)
|
203
|
+
4. **Update File**: Modifies the version file with the new version
|
204
|
+
5. **Git Commit**: Creates a commit with message `chore: bump version to X.Y.Z`
|
205
|
+
6. **Next Steps**: Provides guidance on building and publishing
|
206
|
+
|
207
|
+
### Example Workflow
|
208
|
+
|
209
|
+
```bash
|
210
|
+
# Using rake (recommended for Ruby developers)
|
211
|
+
rake gem:bump
|
212
|
+
# Current version: 0.1.2
|
213
|
+
# Enter new version: 0.2.0
|
214
|
+
# ✅ Updated version to 0.2.0
|
215
|
+
# ✅ Version bump committed successfully!
|
216
|
+
|
217
|
+
# Build and publish
|
218
|
+
rake gem:publish
|
219
|
+
```
|
220
|
+
|
221
|
+
## ⚠️ Prerequisites
|
222
|
+
|
223
|
+
Before publishing:
|
224
|
+
|
225
|
+
1. **RubyGems Account**: Ensure you have a RubyGems.org account
|
226
|
+
2. **API Key**: Configure your RubyGems API key (`gem signin`)
|
227
|
+
3. **Version Bump**: Use interactive version bump commands or manually update `lib/human_number/version.rb`
|
228
|
+
4. **Clean Git**: Commit all changes before publishing (version bump commands handle this automatically)
|
229
|
+
5. **Main Branch**: Recommended to publish from `main` branch
|
230
|
+
|
231
|
+
## 🔧 Configuration Changes
|
232
|
+
|
233
|
+
**Important**: This setup has removed MFA requirement from the gemspec to simplify the publishing process. If you need MFA, you can re-enable it by adding this line to `human_number.gemspec`:
|
234
|
+
|
235
|
+
```ruby
|
236
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
237
|
+
```
|
238
|
+
|
239
|
+
## 💡 Recommendations
|
240
|
+
|
241
|
+
- **For Ruby developers**: Use rake tasks (most native)
|
242
|
+
- **For CI/CD pipelines**: Use shell script (most portable)
|
243
|
+
- **For traditional workflows**: Use Makefile (universal)
|
244
|
+
- **For quick testing**: Use the `*-quick` variants to skip validations
|
245
|
+
|
246
|
+
Choose the tool that best fits your development workflow and team preferences!
|
data/CHANGELOG.md
CHANGED
@@ -86,4 +86,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
86
86
|
- **Formatters::Currency**: Currency symbol and format string application
|
87
87
|
- **Clean separation**: Number formatting independent of currency concerns
|
88
88
|
|
89
|
-
[0.1.0]: https://github.com/
|
89
|
+
[0.1.0]: https://github.com/ether-moon/human_number/releases/tag/v0.1.0
|
data/LICENSE
CHANGED
data/Makefile
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# HumanNumber Gem Build and Publish Makefile
|
2
|
+
|
3
|
+
.PHONY: help build publish build-quick validate clean info test lint bump
|
4
|
+
|
5
|
+
# Default target
|
6
|
+
help:
|
7
|
+
@echo "HumanNumber Gem Build and Publish Makefile"
|
8
|
+
@echo ""
|
9
|
+
@echo "Available targets:"
|
10
|
+
@echo " build Build gem with full validations (tests, linting, git checks)"
|
11
|
+
@echo " publish Publish gem to RubyGems with full validations"
|
12
|
+
@echo " build-quick Build gem without validations (for testing)"
|
13
|
+
@echo " validate Run all validations without building"
|
14
|
+
@echo " test Run tests only"
|
15
|
+
@echo " lint Run RuboCop only"
|
16
|
+
@echo " info Show gem and repository information"
|
17
|
+
@echo " clean Clean build artifacts"
|
18
|
+
@echo " bump Bump version interactively and commit"
|
19
|
+
@echo " help Show this help message"
|
20
|
+
@echo ""
|
21
|
+
@echo "Examples:"
|
22
|
+
@echo " make build # Build gem with validations"
|
23
|
+
@echo " make publish # Build and publish to RubyGems"
|
24
|
+
@echo " make bump # Bump version interactively"
|
25
|
+
@echo " make info # Show current gem info"
|
26
|
+
|
27
|
+
# Run tests
|
28
|
+
test:
|
29
|
+
@echo "📋 Running tests..."
|
30
|
+
@bundle exec rspec
|
31
|
+
@echo "✅ All tests passed"
|
32
|
+
|
33
|
+
# Run linting
|
34
|
+
lint:
|
35
|
+
@echo "🧹 Running RuboCop..."
|
36
|
+
@bundle exec rubocop
|
37
|
+
@echo "✅ RuboCop checks passed"
|
38
|
+
|
39
|
+
# Validate git status
|
40
|
+
validate-git:
|
41
|
+
@echo "📋 Checking git status..."
|
42
|
+
@if [ -n "$$(git status --porcelain)" ]; then \
|
43
|
+
echo "❌ Git working directory is not clean. Please commit all changes first."; \
|
44
|
+
exit 1; \
|
45
|
+
fi
|
46
|
+
@echo "✅ Git working directory is clean"
|
47
|
+
@current_branch=$$(git rev-parse --abbrev-ref HEAD); \
|
48
|
+
if [ "$$current_branch" != "main" ]; then \
|
49
|
+
echo "⚠️ Warning: You're on branch '$$current_branch', not 'main'"; \
|
50
|
+
read -p "Continue anyway? (y/N): " response; \
|
51
|
+
if [ "$$response" != "y" ] && [ "$$response" != "Y" ]; then \
|
52
|
+
echo "❌ Aborted by user"; \
|
53
|
+
exit 1; \
|
54
|
+
fi; \
|
55
|
+
fi
|
56
|
+
@echo "✅ Git branch check passed"
|
57
|
+
|
58
|
+
# Run all validations
|
59
|
+
validate: test lint validate-git
|
60
|
+
@echo "✅ All validations passed!"
|
61
|
+
|
62
|
+
# Build gem with validations
|
63
|
+
build: validate
|
64
|
+
@echo "🔨 Building gem..."
|
65
|
+
@bundle exec rake build
|
66
|
+
@echo "✅ Gem built successfully!"
|
67
|
+
|
68
|
+
# Build gem without validations (for testing)
|
69
|
+
build-quick:
|
70
|
+
@echo "🔨 Quick building gem (no validations)..."
|
71
|
+
@bundle exec rake build
|
72
|
+
@echo "✅ Gem built!"
|
73
|
+
|
74
|
+
# Publish gem to RubyGems
|
75
|
+
publish: validate
|
76
|
+
@echo "📦 Publishing gem to RubyGems..."
|
77
|
+
@bundle exec rake release
|
78
|
+
@echo "✅ Gem published successfully!"
|
79
|
+
|
80
|
+
# Show gem information
|
81
|
+
info:
|
82
|
+
@echo "📋 Gem Information:"
|
83
|
+
@echo " Name: human_number"
|
84
|
+
@echo " Version: $$(ruby -r ./lib/human_number/version -e 'puts HumanNumber::VERSION')"
|
85
|
+
@echo " Built gems: $$(ls pkg/*.gem 2>/dev/null || echo 'none')"
|
86
|
+
@echo " Git branch: $$(git rev-parse --abbrev-ref HEAD)"
|
87
|
+
@git_status=$$(git status --porcelain); \
|
88
|
+
if [ -z "$$git_status" ]; then \
|
89
|
+
echo " Git status: clean"; \
|
90
|
+
else \
|
91
|
+
echo " Git status: dirty"; \
|
92
|
+
fi
|
93
|
+
|
94
|
+
# Clean build artifacts
|
95
|
+
clean:
|
96
|
+
@echo "🧹 Cleaning build artifacts..."
|
97
|
+
@rm -rf pkg/
|
98
|
+
@echo "✅ Build artifacts cleaned!"
|
99
|
+
|
100
|
+
# Bump version interactively and commit
|
101
|
+
bump:
|
102
|
+
@echo "🔖 Version Bump"
|
103
|
+
@current_version=$$(ruby -r ./lib/human_number/version -e 'puts HumanNumber::VERSION'); \
|
104
|
+
echo "Current version: $$current_version"; \
|
105
|
+
echo ""; \
|
106
|
+
read -p "Enter new version: " new_version; \
|
107
|
+
if [ -z "$$new_version" ]; then \
|
108
|
+
echo "❌ No version entered. Aborting."; \
|
109
|
+
exit 1; \
|
110
|
+
fi; \
|
111
|
+
if ! echo "$$new_version" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$$"; then \
|
112
|
+
echo "❌ Invalid version format. Please use semantic versioning (e.g., 1.2.3 or 1.2.3-alpha.1)"; \
|
113
|
+
exit 1; \
|
114
|
+
fi; \
|
115
|
+
if [ "$$new_version" = "$$current_version" ]; then \
|
116
|
+
echo "❌ New version is the same as current version. Aborting."; \
|
117
|
+
exit 1; \
|
118
|
+
fi; \
|
119
|
+
version_file="lib/human_number/version.rb"; \
|
120
|
+
sed -i.bak "s/VERSION = \".*\"/VERSION = \"$$new_version\"/" "$$version_file" && rm "$$version_file.bak"; \
|
121
|
+
echo "✅ Updated version to $$new_version"; \
|
122
|
+
echo ""; \
|
123
|
+
echo "📝 Creating version bump commit..."; \
|
124
|
+
git add "$$version_file"; \
|
125
|
+
commit_message="chore: bump version to $$new_version"; \
|
126
|
+
if git commit -m "$$commit_message"; then \
|
127
|
+
echo "✅ Version bump committed successfully!"; \
|
128
|
+
echo ""; \
|
129
|
+
echo "📋 Next steps:"; \
|
130
|
+
echo " - Review the changes: git show"; \
|
131
|
+
echo " - Build and publish: make publish"; \
|
132
|
+
echo " - Push to remote: git push && git push --tags"; \
|
133
|
+
else \
|
134
|
+
echo "❌ Failed to create commit"; \
|
135
|
+
exit 1; \
|
136
|
+
fi
|
137
|
+
|
138
|
+
# Install dependencies
|
139
|
+
install:
|
140
|
+
@echo "📦 Installing dependencies..."
|
141
|
+
@bundle install
|
142
|
+
@echo "✅ Dependencies installed!"
|
data/README.md
CHANGED
@@ -34,7 +34,7 @@ HumanNumber.human_number(50_000, locale: :ko) #=> "5만"
|
|
34
34
|
|
35
35
|
# Currency formatting
|
36
36
|
HumanNumber.currency(1234.56, currency_code: 'USD', locale: :en) #=> "$1,234.56"
|
37
|
-
HumanNumber.human_currency(1_234_567, currency_code: 'USD', locale: :en) #=> "$
|
37
|
+
HumanNumber.human_currency(1_234_567, currency_code: 'USD', locale: :en) #=> "$1M"
|
38
38
|
```
|
39
39
|
|
40
40
|
## Installation
|
@@ -71,17 +71,17 @@ HumanNumber.human_number(1_234_567) #=> "1.2M"
|
|
71
71
|
HumanNumber.human_number(50_000, locale: :ko) #=> "5만"
|
72
72
|
HumanNumber.human_number(50_000, locale: :ja) #=> "5万"
|
73
73
|
|
74
|
-
#
|
75
|
-
HumanNumber.human_number(1_234_567, max_digits: 1) #=> "1M"
|
76
|
-
HumanNumber.human_number(1_234_567, max_digits: 3) #=> "1.23M"
|
77
|
-
HumanNumber.human_number(1_234_567, max_digits: nil) #=> "1M 234K 567"
|
74
|
+
# Significant digits control (default: max_digits: 2)
|
75
|
+
HumanNumber.human_number(1_234_567, max_digits: 1) #=> "1M" # 1 significant digit
|
76
|
+
HumanNumber.human_number(1_234_567, max_digits: 3) #=> "1.23M" # 3 significant digits
|
77
|
+
HumanNumber.human_number(1_234_567, max_digits: nil) #=> "1M 234K 567" # Complete breakdown
|
78
78
|
|
79
79
|
# Unit preferences (Western locales only)
|
80
80
|
HumanNumber.human_number(1_000_000, abbr_units: true) #=> "1M"
|
81
81
|
HumanNumber.human_number(1_000_000, abbr_units: false) #=> "1 million"
|
82
82
|
|
83
83
|
# Minimum thresholds
|
84
|
-
HumanNumber.human_number(5_000, min_unit: 10_000) #=> "
|
84
|
+
HumanNumber.human_number(5_000, min_unit: 10_000) #=> "5,000"
|
85
85
|
HumanNumber.human_number(50_000, min_unit: 10_000) #=> "50K"
|
86
86
|
|
87
87
|
# Zero trimming (default: true)
|
@@ -115,7 +115,7 @@ HumanNumber.currency(1234.56, currency_code: 'JPY', locale: :en) #=> "1,235円"
|
|
115
115
|
Human-readable currency formatting with cultural abbreviations.
|
116
116
|
|
117
117
|
```ruby
|
118
|
-
HumanNumber.human_currency(1_234_567, currency_code: 'USD', locale: :en) #=> "$
|
118
|
+
HumanNumber.human_currency(1_234_567, currency_code: 'USD', locale: :en) #=> "$1M"
|
119
119
|
HumanNumber.human_currency(50_000, currency_code: 'KRW', locale: :ko) #=> "5만원"
|
120
120
|
|
121
121
|
# Combined options
|
@@ -129,7 +129,7 @@ In Rails applications, helper methods are automatically available:
|
|
129
129
|
|
130
130
|
```erb
|
131
131
|
<%= human_number(1_234_567) %> <!-- 1.2M -->
|
132
|
-
<%= human_currency(1_234_567, currency_code: 'USD') %> <!-- $
|
132
|
+
<%= human_currency(1_234_567, currency_code: 'USD') %> <!-- $1M -->
|
133
133
|
<%= currency(1234.56, currency_code: 'USD') %> <!-- $1,234.56 -->
|
134
134
|
|
135
135
|
<!-- With options -->
|
@@ -155,8 +155,8 @@ HumanNumber.human_number(1_000_000_000, locale: :en) #=> "1B"
|
|
155
155
|
**Used by:** Korean (ko), Japanese (ja), Chinese (zh, zh-CN, zh-TW)
|
156
156
|
|
157
157
|
```ruby
|
158
|
-
HumanNumber.human_number(1_234_567, locale: :ko) #=> "
|
159
|
-
HumanNumber.human_number(1_234_567, locale: :ja) #=> "
|
158
|
+
HumanNumber.human_number(1_234_567, locale: :ko) #=> "120만"
|
159
|
+
HumanNumber.human_number(1_234_567, locale: :ja) #=> "120万"
|
160
160
|
HumanNumber.human_number(100_000_000, locale: :ko) #=> "1억"
|
161
161
|
```
|
162
162
|
|
@@ -277,7 +277,7 @@ $ rake # Runs both spec and rubocop
|
|
277
277
|
|
278
278
|
## Contributing
|
279
279
|
|
280
|
-
1. Fork it (https://github.com/
|
280
|
+
1. Fork it (https://github.com/ether-moon/human_number/fork)
|
281
281
|
2. Create your feature branch (`git checkout -b feature/my-new-feature`)
|
282
282
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
283
283
|
4. Push to the branch (`git push origin feature/my-new-feature`)
|
@@ -320,4 +320,4 @@ See [CHANGELOG.md](CHANGELOG.md) for details.
|
|
320
320
|
|
321
321
|
## Issues
|
322
322
|
|
323
|
-
Please report bugs and feature requests at [GitHub Issues](https://github.com/
|
323
|
+
Please report bugs and feature requests at [GitHub Issues](https://github.com/ether-moon/human_number/issues).
|
data/Rakefile
CHANGED
@@ -10,3 +10,138 @@ require "rubocop/rake_task"
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
12
|
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
# Gem build and publish tasks
|
15
|
+
namespace :gem do
|
16
|
+
desc "Run all pre-publish validations"
|
17
|
+
task :validate do
|
18
|
+
puts "🔍 Running pre-publish validations..."
|
19
|
+
|
20
|
+
# Run tests
|
21
|
+
puts "\n📋 Running tests..."
|
22
|
+
Rake::Task[:spec].invoke
|
23
|
+
|
24
|
+
# Run linting
|
25
|
+
puts "\n🧹 Running RuboCop..."
|
26
|
+
Rake::Task[:rubocop].invoke
|
27
|
+
|
28
|
+
# Check git status
|
29
|
+
puts "\n📋 Checking git status..."
|
30
|
+
unless `git status --porcelain`.strip.empty?
|
31
|
+
abort "❌ Git working directory is not clean. Please commit all changes first."
|
32
|
+
end
|
33
|
+
|
34
|
+
# Check if we're on main branch
|
35
|
+
current_branch = `git rev-parse --abbrev-ref HEAD`.strip
|
36
|
+
unless current_branch == "main"
|
37
|
+
puts "⚠️ Warning: You're on branch '#{current_branch}', not 'main'"
|
38
|
+
print "Continue anyway? (y/N): "
|
39
|
+
response = $stdin.gets.chomp.downcase
|
40
|
+
abort "❌ Aborted by user" unless %w[y yes].include?(response)
|
41
|
+
end
|
42
|
+
|
43
|
+
puts "✅ All validations passed!"
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Build the gem"
|
47
|
+
task build: :validate do
|
48
|
+
puts "\n🔨 Building gem..."
|
49
|
+
Rake::Task["build"].invoke
|
50
|
+
puts "✅ Gem built successfully!"
|
51
|
+
end
|
52
|
+
|
53
|
+
desc "Publish gem to RubyGems (with validations)"
|
54
|
+
task publish: :validate do
|
55
|
+
puts "\n📦 Publishing gem to RubyGems..."
|
56
|
+
|
57
|
+
# Build and push
|
58
|
+
Rake::Task["release"].invoke
|
59
|
+
puts "✅ Gem published successfully!"
|
60
|
+
end
|
61
|
+
|
62
|
+
desc "Quick build without validations (for testing)"
|
63
|
+
task :build_quick do
|
64
|
+
puts "🔨 Quick building gem..."
|
65
|
+
Rake::Task["build"].invoke
|
66
|
+
puts "✅ Gem built!"
|
67
|
+
end
|
68
|
+
|
69
|
+
desc "Clean build artifacts"
|
70
|
+
task :clean do
|
71
|
+
puts "🧹 Cleaning build artifacts..."
|
72
|
+
FileUtils.rm_rf("pkg")
|
73
|
+
puts "✅ Build artifacts cleaned!"
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "Show gem info"
|
77
|
+
task :info do
|
78
|
+
require_relative "lib/human_number/version"
|
79
|
+
puts "\n📋 Gem Information:"
|
80
|
+
puts " Name: human_number"
|
81
|
+
puts " Version: #{HumanNumber::VERSION}"
|
82
|
+
puts " Built gems: #{Dir.glob("pkg/*.gem").join(", ")}"
|
83
|
+
puts " Git branch: #{`git rev-parse --abbrev-ref HEAD`.strip}"
|
84
|
+
puts " Git status: #{`git status --porcelain`.strip.empty? ? "clean" : "dirty"}"
|
85
|
+
end
|
86
|
+
|
87
|
+
desc "Bump version interactively and commit"
|
88
|
+
task :bump do
|
89
|
+
require_relative "lib/human_number/version"
|
90
|
+
|
91
|
+
puts "🔖 Version Bump"
|
92
|
+
puts "Current version: #{HumanNumber::VERSION}"
|
93
|
+
puts ""
|
94
|
+
|
95
|
+
# Get new version from user
|
96
|
+
print "Enter new version: "
|
97
|
+
new_version = $stdin.gets.chomp.strip
|
98
|
+
|
99
|
+
if new_version.empty?
|
100
|
+
puts "❌ No version entered. Aborting."
|
101
|
+
exit 1
|
102
|
+
end
|
103
|
+
|
104
|
+
# Validate version format (basic semver check)
|
105
|
+
unless new_version.match?(/^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?$/)
|
106
|
+
puts "❌ Invalid version format. Please use semantic versioning (e.g., 1.2.3 or 1.2.3-alpha.1)"
|
107
|
+
exit 1
|
108
|
+
end
|
109
|
+
|
110
|
+
# Check if version is different
|
111
|
+
if new_version == HumanNumber::VERSION
|
112
|
+
puts "❌ New version is the same as current version. Aborting."
|
113
|
+
exit 1
|
114
|
+
end
|
115
|
+
|
116
|
+
# Update version file
|
117
|
+
version_file = "lib/human_number/version.rb"
|
118
|
+
version_content = File.read(version_file)
|
119
|
+
new_content = version_content.gsub(/VERSION = ".*"/, "VERSION = \"#{new_version}\"")
|
120
|
+
|
121
|
+
File.write(version_file, new_content)
|
122
|
+
puts "✅ Updated version to #{new_version}"
|
123
|
+
|
124
|
+
# Create commit
|
125
|
+
puts "\n📝 Creating version bump commit..."
|
126
|
+
system("git add #{version_file}")
|
127
|
+
commit_message = "chore: bump version to #{new_version}"
|
128
|
+
|
129
|
+
if system("git commit -m '#{commit_message}'")
|
130
|
+
puts "✅ Version bump committed successfully!"
|
131
|
+
puts "\n📋 Next steps:"
|
132
|
+
puts " - Review the changes: git show"
|
133
|
+
puts " - Build and publish: rake gem:publish"
|
134
|
+
puts " - Push to remote: git push && git push --tags"
|
135
|
+
else
|
136
|
+
puts "❌ Failed to create commit"
|
137
|
+
exit 1
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
# Convenient aliases
|
143
|
+
desc "Build and publish gem (same as gem:publish)"
|
144
|
+
task release: "gem:publish"
|
145
|
+
|
146
|
+
desc "Build gem only (same as gem:build)"
|
147
|
+
task build: "gem:build"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/human_number/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "human_number"
|
7
|
+
spec.version = HumanNumber::VERSION
|
8
|
+
spec.authors = ["Ether Moon"]
|
9
|
+
spec.email = ["ethermoon42@gmail.com"]
|
10
|
+
|
11
|
+
spec.summary = "International standard-based number formatting for Ruby applications"
|
12
|
+
spec.description = "HumanNumber implements accurate number formatting based on international standards " \
|
13
|
+
"including Microsoft Globalization documentation and Unicode CLDR. Provides human-readable " \
|
14
|
+
"number formats with precise locale-specific decimal separators, thousand separators, " \
|
15
|
+
"currency formats, and cultural number conventions."
|
16
|
+
spec.homepage = "https://github.com/ether-moon/human_number"
|
17
|
+
spec.license = "MIT"
|
18
|
+
spec.required_ruby_version = ">= 3.1.0"
|
19
|
+
|
20
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
21
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
22
|
+
spec.metadata["source_code_uri"] = "https://github.com/ether-moon/human_number"
|
23
|
+
spec.metadata["changelog_uri"] = "https://github.com/ether-moon/human_number/blob/main/CHANGELOG.md"
|
24
|
+
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
26
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
27
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
28
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
29
|
+
(f == __FILE__) || f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
30
|
+
end
|
31
|
+
end
|
32
|
+
spec.bindir = "exe"
|
33
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
34
|
+
spec.require_paths = ["lib"]
|
35
|
+
|
36
|
+
# Runtime dependencies
|
37
|
+
spec.add_dependency "actionview", ">= 5.2"
|
38
|
+
spec.add_dependency "i18n", ">= 1.6", "< 2"
|
39
|
+
spec.add_dependency "rails-i18n", ">= 5.1"
|
40
|
+
end
|
@@ -27,14 +27,14 @@ module HumanNumber
|
|
27
27
|
# @param number [Numeric] Number to format
|
28
28
|
# @param locale [Symbol, String] Locale for unit system selection
|
29
29
|
# @param abbr_units [Boolean] Use abbreviated symbols vs full words
|
30
|
-
# @param max_digits [Integer, nil]
|
30
|
+
# @param max_digits [Integer, nil] Maximum significant digits to display, nil for complete mode
|
31
31
|
# @param min_unit [Integer, nil] Minimum unit threshold for abbreviation
|
32
32
|
# @param trim_zeros [Boolean] Remove trailing decimal zeros
|
33
33
|
# @return [String] Formatted number string
|
34
34
|
#
|
35
35
|
# @api private
|
36
36
|
def format(number, locale:, abbr_units:, max_digits:, min_unit:, trim_zeros:)
|
37
|
-
locale = locale.to_sym
|
37
|
+
locale = (locale || I18n.locale).to_sym
|
38
38
|
|
39
39
|
# Direct delegation to appropriate system class
|
40
40
|
system_class = determine_system_class(locale)
|
@@ -48,42 +48,41 @@ module HumanNumber
|
|
48
48
|
)
|
49
49
|
end
|
50
50
|
|
51
|
-
# Default options for human number formatting
|
52
|
-
def default_options
|
53
|
-
{
|
54
|
-
abbr_units: true,
|
55
|
-
max_digits: 2,
|
56
|
-
min_unit: nil,
|
57
|
-
trim_zeros: true,
|
58
|
-
}.freeze
|
59
|
-
end
|
60
|
-
|
61
|
-
# Default options for currency number formatting (tighter display)
|
62
|
-
def default_currency_number_options
|
63
|
-
{
|
64
|
-
abbr_units: true,
|
65
|
-
max_digits: 1,
|
66
|
-
min_unit: nil,
|
67
|
-
trim_zeros: true,
|
68
|
-
}.freeze
|
69
|
-
end
|
70
|
-
|
71
51
|
# Formats a number using Rails' currency precision rules for the given currency.
|
72
52
|
# This ensures consistent precision regardless of display locale.
|
73
53
|
#
|
74
54
|
# @param number [Numeric] The number to format
|
75
55
|
# @param currency_code [String] ISO 4217 currency code
|
76
56
|
# @param locale [Symbol] Display locale (for context)
|
57
|
+
# @param options [Hash] Additional formatting options passed to number_to_currency
|
77
58
|
# @return [String] Formatted number with currency-appropriate precision
|
78
|
-
def format_with_currency_precision(number, currency_code:, locale
|
79
|
-
locale = locale.to_sym
|
59
|
+
def format_with_currency_precision(number, currency_code:, locale:, **options)
|
60
|
+
locale = (locale || I18n.locale).to_sym
|
80
61
|
|
81
|
-
# Use currency's native locale for precision rules
|
62
|
+
# Use currency's native locale for precision rules unless overridden
|
82
63
|
precision_locale = LocaleSupport.currency_precision_locale(currency_code, locale)
|
83
64
|
|
84
|
-
|
85
|
-
|
86
|
-
|
65
|
+
# Prepare options for number_to_currency
|
66
|
+
currency_options = options.dup
|
67
|
+
currency_options[:locale] = precision_locale
|
68
|
+
currency_options[:format] = "%n" # Always format as just the number (no currency symbol)
|
69
|
+
|
70
|
+
number_to_currency(number, **currency_options) || ZERO_STRING
|
71
|
+
end
|
72
|
+
|
73
|
+
# Default options for human number formatting
|
74
|
+
def default_options
|
75
|
+
{
|
76
|
+
abbr_units: true,
|
77
|
+
max_digits: 2,
|
78
|
+
min_unit: nil,
|
79
|
+
trim_zeros: true,
|
80
|
+
}.freeze
|
81
|
+
end
|
82
|
+
|
83
|
+
# Format numbers below min_unit threshold with locale-appropriate delimiters
|
84
|
+
def format_below_min_unit(number, locale)
|
85
|
+
number_with_delimiter(number, locale: locale) || number.to_s
|
87
86
|
end
|
88
87
|
|
89
88
|
private
|
@@ -117,6 +116,9 @@ module HumanNumber
|
|
117
116
|
def format_number(number, locale:, abbr_units: true, min_unit: nil, max_digits: 2, trim_zeros: true)
|
118
117
|
return ZERO_STRING if number.zero?
|
119
118
|
|
119
|
+
# Check if number meets minimum unit threshold for human formatting
|
120
|
+
return Number.format_below_min_unit(number, locale) if min_unit && number.abs < min_unit
|
121
|
+
|
120
122
|
parts = if max_digits.nil?
|
121
123
|
format_in_complete_mode(number, locale, abbr_units, min_unit)
|
122
124
|
else
|
@@ -128,22 +130,20 @@ module HumanNumber
|
|
128
130
|
finalize_result(number, parts)
|
129
131
|
end
|
130
132
|
|
131
|
-
def format_in_complete_mode(number, locale, abbr_units,
|
133
|
+
def format_in_complete_mode(number, locale, abbr_units, _min_unit)
|
132
134
|
# Complete mode shows all units: "1M 234K 567"
|
133
135
|
unit_breakdown = break_down_into_all_units(number)
|
134
|
-
|
135
|
-
return nil if units_above_threshold.empty?
|
136
|
+
return nil if unit_breakdown.empty?
|
136
137
|
|
137
|
-
format_all_unit_parts(
|
138
|
+
format_all_unit_parts(unit_breakdown, locale, abbr_units)
|
138
139
|
end
|
139
140
|
|
140
|
-
def format_in_abbreviated_mode(number, locale, abbr_units, max_digits, trim_zeros,
|
141
|
+
def format_in_abbreviated_mode(number, locale, abbr_units, max_digits, trim_zeros, _min_unit)
|
141
142
|
# Abbreviated mode shows single largest unit: "1.2M"
|
142
143
|
unit_breakdown = break_down_into_largest_unit(number)
|
143
|
-
|
144
|
-
return nil if units_above_threshold.empty?
|
144
|
+
return nil if unit_breakdown.empty?
|
145
145
|
|
146
|
-
format_abbreviated_unit_parts(
|
146
|
+
format_abbreviated_unit_parts(unit_breakdown, locale, abbr_units, max_digits, trim_zeros)
|
147
147
|
end
|
148
148
|
|
149
149
|
private
|
@@ -267,10 +267,13 @@ module HumanNumber
|
|
267
267
|
return handle_edge_cases(number, digits) if should_handle_edge_case?(number, digits)
|
268
268
|
|
269
269
|
abs_num = number.abs
|
270
|
-
int_digits = count_integer_digits(abs_num.to_i)
|
271
270
|
|
272
|
-
|
273
|
-
|
271
|
+
# Simple significant digits rounding using scientific notation
|
272
|
+
magnitude = Math.log10(abs_num).floor
|
273
|
+
scale_factor = 10**(digits - 1 - magnitude)
|
274
|
+
|
275
|
+
rounded = (abs_num * scale_factor).round / scale_factor.to_f
|
276
|
+
apply_sign(number, rounded)
|
274
277
|
end
|
275
278
|
|
276
279
|
def should_handle_edge_case?(number, digits)
|
@@ -283,14 +286,6 @@ module HumanNumber
|
|
283
286
|
number
|
284
287
|
end
|
285
288
|
|
286
|
-
def calculate_significant_digits_result(abs_num, digits, int_digits)
|
287
|
-
if int_digits >= digits
|
288
|
-
truncate_to_digits(abs_num.to_i, digits)
|
289
|
-
else
|
290
|
-
round_with_decimals(abs_num, digits, int_digits)
|
291
|
-
end
|
292
|
-
end
|
293
|
-
|
294
289
|
def apply_sign(original_number, result)
|
295
290
|
original_number.negative? ? -result : result
|
296
291
|
end
|
@@ -299,15 +294,6 @@ module HumanNumber
|
|
299
294
|
int_part.zero? ? SINGLE_DIGIT_LIMIT : int_part.to_s.length
|
300
295
|
end
|
301
296
|
|
302
|
-
def truncate_to_digits(int_part, digits)
|
303
|
-
int_part.to_s[0, digits].to_i.to_f
|
304
|
-
end
|
305
|
-
|
306
|
-
def round_with_decimals(abs_num, digits, int_digits)
|
307
|
-
decimal_places = digits - int_digits
|
308
|
-
(abs_num * (10**decimal_places)).round / (10**decimal_places).to_f
|
309
|
-
end
|
310
|
-
|
311
297
|
# Look up the localized symbol for a unit (e.g., 'M' for million)
|
312
298
|
def lookup_unit_symbol(locale, unit_key, abbr_units)
|
313
299
|
section = abbr_units ? I18N_ABBR_UNITS_SECTION : I18N_UNITS_SECTION
|
@@ -7,75 +7,49 @@ module HumanNumber
|
|
7
7
|
# These helpers provide convenient access to HumanNumber functionality
|
8
8
|
# within Rails views and controllers with Rails-friendly parameter handling.
|
9
9
|
module Helpers
|
10
|
-
#
|
11
|
-
#
|
12
|
-
# This is a Rails helper wrapper around HumanNumber.human_number that provides
|
13
|
-
# Rails-friendly parameter handling with optional locale detection.
|
10
|
+
# Rails helper for formatting numbers with intelligent abbreviations.
|
14
11
|
#
|
15
12
|
# @param number [Numeric] The number to format
|
16
13
|
# @param locale [Symbol, String, nil] The locale for formatting (default: current I18n locale)
|
17
14
|
# @param options [Hash] Additional formatting options
|
18
|
-
# @option options [Boolean] :abbr_units (true) Use abbreviated unit symbols
|
19
|
-
# @option options [Integer, nil] :max_digits (1) Maximum significant digits
|
20
|
-
# @option options [Integer, nil] :min_unit (nil) Minimum unit threshold
|
21
|
-
# @option options [Boolean] :trim_zeros (true) Remove trailing decimal zeros
|
22
|
-
#
|
23
|
-
# @return [String] The formatted number string
|
24
15
|
#
|
25
16
|
# @example Basic usage in views
|
26
|
-
# <%= human_number(1234567) %>
|
27
|
-
# <%= human_number(50000, locale: :ko) %>
|
28
|
-
# <%= human_number(1234567, max_digits: 2) %> #=> "1.23M"
|
17
|
+
# <%= human_number(1234567) %> #=> "1.2M"
|
18
|
+
# <%= human_number(50000, locale: :ko) %> #=> "5만"
|
29
19
|
#
|
30
|
-
# @see HumanNumber.human_number
|
20
|
+
# @see HumanNumber.human_number For detailed documentation and all available options
|
31
21
|
def human_number(number, locale: I18n.locale, **options)
|
32
22
|
HumanNumber.human_number(number, locale:, **options)
|
33
23
|
end
|
34
24
|
|
35
|
-
#
|
36
|
-
#
|
37
|
-
# This helper provides Rails-friendly parameter handling for currency formatting
|
38
|
-
# with automatic locale detection and parameter normalization.
|
25
|
+
# Rails helper for formatting currency amounts with standard precision.
|
39
26
|
#
|
40
27
|
# @param number [Numeric] The amount to format
|
41
28
|
# @param currency_code [String] ISO 4217 currency code (e.g., 'USD', 'EUR')
|
42
29
|
# @param locale [Symbol, String, nil] Display locale (default: current I18n locale)
|
43
|
-
#
|
44
|
-
# @return [String] The formatted currency string
|
30
|
+
# @param options [Hash] Additional formatting options
|
45
31
|
#
|
46
32
|
# @example Basic usage in views
|
47
|
-
# <%= currency(1234.56, currency_code: 'USD') %>
|
48
|
-
# <%= currency(50000, currency_code: 'KRW'
|
49
|
-
# <%= currency(1234.99, currency_code: 'JPY') %> #=> "1,235円"
|
33
|
+
# <%= currency(1234.56, currency_code: 'USD') %> #=> "$1,234.56"
|
34
|
+
# <%= currency(50000, currency_code: 'KRW') %> #=> "50,000원"
|
50
35
|
#
|
51
|
-
# @see HumanNumber.currency
|
52
|
-
def currency(number, currency_code:, locale: I18n.locale)
|
53
|
-
HumanNumber.currency(number, currency_code:, locale
|
36
|
+
# @see HumanNumber.currency For detailed documentation and all available options
|
37
|
+
def currency(number, currency_code:, locale: I18n.locale, **options)
|
38
|
+
HumanNumber.currency(number, currency_code:, locale:, **options)
|
54
39
|
end
|
55
40
|
|
56
|
-
#
|
57
|
-
#
|
58
|
-
# This helper combines human-readable number formatting with currency symbols,
|
59
|
-
# providing Rails-friendly access to abbreviated currency formatting.
|
41
|
+
# Rails helper for formatting currency amounts with intelligent abbreviations.
|
60
42
|
#
|
61
43
|
# @param number [Numeric] The amount to format
|
62
44
|
# @param currency_code [String] ISO 4217 currency code (e.g., 'USD', 'EUR')
|
63
45
|
# @param locale [Symbol, String, nil] Locale for formatting (default: current I18n locale)
|
64
46
|
# @param options [Hash] Additional formatting options
|
65
|
-
# @option options [Boolean] :abbr_units (true) Use abbreviated unit symbols
|
66
|
-
# @option options [Integer, nil] :max_digits (1) Maximum significant digits
|
67
|
-
# @option options [Integer, nil] :min_unit (nil) Minimum unit threshold
|
68
|
-
# @option options [Boolean] :trim_zeros (true) Remove trailing decimal zeros
|
69
|
-
#
|
70
|
-
# @return [String] The formatted currency string with abbreviations
|
71
47
|
#
|
72
48
|
# @example Basic usage in views
|
73
|
-
# <%= human_currency(1234567, currency_code: 'USD') %>
|
74
|
-
# <%= human_currency(50000, currency_code: 'KRW'
|
75
|
-
# <%= human_currency(1234567, currency_code: 'USD', max_digits: 2) %> #=> "$1.23M"
|
76
|
-
# <%= human_currency(1000000, currency_code: 'USD', abbr_units: false) %> #=> "$1 million"
|
49
|
+
# <%= human_currency(1234567, currency_code: 'USD') %> #=> "$1.2M"
|
50
|
+
# <%= human_currency(50000, currency_code: 'KRW') %> #=> "5만원"
|
77
51
|
#
|
78
|
-
# @see HumanNumber.human_currency
|
52
|
+
# @see HumanNumber.human_currency For detailed documentation and all available options
|
79
53
|
def human_currency(number, currency_code:, locale: I18n.locale, **options)
|
80
54
|
HumanNumber.human_currency(number, currency_code:, locale:, **options)
|
81
55
|
end
|
data/lib/human_number/version.rb
CHANGED
data/lib/human_number.rb
CHANGED
@@ -66,7 +66,6 @@ module HumanNumber
|
|
66
66
|
#
|
67
67
|
# @note Complete mode (max_digits: nil) shows full precision across multiple units
|
68
68
|
# @see Formatters::Number.format The underlying formatter implementation
|
69
|
-
# @since 1.0.0
|
70
69
|
def human_number(number, locale: I18n.locale, **options)
|
71
70
|
validate_number_input!(number)
|
72
71
|
validate_locale!(locale)
|
@@ -76,23 +75,41 @@ module HumanNumber
|
|
76
75
|
Formatters::Number.format(number, locale:, **final_options)
|
77
76
|
end
|
78
77
|
|
79
|
-
# Formats a currency amount
|
78
|
+
# Formats a currency amount with standard precision and symbol placement.
|
80
79
|
#
|
81
|
-
# This method
|
82
|
-
#
|
83
|
-
#
|
80
|
+
# This method provides currency formatting with automatic precision rules based on
|
81
|
+
# international standards, ensuring consistent display across different locales:
|
82
|
+
# - **Currency-specific precision**: 2 decimals for USD/EUR, 0 for JPY/KRW
|
83
|
+
# - **Native locale rules**: Precision determined by currency's origin locale
|
84
|
+
# - **Cross-locale consistency**: USD shows 2 decimals regardless of display locale
|
85
|
+
# - **Symbol placement**: Follows locale-specific conventions ($1,234 vs 1,234원)
|
84
86
|
#
|
85
87
|
# @param number [Numeric] The amount to format
|
86
88
|
# @param currency_code [String] ISO 4217 currency code (e.g., 'USD', 'EUR', 'KRW', 'JPY')
|
87
|
-
# @param locale [Symbol, String] Display locale for formatting rules
|
89
|
+
# @param locale [Symbol, String] Display locale for formatting rules (default: current I18n locale)
|
90
|
+
# Determines delimiter, separator, and symbol placement conventions
|
91
|
+
#
|
92
|
+
# @option options [Integer] :precision Decimal precision level. Overrides currency-specific precision
|
93
|
+
# @option options [Symbol] :round_mode Rounding mode (see BigDecimal.mode). Defaults to :default
|
94
|
+
# @option options [String] :separator Decimal separator. Defaults to locale-specific separator
|
95
|
+
# @option options [String] :delimiter Thousands delimiter. Defaults to locale-specific delimiter
|
96
|
+
# @option options [Boolean] :strip_insignificant_zeros (false) Remove trailing decimal zeros
|
97
|
+
#
|
98
|
+
# @note Only numeric formatting options are supported. Currency symbols and formats are
|
99
|
+
# automatically determined by ISO 4217 standards and locale conventions.
|
88
100
|
#
|
89
101
|
# @return [String] The formatted currency string
|
90
102
|
#
|
91
|
-
# @example
|
103
|
+
# @example Basic currency formatting
|
92
104
|
# HumanNumber.currency(1234.56, currency_code: 'USD', locale: :en) #=> "$1,234.56"
|
93
105
|
# HumanNumber.currency(50000, currency_code: 'KRW', locale: :ko) #=> "50,000원"
|
94
106
|
# HumanNumber.currency(1234.99, currency_code: 'JPY', locale: :ja) #=> "1,235円"
|
95
107
|
#
|
108
|
+
# @example Custom precision and formatting
|
109
|
+
# HumanNumber.currency(1234.56, currency_code: 'USD', precision: 1) #=> "$1,234.6"
|
110
|
+
# HumanNumber.currency(1234.56, currency_code: 'USD', delimiter: " ") #=> "$1 234.56"
|
111
|
+
# HumanNumber.currency(1234.50, currency_code: 'USD', strip_insignificant_zeros: true) #=> "$1,234.5"
|
112
|
+
#
|
96
113
|
# @example Cross-locale precision consistency
|
97
114
|
# # USD always shows 2 decimals regardless of display locale
|
98
115
|
# HumanNumber.currency(1234.56, currency_code: 'USD', locale: :ko) #=> "$1,234.56"
|
@@ -100,16 +117,20 @@ module HumanNumber
|
|
100
117
|
# # JPY always shows 0 decimals regardless of display locale
|
101
118
|
# HumanNumber.currency(1234.56, currency_code: 'JPY', locale: :en) #=> "1,235円"
|
102
119
|
#
|
103
|
-
# @
|
120
|
+
# @example Edge cases
|
121
|
+
# HumanNumber.currency(0, currency_code: 'USD') #=> "$0.00"
|
122
|
+
# HumanNumber.currency(-1234.56, currency_code: 'USD') #=> "-$1,234.56"
|
123
|
+
#
|
124
|
+
# @note Precision is determined by currency's native locale unless overridden via :precision option
|
125
|
+
# @see #human_currency For currency formatting with intelligent abbreviations (K/M/B)
|
104
126
|
# @see Formatters::Number.format_with_currency_precision Currency precision logic
|
105
127
|
# @see Formatters::Currency.format Currency symbol and format application
|
106
|
-
|
107
|
-
def currency(number, currency_code:, locale: I18n.locale)
|
128
|
+
def currency(number, currency_code:, locale: I18n.locale, **options)
|
108
129
|
validate_number_input!(number)
|
109
130
|
validate_currency_code!(currency_code)
|
110
131
|
validate_locale!(locale)
|
111
132
|
|
112
|
-
formatted_number = Formatters::Number.format_with_currency_precision(number, currency_code:, locale
|
133
|
+
formatted_number = Formatters::Number.format_with_currency_precision(number, currency_code:, locale:, **options)
|
113
134
|
Formatters::Currency.format(formatted_number, currency_code:, locale:)
|
114
135
|
end
|
115
136
|
|
@@ -139,7 +160,7 @@ module HumanNumber
|
|
139
160
|
# HumanNumber.human_currency(50000, currency_code: 'KRW', locale: :ko) #=> "5만원"
|
140
161
|
#
|
141
162
|
# @example Precision control
|
142
|
-
# HumanNumber.human_currency(1234567, currency_code: 'USD', locale: :en, max_digits: 2) #=> "$1.
|
163
|
+
# HumanNumber.human_currency(1234567, currency_code: 'USD', locale: :en, max_digits: 2) #=> "$1.2M"
|
143
164
|
# HumanNumber.human_currency(1234567, currency_code: 'USD', locale: :en, max_digits: nil) #=> "$1M 234K 567"
|
144
165
|
#
|
145
166
|
# @example Unit preferences
|
@@ -153,13 +174,12 @@ module HumanNumber
|
|
153
174
|
# @note Combines human number formatting with currency-specific symbol placement
|
154
175
|
# @see #human_number For detailed number formatting options
|
155
176
|
# @see #currency For standard currency formatting without abbreviations
|
156
|
-
# @since 1.0.0
|
157
177
|
def human_currency(number, currency_code:, locale: I18n.locale, **options)
|
158
178
|
validate_number_input!(number)
|
159
179
|
validate_currency_code!(currency_code)
|
160
180
|
validate_locale!(locale)
|
161
181
|
|
162
|
-
final_options = Formatters::Number.
|
182
|
+
final_options = Formatters::Number.default_options.merge(options)
|
163
183
|
|
164
184
|
formatted_number = Formatters::Number.format(number, locale:, **final_options)
|
165
185
|
Formatters::Currency.format(formatted_number, currency_code:, locale:)
|
@@ -0,0 +1,204 @@
|
|
1
|
+
#!/bin/bash
|
2
|
+
|
3
|
+
# HumanNumber Gem Build and Publish Script
|
4
|
+
# Usage: ./scripts/build_and_publish.sh [build|publish|info|clean]
|
5
|
+
|
6
|
+
set -e # Exit on any error
|
7
|
+
|
8
|
+
# Colors for output
|
9
|
+
RED='\033[0;31m'
|
10
|
+
GREEN='\033[0;32m'
|
11
|
+
YELLOW='\033[1;33m'
|
12
|
+
BLUE='\033[0;34m'
|
13
|
+
NC='\033[0m' # No Color
|
14
|
+
|
15
|
+
# Helper functions
|
16
|
+
log_info() {
|
17
|
+
echo -e "${BLUE}ℹ️ $1${NC}"
|
18
|
+
}
|
19
|
+
|
20
|
+
log_success() {
|
21
|
+
echo -e "${GREEN}✅ $1${NC}"
|
22
|
+
}
|
23
|
+
|
24
|
+
log_warning() {
|
25
|
+
echo -e "${YELLOW}⚠️ $1${NC}"
|
26
|
+
}
|
27
|
+
|
28
|
+
log_error() {
|
29
|
+
echo -e "${RED}❌ $1${NC}"
|
30
|
+
exit 1
|
31
|
+
}
|
32
|
+
|
33
|
+
# Validation functions
|
34
|
+
validate_git_status() {
|
35
|
+
log_info "Checking git status..."
|
36
|
+
if [ -n "$(git status --porcelain)" ]; then
|
37
|
+
log_error "Git working directory is not clean. Please commit all changes first."
|
38
|
+
fi
|
39
|
+
log_success "Git working directory is clean"
|
40
|
+
}
|
41
|
+
|
42
|
+
validate_git_branch() {
|
43
|
+
log_info "Checking git branch..."
|
44
|
+
current_branch=$(git rev-parse --abbrev-ref HEAD)
|
45
|
+
if [ "$current_branch" != "main" ]; then
|
46
|
+
log_warning "You're on branch '$current_branch', not 'main'"
|
47
|
+
read -p "Continue anyway? (y/N): " -n 1 -r
|
48
|
+
echo
|
49
|
+
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
50
|
+
log_error "Aborted by user"
|
51
|
+
fi
|
52
|
+
fi
|
53
|
+
log_success "Git branch check passed"
|
54
|
+
}
|
55
|
+
|
56
|
+
run_tests() {
|
57
|
+
log_info "Running tests..."
|
58
|
+
if ! bundle exec rspec; then
|
59
|
+
log_error "Tests failed"
|
60
|
+
fi
|
61
|
+
log_success "All tests passed"
|
62
|
+
}
|
63
|
+
|
64
|
+
run_linting() {
|
65
|
+
log_info "Running RuboCop..."
|
66
|
+
if ! bundle exec rubocop; then
|
67
|
+
log_error "RuboCop checks failed"
|
68
|
+
fi
|
69
|
+
log_success "RuboCop checks passed"
|
70
|
+
}
|
71
|
+
|
72
|
+
validate_all() {
|
73
|
+
echo "🔍 Running pre-publish validations..."
|
74
|
+
run_tests
|
75
|
+
run_linting
|
76
|
+
validate_git_status
|
77
|
+
validate_git_branch
|
78
|
+
log_success "All validations passed!"
|
79
|
+
}
|
80
|
+
|
81
|
+
build_gem() {
|
82
|
+
log_info "Building gem..."
|
83
|
+
if ! bundle exec rake build; then
|
84
|
+
log_error "Gem build failed"
|
85
|
+
fi
|
86
|
+
log_success "Gem built successfully!"
|
87
|
+
}
|
88
|
+
|
89
|
+
publish_gem() {
|
90
|
+
log_info "Publishing gem to RubyGems..."
|
91
|
+
if ! bundle exec rake release; then
|
92
|
+
log_error "Gem publish failed"
|
93
|
+
fi
|
94
|
+
log_success "Gem published successfully!"
|
95
|
+
}
|
96
|
+
|
97
|
+
show_info() {
|
98
|
+
echo "📋 Gem Information:"
|
99
|
+
echo " Name: human_number"
|
100
|
+
echo " Version: $(ruby -r ./lib/human_number/version -e 'puts HumanNumber::VERSION')"
|
101
|
+
echo " Built gems: $(ls pkg/*.gem 2>/dev/null || echo 'none')"
|
102
|
+
echo " Git branch: $(git rev-parse --abbrev-ref HEAD)"
|
103
|
+
echo " Git status: $([ -z "$(git status --porcelain)" ] && echo 'clean' || echo 'dirty')"
|
104
|
+
}
|
105
|
+
|
106
|
+
clean_artifacts() {
|
107
|
+
log_info "Cleaning build artifacts..."
|
108
|
+
rm -rf pkg/
|
109
|
+
log_success "Build artifacts cleaned!"
|
110
|
+
}
|
111
|
+
|
112
|
+
version_bump() {
|
113
|
+
echo "🔖 Version Bump"
|
114
|
+
current_version=$(ruby -r ./lib/human_number/version -e 'puts HumanNumber::VERSION')
|
115
|
+
echo "Current version: $current_version"
|
116
|
+
echo ""
|
117
|
+
|
118
|
+
# Get new version from user
|
119
|
+
read -p "Enter new version: " new_version
|
120
|
+
|
121
|
+
if [ -z "$new_version" ]; then
|
122
|
+
log_error "No version entered. Aborting."
|
123
|
+
fi
|
124
|
+
|
125
|
+
# Validate version format (basic semver check)
|
126
|
+
if ! echo "$new_version" | grep -qE "^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$"; then
|
127
|
+
log_error "Invalid version format. Please use semantic versioning (e.g., 1.2.3 or 1.2.3-alpha.1)"
|
128
|
+
fi
|
129
|
+
|
130
|
+
# Check if version is different
|
131
|
+
if [ "$new_version" = "$current_version" ]; then
|
132
|
+
log_error "New version is the same as current version. Aborting."
|
133
|
+
fi
|
134
|
+
|
135
|
+
# Update version file
|
136
|
+
version_file="lib/human_number/version.rb"
|
137
|
+
sed -i.bak "s/VERSION = \".*\"/VERSION = \"$new_version\"/" "$version_file" && rm "$version_file.bak"
|
138
|
+
log_success "Updated version to $new_version"
|
139
|
+
|
140
|
+
# Create commit
|
141
|
+
log_info "Creating version bump commit..."
|
142
|
+
git add "$version_file"
|
143
|
+
commit_message="chore: bump version to $new_version"
|
144
|
+
|
145
|
+
if git commit -m "$commit_message"; then
|
146
|
+
log_success "Version bump committed successfully!"
|
147
|
+
echo ""
|
148
|
+
echo "📋 Next steps:"
|
149
|
+
echo " - Review the changes: git show"
|
150
|
+
echo " - Build and publish: $0 publish"
|
151
|
+
echo " - Push to remote: git push && git push --tags"
|
152
|
+
else
|
153
|
+
log_error "Failed to create commit"
|
154
|
+
fi
|
155
|
+
}
|
156
|
+
|
157
|
+
# Main script logic
|
158
|
+
case "${1:-help}" in
|
159
|
+
"build")
|
160
|
+
validate_all
|
161
|
+
build_gem
|
162
|
+
;;
|
163
|
+
"publish")
|
164
|
+
validate_all
|
165
|
+
publish_gem
|
166
|
+
;;
|
167
|
+
"build-quick")
|
168
|
+
log_info "Quick building gem (no validations)..."
|
169
|
+
build_gem
|
170
|
+
;;
|
171
|
+
"info")
|
172
|
+
show_info
|
173
|
+
;;
|
174
|
+
"clean")
|
175
|
+
clean_artifacts
|
176
|
+
;;
|
177
|
+
"validate")
|
178
|
+
validate_all
|
179
|
+
;;
|
180
|
+
"bump")
|
181
|
+
version_bump
|
182
|
+
;;
|
183
|
+
"help"|*)
|
184
|
+
echo "HumanNumber Gem Build and Publish Script"
|
185
|
+
echo ""
|
186
|
+
echo "Usage: $0 [command]"
|
187
|
+
echo ""
|
188
|
+
echo "Commands:"
|
189
|
+
echo " build Build gem with full validations (tests, linting, git checks)"
|
190
|
+
echo " publish Publish gem to RubyGems with full validations"
|
191
|
+
echo " build-quick Build gem without validations (for testing)"
|
192
|
+
echo " validate Run all validations without building"
|
193
|
+
echo " info Show gem and repository information"
|
194
|
+
echo " clean Clean build artifacts"
|
195
|
+
echo " bump Bump version interactively and commit"
|
196
|
+
echo " help Show this help message"
|
197
|
+
echo ""
|
198
|
+
echo "Examples:"
|
199
|
+
echo " $0 build # Build gem with validations"
|
200
|
+
echo " $0 publish # Build and publish to RubyGems"
|
201
|
+
echo " $0 bump # Bump version interactively"
|
202
|
+
echo " $0 info # Show current gem info"
|
203
|
+
;;
|
204
|
+
esac
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: human_number
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- Ether Moon
|
8
8
|
bindir: exe
|
9
9
|
cert_chain: []
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
@@ -62,17 +62,19 @@ description: HumanNumber implements accurate number formatting based on internat
|
|
62
62
|
human-readable number formats with precise locale-specific decimal separators, thousand
|
63
63
|
separators, currency formats, and cultural number conventions.
|
64
64
|
email:
|
65
|
-
-
|
65
|
+
- ethermoon42@gmail.com
|
66
66
|
executables: []
|
67
67
|
extensions: []
|
68
68
|
extra_rdoc_files: []
|
69
69
|
files:
|
70
70
|
- ".rspec"
|
71
71
|
- ".rubocop.yml"
|
72
|
+
- BUILD.md
|
72
73
|
- CHANGELOG.md
|
73
74
|
- CLAUDE.md
|
74
75
|
- Gemfile
|
75
76
|
- LICENSE
|
77
|
+
- Makefile
|
76
78
|
- README.md
|
77
79
|
- Rakefile
|
78
80
|
- config/locales/bn.yml
|
@@ -89,6 +91,7 @@ files:
|
|
89
91
|
- config/locales/zh-CN.yml
|
90
92
|
- config/locales/zh-TW.yml
|
91
93
|
- config/locales/zh.yml
|
94
|
+
- human_number.gemspec
|
92
95
|
- lib/human_number.rb
|
93
96
|
- lib/human_number/formatters/currency.rb
|
94
97
|
- lib/human_number/formatters/number.rb
|
@@ -96,15 +99,15 @@ files:
|
|
96
99
|
- lib/human_number/rails/helpers.rb
|
97
100
|
- lib/human_number/railtie.rb
|
98
101
|
- lib/human_number/version.rb
|
99
|
-
|
102
|
+
- scripts/build_and_publish.sh
|
103
|
+
homepage: https://github.com/ether-moon/human_number
|
100
104
|
licenses:
|
101
105
|
- MIT
|
102
106
|
metadata:
|
103
107
|
allowed_push_host: https://rubygems.org
|
104
|
-
homepage_uri: https://github.com/
|
105
|
-
source_code_uri: https://github.com/
|
106
|
-
changelog_uri: https://github.com/
|
107
|
-
rubygems_mfa_required: 'true'
|
108
|
+
homepage_uri: https://github.com/ether-moon/human_number
|
109
|
+
source_code_uri: https://github.com/ether-moon/human_number
|
110
|
+
changelog_uri: https://github.com/ether-moon/human_number/blob/main/CHANGELOG.md
|
108
111
|
rdoc_options: []
|
109
112
|
require_paths:
|
110
113
|
- lib
|