UnityManager 0.0.1a1__tar.gz
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.
- unitymanager-0.0.1a1/.github/workflows/python-package.yml +40 -0
- unitymanager-0.0.1a1/.gitignore +224 -0
- unitymanager-0.0.1a1/LICENSE +21 -0
- unitymanager-0.0.1a1/PKG-INFO +249 -0
- unitymanager-0.0.1a1/README.md +200 -0
- unitymanager-0.0.1a1/UnityManager/__init__.py +41 -0
- unitymanager-0.0.1a1/UnityManager/_units.py +60 -0
- unitymanager-0.0.1a1/UnityManager/bytes.py +55 -0
- unitymanager-0.0.1a1/UnityManager/length.py +49 -0
- unitymanager-0.0.1a1/UnityManager/measure.py +49 -0
- unitymanager-0.0.1a1/games/__init__.py +11 -0
- unitymanager-0.0.1a1/games/_units.py +15 -0
- unitymanager-0.0.1a1/games/monetary.py +58 -0
- unitymanager-0.0.1a1/pyproject.toml +54 -0
- unitymanager-0.0.1a1/tests/__init__.py +0 -0
- unitymanager-0.0.1a1/tests/test_formatters.py +25 -0
- unitymanager-0.0.1a1/tests/test_money.py +14 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
|
|
2
|
+
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
|
|
3
|
+
|
|
4
|
+
name: Python package
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [ "master" ]
|
|
9
|
+
pull_request:
|
|
10
|
+
branches: [ "master" ]
|
|
11
|
+
|
|
12
|
+
jobs:
|
|
13
|
+
build:
|
|
14
|
+
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
strategy:
|
|
17
|
+
fail-fast: false
|
|
18
|
+
matrix:
|
|
19
|
+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
|
|
20
|
+
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
24
|
+
uses: actions/setup-python@v3
|
|
25
|
+
with:
|
|
26
|
+
python-version: ${{ matrix.python-version }}
|
|
27
|
+
- name: Install dependencies
|
|
28
|
+
run: |
|
|
29
|
+
python -m pip install --upgrade pip
|
|
30
|
+
python -m pip install flake8 pytest
|
|
31
|
+
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
|
|
32
|
+
- name: Lint with flake8
|
|
33
|
+
run: |
|
|
34
|
+
# stop the build if there are Python syntax errors or undefined names
|
|
35
|
+
flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
|
|
36
|
+
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
|
|
37
|
+
flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
|
|
38
|
+
- name: Test with pytest
|
|
39
|
+
run: |
|
|
40
|
+
pytest
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
.env-*
|
|
90
|
+
.env*
|
|
91
|
+
.venv-*
|
|
92
|
+
.venv*
|
|
93
|
+
|
|
94
|
+
# pipenv
|
|
95
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
96
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
97
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
98
|
+
# install all needed dependencies.
|
|
99
|
+
# Pipfile.lock
|
|
100
|
+
|
|
101
|
+
# UV
|
|
102
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
103
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
104
|
+
# commonly ignored for libraries.
|
|
105
|
+
# uv.lock
|
|
106
|
+
|
|
107
|
+
# poetry
|
|
108
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
109
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
110
|
+
# commonly ignored for libraries.
|
|
111
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
112
|
+
# poetry.lock
|
|
113
|
+
# poetry.toml
|
|
114
|
+
|
|
115
|
+
# pdm
|
|
116
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
117
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
118
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
119
|
+
# pdm.lock
|
|
120
|
+
# pdm.toml
|
|
121
|
+
.pdm-python
|
|
122
|
+
.pdm-build/
|
|
123
|
+
|
|
124
|
+
# pixi
|
|
125
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
126
|
+
# pixi.lock
|
|
127
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
128
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
129
|
+
.pixi
|
|
130
|
+
|
|
131
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
132
|
+
__pypackages__/
|
|
133
|
+
|
|
134
|
+
# Celery stuff
|
|
135
|
+
celerybeat-schedule
|
|
136
|
+
celerybeat.pid
|
|
137
|
+
|
|
138
|
+
# Redis
|
|
139
|
+
*.rdb
|
|
140
|
+
*.aof
|
|
141
|
+
*.pid
|
|
142
|
+
|
|
143
|
+
# RabbitMQ
|
|
144
|
+
mnesia/
|
|
145
|
+
rabbitmq/
|
|
146
|
+
rabbitmq-data/
|
|
147
|
+
|
|
148
|
+
# ActiveMQ
|
|
149
|
+
activemq-data/
|
|
150
|
+
|
|
151
|
+
# SageMath parsed files
|
|
152
|
+
*.sage.py
|
|
153
|
+
|
|
154
|
+
# Environments
|
|
155
|
+
.env
|
|
156
|
+
.envrc
|
|
157
|
+
.venv
|
|
158
|
+
env/
|
|
159
|
+
venv/
|
|
160
|
+
ENV/
|
|
161
|
+
env.bak/
|
|
162
|
+
venv.bak/
|
|
163
|
+
|
|
164
|
+
# Spyder project settings
|
|
165
|
+
.spyderproject
|
|
166
|
+
.spyproject
|
|
167
|
+
|
|
168
|
+
# Rope project settings
|
|
169
|
+
.ropeproject
|
|
170
|
+
|
|
171
|
+
# mkdocs documentation
|
|
172
|
+
/site
|
|
173
|
+
|
|
174
|
+
# mypy
|
|
175
|
+
.mypy_cache/
|
|
176
|
+
.dmypy.json
|
|
177
|
+
dmypy.json
|
|
178
|
+
|
|
179
|
+
# Pyre type checker
|
|
180
|
+
.pyre/
|
|
181
|
+
|
|
182
|
+
# pytype static type analyzer
|
|
183
|
+
.pytype/
|
|
184
|
+
|
|
185
|
+
# Cython debug symbols
|
|
186
|
+
cython_debug/
|
|
187
|
+
|
|
188
|
+
# PyCharm
|
|
189
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
190
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
191
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
192
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
193
|
+
# .idea/
|
|
194
|
+
|
|
195
|
+
# Abstra
|
|
196
|
+
# Abstra is an AI-powered process automation framework.
|
|
197
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
198
|
+
# Learn more at https://abstra.io/docs
|
|
199
|
+
.abstra/
|
|
200
|
+
|
|
201
|
+
# Visual Studio Code
|
|
202
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
203
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
204
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
205
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
206
|
+
# .vscode/
|
|
207
|
+
# Temporary file for partial code execution
|
|
208
|
+
tempCodeRunnerFile.py
|
|
209
|
+
|
|
210
|
+
# Ruff stuff:
|
|
211
|
+
.ruff_cache/
|
|
212
|
+
|
|
213
|
+
# PyPI configuration file
|
|
214
|
+
.pypirc
|
|
215
|
+
|
|
216
|
+
# Marimo
|
|
217
|
+
marimo/_static/
|
|
218
|
+
marimo/_lsp/
|
|
219
|
+
__marimo__/
|
|
220
|
+
|
|
221
|
+
# Streamlit
|
|
222
|
+
.streamlit/secrets.toml
|
|
223
|
+
|
|
224
|
+
.docs/*
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PlayGames-2020
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: UnityManager
|
|
3
|
+
Version: 0.0.1a1
|
|
4
|
+
Summary: Utilities for formatting common units.
|
|
5
|
+
Project-URL: Homepage, https://github.com/PlayGames-2020/UnityManager
|
|
6
|
+
Project-URL: Repository, https://github.com/PlayGames-2020/UnityManager.git
|
|
7
|
+
Project-URL: Documentation, https://github.com/PlayGames-2020/UnityManager/blob/main/README.md
|
|
8
|
+
Project-URL: Authors, https://github.com/PlayGames-2020
|
|
9
|
+
Author-email: PlayGames-2020 <playgames16.01.2020@gmail.com>
|
|
10
|
+
License: MIT License
|
|
11
|
+
|
|
12
|
+
Copyright (c) 2026 PlayGames-2020
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
15
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
16
|
+
in the Software without restriction, including without limitation the rights
|
|
17
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
18
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
19
|
+
furnished to do so, subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
License-File: LICENSE
|
|
32
|
+
Keywords: bytes,formatting,games,length,mass,money,units,volume
|
|
33
|
+
Classifier: Development Status :: 3 - Alpha
|
|
34
|
+
Classifier: Intended Audience :: Developers
|
|
35
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
36
|
+
Classifier: Operating System :: OS Independent
|
|
37
|
+
Classifier: Programming Language :: Python :: 3
|
|
38
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
41
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
43
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
44
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
45
|
+
Requires-Python: >=3.9
|
|
46
|
+
Provides-Extra: dev
|
|
47
|
+
Requires-Dist: pytest>=8.4.2; extra == 'dev'
|
|
48
|
+
Description-Content-Type: text/markdown
|
|
49
|
+
|
|
50
|
+
# UnityManager
|
|
51
|
+
|
|
52
|
+
Utilities for formatting common units without runtime dependencies.
|
|
53
|
+
|
|
54
|
+
> **Status:** alpha (`0.0.1-alpha`). The API is still subject to change.
|
|
55
|
+
|
|
56
|
+
## Requirements
|
|
57
|
+
|
|
58
|
+
- Python **3.9 or later**
|
|
59
|
+
- No runtime dependencies
|
|
60
|
+
|
|
61
|
+
## Installation
|
|
62
|
+
|
|
63
|
+
### GitHub
|
|
64
|
+
|
|
65
|
+
``` bash
|
|
66
|
+
pip install git+https://github.com/PlayGames-2020/UnityManager.git
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
or
|
|
70
|
+
|
|
71
|
+
``` bash
|
|
72
|
+
pip install git+https://github.com/PlayGames-2020/UnityManager.git@master
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### PyPI
|
|
76
|
+
|
|
77
|
+
``` bash
|
|
78
|
+
pip install UnityManager
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
To install the development dependencies:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install "unitymanager[dev]"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Quick start
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
from UnityManager import (
|
|
91
|
+
format_bytes,
|
|
92
|
+
format_capacity,
|
|
93
|
+
format_length,
|
|
94
|
+
format_mass,
|
|
95
|
+
format_volume,
|
|
96
|
+
)
|
|
97
|
+
from games import format_money
|
|
98
|
+
|
|
99
|
+
print(format_bytes(1024)) # 1.00 KB
|
|
100
|
+
print(format_length(1500)) # 1.50 km
|
|
101
|
+
print(format_capacity(2.5)) # 2.50 l
|
|
102
|
+
print(format_mass(2500)) # 2.50 kg
|
|
103
|
+
print(format_volume(1)) # 1.00 m³
|
|
104
|
+
print(format_money(1500)) # 1.50 K
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Each formatter accepts a text mode. The default mode `"s"` displays the unit symbol. Use `"n"` for the unit name, combine modes such as `"ns"`, or add `"upper"`, `"lower"`, `"title"` and `"v"` as needed.
|
|
108
|
+
|
|
109
|
+
## Games package
|
|
110
|
+
|
|
111
|
+
The optional game-oriented API is available from the `games` package:
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from games import format_money
|
|
115
|
+
|
|
116
|
+
format_money(999) # "999.00"
|
|
117
|
+
format_money(1_500) # "1.50 K"
|
|
118
|
+
format_money(2_000_000) # "2.00 M"
|
|
119
|
+
format_money(1_500, "n") # "1.50 thousand"
|
|
120
|
+
format_money(1_500, "ns") # "1.50 thousand K"
|
|
121
|
+
format_money(1_500, "v") # "1.50 (1500 gold)"
|
|
122
|
+
format_money(1_500, "nsupper") # "1.50 THOUSAND K"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### `format_money(value, mode="s")`
|
|
126
|
+
|
|
127
|
+
Formats a numeric amount in the base game currency, assumed to be gold. Values
|
|
128
|
+
are rounded to two decimal places and use the largest applicable suffix:
|
|
129
|
+
|
|
130
|
+
| Range | Name | Symbol |
|
|
131
|
+
| ---: | --- | :--- |
|
|
132
|
+
| 1,000 | thousand | `K` |
|
|
133
|
+
| 1,000,000 | million | `M` |
|
|
134
|
+
| 1,000,000,000 | billion | `B` |
|
|
135
|
+
| 1,000,000,000,000 | trillion | `T` |
|
|
136
|
+
| 1,000,000,000,000,000 | quadrillion | `Q` |
|
|
137
|
+
|
|
138
|
+
Supported mode components:
|
|
139
|
+
|
|
140
|
+
- `s`: append the compact symbol.
|
|
141
|
+
- `n`: append the full unit name.
|
|
142
|
+
- `d`: append the unit description (reserved for future descriptions).
|
|
143
|
+
- `v`: append the original value as gold.
|
|
144
|
+
- `upper`, `lower`, `title`: change the case of the appended text.
|
|
145
|
+
|
|
146
|
+
Modes can be combined, for example `"nsupper"`. Non-numeric values are returned
|
|
147
|
+
as text instead of raising an exception.
|
|
148
|
+
|
|
149
|
+
The formatter is intentionally currency-neutral in its arithmetic: it only
|
|
150
|
+
compacts the amount. The displayed base-currency label is currently `gold` and
|
|
151
|
+
can be extended in a future API version if games need coins, gems, credits or
|
|
152
|
+
other currencies.
|
|
153
|
+
|
|
154
|
+
## UnityManager API
|
|
155
|
+
|
|
156
|
+
All public formatters return a string rounded to two decimal places. Numeric
|
|
157
|
+
inputs use the base unit described below; non-numeric inputs are returned using
|
|
158
|
+
`str(value)`.
|
|
159
|
+
|
|
160
|
+
### `format_bytes(value, mode="s")`
|
|
161
|
+
|
|
162
|
+
Formats binary quantities using powers of 1024. The base input is bytes and the
|
|
163
|
+
available symbols include `B`, `KB`, `MB`, `GB`, `TB`, `PB`, `EB` and `ZB`.
|
|
164
|
+
The mode `"byte"` adds the original value in bytes, while `"bit"` adds its
|
|
165
|
+
corresponding bit value.
|
|
166
|
+
|
|
167
|
+
```python
|
|
168
|
+
format_bytes(1024) # "1.00 KB"
|
|
169
|
+
format_bytes(1024, "n") # "1.00 kilobyte"
|
|
170
|
+
format_bytes(2048, "sbyte") # "2.00 KB (2048 bytes)"
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### `format_length(value, mode="s")`
|
|
174
|
+
|
|
175
|
+
Formats a value expressed in metres using metric units from millimetres to
|
|
176
|
+
kilometres.
|
|
177
|
+
|
|
178
|
+
```python
|
|
179
|
+
format_length(1_500) # "1.50 km"
|
|
180
|
+
format_length(1, "n") # "1.00 metre"
|
|
181
|
+
format_length(1, "nv") # "1.00 metre (1.0 m)"
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### `format_capacity(value, mode="s")`
|
|
185
|
+
|
|
186
|
+
Formats a value expressed in litres, from millilitres to kilolitres.
|
|
187
|
+
|
|
188
|
+
```python
|
|
189
|
+
format_capacity(2.5) # "2.50 l"
|
|
190
|
+
format_capacity(1_000, "n") # "1.00 kilolitre"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `format_mass(value, mode="s")`
|
|
194
|
+
|
|
195
|
+
Formats a value expressed in grams, from milligrams to kilograms.
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
format_mass(2_500) # "2.50 kg"
|
|
199
|
+
format_mass(1, "n") # "1.00 gram"
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### `format_volume(value, mode="s")`
|
|
203
|
+
|
|
204
|
+
Formats a value expressed in cubic metres, from cubic millimetres to cubic
|
|
205
|
+
kilometres.
|
|
206
|
+
|
|
207
|
+
```python
|
|
208
|
+
format_volume(1) # "1.00 m³"
|
|
209
|
+
format_volume(0.000001) # "1.00 cm³"
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Text modes
|
|
213
|
+
|
|
214
|
+
The general formatters support these mode components:
|
|
215
|
+
|
|
216
|
+
- `s`: append the unit symbol (the default).
|
|
217
|
+
- `n`: append the full unit name.
|
|
218
|
+
- `d`: append the unit description, when available.
|
|
219
|
+
- `v`: append the original value in the base unit.
|
|
220
|
+
- `upper`, `lower`, `title`: change the case of the appended text.
|
|
221
|
+
|
|
222
|
+
Modes can be combined as a string, for example `"nsupper"`.
|
|
223
|
+
|
|
224
|
+
## Supported formatters
|
|
225
|
+
|
|
226
|
+
- `format_bytes`: binary byte units (`B`, `KB`, `MB`, and so on).
|
|
227
|
+
- `format_length`: metric lengths from millimetres to kilometres.
|
|
228
|
+
- `format_capacity`: metric capacities from millilitres to kilolitres.
|
|
229
|
+
- `format_mass`: metric masses from milligrams to kilograms.
|
|
230
|
+
- `format_volume`: cubic metric units from cubic millimetres to cubic kilometres.
|
|
231
|
+
- `games.format_money`: compact game currency values (`K`, `M`, `B`, `T`, `Q`).
|
|
232
|
+
|
|
233
|
+
## Development and testing
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
git clone https://github.com/PlayGames-2020/UnityManager.git
|
|
237
|
+
cd UnityManager
|
|
238
|
+
pip install -e ".[dev]"
|
|
239
|
+
python -m pytest
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
Distributed under the [MIT License](LICENSE).
|
|
245
|
+
|
|
246
|
+
## Links
|
|
247
|
+
|
|
248
|
+
- [Repository](https://github.com/PlayGames-2020/UnityManager)
|
|
249
|
+
- [Documentation](https://github.com/PlayGames-2020/UnityManager/blob/main/README.md)
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# UnityManager
|
|
2
|
+
|
|
3
|
+
Utilities for formatting common units without runtime dependencies.
|
|
4
|
+
|
|
5
|
+
> **Status:** alpha (`0.0.1-alpha`). The API is still subject to change.
|
|
6
|
+
|
|
7
|
+
## Requirements
|
|
8
|
+
|
|
9
|
+
- Python **3.9 or later**
|
|
10
|
+
- No runtime dependencies
|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
### GitHub
|
|
15
|
+
|
|
16
|
+
``` bash
|
|
17
|
+
pip install git+https://github.com/PlayGames-2020/UnityManager.git
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
or
|
|
21
|
+
|
|
22
|
+
``` bash
|
|
23
|
+
pip install git+https://github.com/PlayGames-2020/UnityManager.git@master
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### PyPI
|
|
27
|
+
|
|
28
|
+
``` bash
|
|
29
|
+
pip install UnityManager
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
To install the development dependencies:
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install "unitymanager[dev]"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from UnityManager import (
|
|
42
|
+
format_bytes,
|
|
43
|
+
format_capacity,
|
|
44
|
+
format_length,
|
|
45
|
+
format_mass,
|
|
46
|
+
format_volume,
|
|
47
|
+
)
|
|
48
|
+
from games import format_money
|
|
49
|
+
|
|
50
|
+
print(format_bytes(1024)) # 1.00 KB
|
|
51
|
+
print(format_length(1500)) # 1.50 km
|
|
52
|
+
print(format_capacity(2.5)) # 2.50 l
|
|
53
|
+
print(format_mass(2500)) # 2.50 kg
|
|
54
|
+
print(format_volume(1)) # 1.00 m³
|
|
55
|
+
print(format_money(1500)) # 1.50 K
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Each formatter accepts a text mode. The default mode `"s"` displays the unit symbol. Use `"n"` for the unit name, combine modes such as `"ns"`, or add `"upper"`, `"lower"`, `"title"` and `"v"` as needed.
|
|
59
|
+
|
|
60
|
+
## Games package
|
|
61
|
+
|
|
62
|
+
The optional game-oriented API is available from the `games` package:
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from games import format_money
|
|
66
|
+
|
|
67
|
+
format_money(999) # "999.00"
|
|
68
|
+
format_money(1_500) # "1.50 K"
|
|
69
|
+
format_money(2_000_000) # "2.00 M"
|
|
70
|
+
format_money(1_500, "n") # "1.50 thousand"
|
|
71
|
+
format_money(1_500, "ns") # "1.50 thousand K"
|
|
72
|
+
format_money(1_500, "v") # "1.50 (1500 gold)"
|
|
73
|
+
format_money(1_500, "nsupper") # "1.50 THOUSAND K"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### `format_money(value, mode="s")`
|
|
77
|
+
|
|
78
|
+
Formats a numeric amount in the base game currency, assumed to be gold. Values
|
|
79
|
+
are rounded to two decimal places and use the largest applicable suffix:
|
|
80
|
+
|
|
81
|
+
| Range | Name | Symbol |
|
|
82
|
+
| ---: | --- | :--- |
|
|
83
|
+
| 1,000 | thousand | `K` |
|
|
84
|
+
| 1,000,000 | million | `M` |
|
|
85
|
+
| 1,000,000,000 | billion | `B` |
|
|
86
|
+
| 1,000,000,000,000 | trillion | `T` |
|
|
87
|
+
| 1,000,000,000,000,000 | quadrillion | `Q` |
|
|
88
|
+
|
|
89
|
+
Supported mode components:
|
|
90
|
+
|
|
91
|
+
- `s`: append the compact symbol.
|
|
92
|
+
- `n`: append the full unit name.
|
|
93
|
+
- `d`: append the unit description (reserved for future descriptions).
|
|
94
|
+
- `v`: append the original value as gold.
|
|
95
|
+
- `upper`, `lower`, `title`: change the case of the appended text.
|
|
96
|
+
|
|
97
|
+
Modes can be combined, for example `"nsupper"`. Non-numeric values are returned
|
|
98
|
+
as text instead of raising an exception.
|
|
99
|
+
|
|
100
|
+
The formatter is intentionally currency-neutral in its arithmetic: it only
|
|
101
|
+
compacts the amount. The displayed base-currency label is currently `gold` and
|
|
102
|
+
can be extended in a future API version if games need coins, gems, credits or
|
|
103
|
+
other currencies.
|
|
104
|
+
|
|
105
|
+
## UnityManager API
|
|
106
|
+
|
|
107
|
+
All public formatters return a string rounded to two decimal places. Numeric
|
|
108
|
+
inputs use the base unit described below; non-numeric inputs are returned using
|
|
109
|
+
`str(value)`.
|
|
110
|
+
|
|
111
|
+
### `format_bytes(value, mode="s")`
|
|
112
|
+
|
|
113
|
+
Formats binary quantities using powers of 1024. The base input is bytes and the
|
|
114
|
+
available symbols include `B`, `KB`, `MB`, `GB`, `TB`, `PB`, `EB` and `ZB`.
|
|
115
|
+
The mode `"byte"` adds the original value in bytes, while `"bit"` adds its
|
|
116
|
+
corresponding bit value.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
format_bytes(1024) # "1.00 KB"
|
|
120
|
+
format_bytes(1024, "n") # "1.00 kilobyte"
|
|
121
|
+
format_bytes(2048, "sbyte") # "2.00 KB (2048 bytes)"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `format_length(value, mode="s")`
|
|
125
|
+
|
|
126
|
+
Formats a value expressed in metres using metric units from millimetres to
|
|
127
|
+
kilometres.
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
format_length(1_500) # "1.50 km"
|
|
131
|
+
format_length(1, "n") # "1.00 metre"
|
|
132
|
+
format_length(1, "nv") # "1.00 metre (1.0 m)"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### `format_capacity(value, mode="s")`
|
|
136
|
+
|
|
137
|
+
Formats a value expressed in litres, from millilitres to kilolitres.
|
|
138
|
+
|
|
139
|
+
```python
|
|
140
|
+
format_capacity(2.5) # "2.50 l"
|
|
141
|
+
format_capacity(1_000, "n") # "1.00 kilolitre"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### `format_mass(value, mode="s")`
|
|
145
|
+
|
|
146
|
+
Formats a value expressed in grams, from milligrams to kilograms.
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
format_mass(2_500) # "2.50 kg"
|
|
150
|
+
format_mass(1, "n") # "1.00 gram"
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `format_volume(value, mode="s")`
|
|
154
|
+
|
|
155
|
+
Formats a value expressed in cubic metres, from cubic millimetres to cubic
|
|
156
|
+
kilometres.
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
format_volume(1) # "1.00 m³"
|
|
160
|
+
format_volume(0.000001) # "1.00 cm³"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Text modes
|
|
164
|
+
|
|
165
|
+
The general formatters support these mode components:
|
|
166
|
+
|
|
167
|
+
- `s`: append the unit symbol (the default).
|
|
168
|
+
- `n`: append the full unit name.
|
|
169
|
+
- `d`: append the unit description, when available.
|
|
170
|
+
- `v`: append the original value in the base unit.
|
|
171
|
+
- `upper`, `lower`, `title`: change the case of the appended text.
|
|
172
|
+
|
|
173
|
+
Modes can be combined as a string, for example `"nsupper"`.
|
|
174
|
+
|
|
175
|
+
## Supported formatters
|
|
176
|
+
|
|
177
|
+
- `format_bytes`: binary byte units (`B`, `KB`, `MB`, and so on).
|
|
178
|
+
- `format_length`: metric lengths from millimetres to kilometres.
|
|
179
|
+
- `format_capacity`: metric capacities from millilitres to kilolitres.
|
|
180
|
+
- `format_mass`: metric masses from milligrams to kilograms.
|
|
181
|
+
- `format_volume`: cubic metric units from cubic millimetres to cubic kilometres.
|
|
182
|
+
- `games.format_money`: compact game currency values (`K`, `M`, `B`, `T`, `Q`).
|
|
183
|
+
|
|
184
|
+
## Development and testing
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git clone https://github.com/PlayGames-2020/UnityManager.git
|
|
188
|
+
cd UnityManager
|
|
189
|
+
pip install -e ".[dev]"
|
|
190
|
+
python -m pytest
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## License
|
|
194
|
+
|
|
195
|
+
Distributed under the [MIT License](LICENSE).
|
|
196
|
+
|
|
197
|
+
## Links
|
|
198
|
+
|
|
199
|
+
- [Repository](https://github.com/PlayGames-2020/UnityManager)
|
|
200
|
+
- [Documentation](https://github.com/PlayGames-2020/UnityManager/blob/main/README.md)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Utilities for formatting common units.
|
|
2
|
+
|
|
3
|
+
The package exposes byte, length, capacity, mass and volume formatters at the
|
|
4
|
+
package level for convenient imports.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
import sys
|
|
11
|
+
|
|
12
|
+
from .bytes import format_bytes
|
|
13
|
+
from .length import format_length
|
|
14
|
+
from .measure import format_capacity, format_mass, format_volume
|
|
15
|
+
|
|
16
|
+
__version__ = "0.0.1-alpha1"
|
|
17
|
+
__author__ = "PlayGames-2020"
|
|
18
|
+
__summary__ = "Utilities for formatting common units."
|
|
19
|
+
|
|
20
|
+
_match = re.match(r"(\d+)\.(\d+)\.(\d+)(?:-(.+))?", __version__)
|
|
21
|
+
|
|
22
|
+
if _match: major, minor, patch, tag = _match.groups()
|
|
23
|
+
else: major, minor, patch, tag = 0, 0, 0, "unknown"
|
|
24
|
+
|
|
25
|
+
VERSION_INFO = (int(major), int(minor), int(patch), tag or "release")
|
|
26
|
+
|
|
27
|
+
NAME = __name__
|
|
28
|
+
|
|
29
|
+
_REQUIRED_PY = (3, 9)
|
|
30
|
+
if sys.version_info < (3, 9):
|
|
31
|
+
runtime = ".".join(str(v) for v in (_REQUIRED_PY))
|
|
32
|
+
info = f"{__name__} requires Python {runtime} or higher" \
|
|
33
|
+
f"(current: {sys.version.split()[0]})"
|
|
34
|
+
print(info)
|
|
35
|
+
raise RuntimeError(info)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
"__version__", "VERSION_INFO", "NAME",
|
|
40
|
+
"format_volume", "format_mass", "format_capacity", "format_length", "format_bytes"
|
|
41
|
+
]
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Literal, Tuple, Union
|
|
4
|
+
|
|
5
|
+
_Mode = Union[str, Literal["n", "s", "d", "v", "lower", "upper", "title"]]
|
|
6
|
+
_TextMode = _Mode
|
|
7
|
+
_MeasureTextMode = _TextMode
|
|
8
|
+
_BytesTextMode = Union[str, Literal["s", "n", "d", "upper", "lower", "title", "byte", "bit"]]
|
|
9
|
+
|
|
10
|
+
_BYTES_TABLE: List[Tuple[str, str, Union[int, float], str]] = [
|
|
11
|
+
("zettabyte", "ZB", 1024**7, ""),
|
|
12
|
+
("exabyte", "EB", 1024**6, ""),
|
|
13
|
+
("petabyte", "PB", 1024**5, ""),
|
|
14
|
+
("terabyte", "TB", 1024**4, ""),
|
|
15
|
+
("gigabyte", "GB", 1024**3, ""),
|
|
16
|
+
("megabyte", "MB", 1024**2, ""),
|
|
17
|
+
("kilobyte", "KB", 1024, ""),
|
|
18
|
+
("byte", "B", 1, ""),
|
|
19
|
+
("bit", "b", 1 / 8, ""),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
_LENGTH_TABLE: List[Tuple[str, str, float, str]] = [
|
|
23
|
+
("kilometre", "km", 1000.0, ""),
|
|
24
|
+
("hectometre", "hm", 100.0, ""),
|
|
25
|
+
("decametre", "dam", 10.0, ""),
|
|
26
|
+
("metre", "m", 1.0, ""),
|
|
27
|
+
("decimetre", "dm", 0.1, ""),
|
|
28
|
+
("centimetre", "cm", 0.01, ""),
|
|
29
|
+
("millimetre", "mm", 0.001, ""),
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
_CAPACITY_TABLE: List[Tuple[str, str, float, str]] = [
|
|
33
|
+
("kilolitre", "kl", 1000.0, ""),
|
|
34
|
+
("hectolitre", "hl", 100.0, ""),
|
|
35
|
+
("decalitre", "dal", 10.0, ""),
|
|
36
|
+
("litre", "l", 1.0, ""),
|
|
37
|
+
("decilitre", "dl", 0.1, ""),
|
|
38
|
+
("centilitre", "cl", 0.01, ""),
|
|
39
|
+
("millilitre", "ml", 0.001, ""),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
_MASS_TABLE: List[Tuple[str, str, float, str]] = [
|
|
43
|
+
("kilogram", "kg", 1000.0, ""),
|
|
44
|
+
("hectogram", "hg", 100.0, ""),
|
|
45
|
+
("decagram", "dag", 10.0, ""),
|
|
46
|
+
("gram", "g", 1.0, ""),
|
|
47
|
+
("decigram", "dg", 0.1, ""),
|
|
48
|
+
("centigram", "cg", 0.01, ""),
|
|
49
|
+
("milligram", "mg", 0.001, ""),
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
_VOLUME_TABLE: List[Tuple[str, str, Union[int, float], str]] = [
|
|
53
|
+
("kilometre cubic", "km³", 1_000_000_000.0, ""),
|
|
54
|
+
("hectometre cubic", "hm³", 1_000_000.0, ""),
|
|
55
|
+
("decametre cubic", "dam³", 1_000.0, ""),
|
|
56
|
+
("metre cubic", "m³", 1.0, ""),
|
|
57
|
+
("decimetre cubic", "dm³", 0.001, ""),
|
|
58
|
+
("centimetre cubic", "cm³", 0.000_001, ""),
|
|
59
|
+
("millimetre cubic", "mm³", 0.000_000_001, ""),
|
|
60
|
+
]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
""""""
|
|
2
|
+
|
|
3
|
+
from typing import Union, Any, List
|
|
4
|
+
from decimal import Decimal
|
|
5
|
+
|
|
6
|
+
from ._units import _BYTES_TABLE, _BytesTextMode
|
|
7
|
+
|
|
8
|
+
def format_bytes(n: Union[int, Any], mode: _BytesTextMode = "s") -> str:
|
|
9
|
+
"""Format a byte value using binary units.
|
|
10
|
+
|
|
11
|
+
Values are divided by powers of 1024 and rounded to two decimals. The
|
|
12
|
+
default mode, ``"s"``, displays the unit symbol. ``"n"`` displays the
|
|
13
|
+
unit name, ``"d"`` its description, and ``"byte"`` or ``"bit"`` adds a
|
|
14
|
+
conversion note. Case modifiers can be combined with these modes.
|
|
15
|
+
|
|
16
|
+
Non-numeric values are returned as text.
|
|
17
|
+
|
|
18
|
+
Examples:
|
|
19
|
+
>>> format_bytes(1024)
|
|
20
|
+
'1.00 KB'
|
|
21
|
+
>>> format_bytes(1024, "n")
|
|
22
|
+
'1.00 kilobyte'
|
|
23
|
+
"""
|
|
24
|
+
if not isinstance(n, (int, float, Decimal)): return str(n)
|
|
25
|
+
|
|
26
|
+
amount = Decimal(str(n))
|
|
27
|
+
abs_n = abs(amount)
|
|
28
|
+
unit_name: str
|
|
29
|
+
unit_symbol: str
|
|
30
|
+
unit_description: str
|
|
31
|
+
unit_name, unit_symbol, unit_description = "bit", "b", ""
|
|
32
|
+
for name, symbol, limit, description in _BYTES_TABLE:
|
|
33
|
+
_limit = Decimal(str(limit))
|
|
34
|
+
if abs_n >= limit:
|
|
35
|
+
amount = amount / _limit
|
|
36
|
+
unit_name, unit_symbol, unit_description = name, symbol, description
|
|
37
|
+
break
|
|
38
|
+
|
|
39
|
+
parts: List[str] = []
|
|
40
|
+
|
|
41
|
+
if "s" in mode: parts.append(unit_symbol)
|
|
42
|
+
if "n" in mode: parts.append(unit_name)
|
|
43
|
+
if "d" in mode: parts.append(unit_description)
|
|
44
|
+
if "upper" in mode: parts = [p.upper() for p in parts]
|
|
45
|
+
if "lower" in mode: parts = [p.lower() for p in parts]
|
|
46
|
+
if "title" in mode: parts = [p.title() for p in parts]
|
|
47
|
+
if "byte" in mode: parts.append(f"({n} bytes)")
|
|
48
|
+
if "bit" in mode: parts.append(f"({n / 8} bits)")
|
|
49
|
+
|
|
50
|
+
suffix = " ".join(parts)
|
|
51
|
+
return f"{amount:.2f}{f' {suffix}' if suffix else ''}"
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
"format_bytes"
|
|
55
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Format metric length quantities for display."""
|
|
2
|
+
from typing import Any, List, Literal, Union
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from ._units import _LENGTH_TABLE, _TextMode
|
|
6
|
+
_LengthTextMode = _TextMode
|
|
7
|
+
|
|
8
|
+
def format_length(n: Union[int, Any], mode: _LengthTextMode = "s") -> str:
|
|
9
|
+
"""Format a length in metric units, from millimetres to kilometres.
|
|
10
|
+
|
|
11
|
+
The input is interpreted as metres. The largest suitable unit is selected
|
|
12
|
+
automatically and the result is rounded to two decimal places. Use
|
|
13
|
+
``"s"`` for the symbol, ``"n"`` for the name, ``"v"`` for the original
|
|
14
|
+
value, and combine these with ``"upper"``, ``"lower"`` or ``"title"``.
|
|
15
|
+
|
|
16
|
+
Non-numeric values are returned as text.
|
|
17
|
+
"""
|
|
18
|
+
if not isinstance(n, (int, float, Decimal)): return str(n)
|
|
19
|
+
|
|
20
|
+
abs_n = abs(Decimal(str(n)))
|
|
21
|
+
amount = Decimal(str(n))
|
|
22
|
+
name, symbol, description = "metre", "m", ""
|
|
23
|
+
|
|
24
|
+
for name_item, symbol_item, limit, desc_item in _LENGTH_TABLE:
|
|
25
|
+
if limit >= 1 and abs_n >= Decimal(str(limit)):
|
|
26
|
+
amount = Decimal(str(n)) / Decimal(str(limit))
|
|
27
|
+
name, symbol, description = name_item, symbol_item, desc_item
|
|
28
|
+
break
|
|
29
|
+
elif limit < 1 and abs_n >= Decimal(str(limit)):
|
|
30
|
+
amount = Decimal(str(n)) / Decimal(str(limit))
|
|
31
|
+
name, symbol, description = name_item, symbol_item, desc_item
|
|
32
|
+
break
|
|
33
|
+
|
|
34
|
+
parts: List[str] = []
|
|
35
|
+
if "n" in mode: parts.append(name)
|
|
36
|
+
if "s" in mode: parts.append(symbol)
|
|
37
|
+
if "d" in mode: parts.append(description)
|
|
38
|
+
if "v" in mode: parts.append(f"({float(n)} m)")
|
|
39
|
+
if "lower" in mode: parts = [p.lower() for p in parts]
|
|
40
|
+
if "upper" in mode: parts = [p.upper() for p in parts]
|
|
41
|
+
if "title" in mode: parts = [p.title() for p in parts]
|
|
42
|
+
|
|
43
|
+
suffix = " ".join(parts).strip()
|
|
44
|
+
return f"{amount:.2f}{f' {suffix}' if suffix else ''}"
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"format_length"
|
|
49
|
+
]
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Format capacity, mass and volume quantities for display."""
|
|
2
|
+
from typing import Any, List, Literal, Union, Tuple
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from ._units import (
|
|
6
|
+
_CAPACITY_TABLE, _MASS_TABLE, _VOLUME_TABLE, _MeasureTextMode
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
def _generic_formatter(n: Union[int, Any], table: List[Tuple], default_unit: Tuple, base_symbol: str, mode: _MeasureTextMode = "s") -> str:
|
|
10
|
+
"""Format a numeric measurement using the supplied unit table."""
|
|
11
|
+
if not isinstance(n, (int, float, Decimal)): return str(n)
|
|
12
|
+
|
|
13
|
+
abs_n = abs(Decimal(str(n)))
|
|
14
|
+
amount = Decimal(str(n))
|
|
15
|
+
name, symbol, description = default_unit
|
|
16
|
+
|
|
17
|
+
for name_item, symbol_item, limit, desc_item in table:
|
|
18
|
+
if abs_n >= Decimal(str(limit)):
|
|
19
|
+
amount = Decimal(str(n)) / Decimal(str(limit))
|
|
20
|
+
name, symbol, description = name_item, symbol_item, desc_item
|
|
21
|
+
break
|
|
22
|
+
|
|
23
|
+
parts: List[str] = []
|
|
24
|
+
if "n" in mode: parts.append(name)
|
|
25
|
+
if "s" in mode: parts.append(symbol)
|
|
26
|
+
if "d" in mode: parts.append(description)
|
|
27
|
+
if "v" in mode: parts.append(f"({float(n)} {base_symbol})")
|
|
28
|
+
if "lower" in mode: parts = [p.lower() for p in parts]
|
|
29
|
+
if "upper" in mode: parts = [p.upper() for p in parts]
|
|
30
|
+
if "title" in mode: parts = [p.title() for p in parts]
|
|
31
|
+
|
|
32
|
+
suffix = " ".join(parts).strip()
|
|
33
|
+
return f"{amount:.2f}{f' {suffix}' if suffix else ''}"
|
|
34
|
+
|
|
35
|
+
def format_capacity(n: Union[int, Any], mode: _MeasureTextMode = "s") -> str:
|
|
36
|
+
"""Format a capacity expressed in litres."""
|
|
37
|
+
return _generic_formatter(n, _CAPACITY_TABLE, ("litre", "l", ""), "l", mode)
|
|
38
|
+
|
|
39
|
+
def format_mass(n: Union[int, Any], mode: _MeasureTextMode = "s") -> str:
|
|
40
|
+
"""Format a mass expressed in grams."""
|
|
41
|
+
return _generic_formatter(n, _MASS_TABLE, ("gram", "g", ""), "g", mode)
|
|
42
|
+
|
|
43
|
+
def format_volume(n: Union[int, Any], mode: _MeasureTextMode = "s") -> str:
|
|
44
|
+
"""Format a volume expressed in cubic metres."""
|
|
45
|
+
return _generic_formatter(n, _VOLUME_TABLE, ("metre cubic", "m³", ""), "m³", mode)
|
|
46
|
+
|
|
47
|
+
__all__ = [
|
|
48
|
+
"format_capacity", "format_mass", "format_volume"
|
|
49
|
+
]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Game-specific formatting helpers.
|
|
2
|
+
|
|
3
|
+
The public API currently provides :func:`format_money`, which formats large
|
|
4
|
+
in-game currency values using compact suffixes such as ``K`` and ``M``.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .monetary import format_money
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"format_money"
|
|
11
|
+
]
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import List, Tuple, Union
|
|
4
|
+
from UnityManager._units import _TextMode
|
|
5
|
+
|
|
6
|
+
_MoneyTextMode = _TextMode
|
|
7
|
+
|
|
8
|
+
_MONEY_TABLE: List[Tuple[str, str, Union[int, float], str]] = [
|
|
9
|
+
("quadrillion", "Q", 10 ** 15, ""),
|
|
10
|
+
("trillion", "T", 10 ** 12, ""),
|
|
11
|
+
("billion", "B", 10 ** 9, ""),
|
|
12
|
+
("million", "M", 10 ** 6, ""),
|
|
13
|
+
("thousand", "K", 10 ** 3, ""),
|
|
14
|
+
("", "", 1, ""),
|
|
15
|
+
]
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Formatting helpers for in-game monetary values."""
|
|
2
|
+
from typing import Any, List, Literal, Union
|
|
3
|
+
from decimal import Decimal
|
|
4
|
+
|
|
5
|
+
from ._units import _MONEY_TABLE, _MoneyTextMode
|
|
6
|
+
|
|
7
|
+
def format_money(n: Union[int, Any], mode: _MoneyTextMode = "s") -> str:
|
|
8
|
+
"""Format an in-game currency amount with a compact suffix.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
n: Numeric amount in the base currency (gold by default). Non-numeric
|
|
12
|
+
values are returned unchanged as text.
|
|
13
|
+
mode: Controls the text appended to the formatted number. ``"s"``
|
|
14
|
+
adds the suffix symbol, ``"n"`` adds its name, ``"d"`` adds the
|
|
15
|
+
description, and ``"v"`` shows the original amount as gold.
|
|
16
|
+
Case modifiers ``"upper"``, ``"lower"`` and ``"title"`` can be
|
|
17
|
+
combined with the other modes.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
A string containing the amount rounded to two decimal places and the
|
|
21
|
+
requested currency text.
|
|
22
|
+
|
|
23
|
+
Examples:
|
|
24
|
+
>>> format_money(1500)
|
|
25
|
+
'1.50 K'
|
|
26
|
+
>>> format_money(2_000_000, "n")
|
|
27
|
+
'2.00 million'
|
|
28
|
+
>>> format_money(1500, "nsupper")
|
|
29
|
+
'1.50 THOUSAND K'
|
|
30
|
+
"""
|
|
31
|
+
if not isinstance(n, (int, float, Decimal)): return str(n)
|
|
32
|
+
|
|
33
|
+
abs_n = abs(Decimal(str(n)))
|
|
34
|
+
amount = Decimal(str(n))
|
|
35
|
+
name, symbol, description = "", "", ""
|
|
36
|
+
|
|
37
|
+
for name_item, symbol_item, limit, desc_item in _MONEY_TABLE:
|
|
38
|
+
if abs_n >= Decimal(str(limit)):
|
|
39
|
+
amount = Decimal(str(n)) / Decimal(str(limit))
|
|
40
|
+
name, symbol, description = name_item, symbol_item, desc_item
|
|
41
|
+
break
|
|
42
|
+
|
|
43
|
+
parts: List[str] = []
|
|
44
|
+
|
|
45
|
+
if "n" in mode: parts.append(name)
|
|
46
|
+
if "s" in mode: parts.append(symbol)
|
|
47
|
+
if "d" in mode: parts.append(description)
|
|
48
|
+
if "v" in mode: parts.append(f"({n} gold)")
|
|
49
|
+
if "lower" in mode: parts = [p.lower() for p in parts]
|
|
50
|
+
if "upper" in mode: parts = [p.upper() for p in parts]
|
|
51
|
+
if "title" in mode: parts = [p.title() for p in parts]
|
|
52
|
+
|
|
53
|
+
suffix = " ".join(part for part in parts if part)
|
|
54
|
+
return f"{amount:.2f}{f' {suffix}' if suffix else ''}"
|
|
55
|
+
|
|
56
|
+
__all__ = [
|
|
57
|
+
"format_money"
|
|
58
|
+
]
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "UnityManager"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
author = "PlayGames-2020"
|
|
9
|
+
description = "Utilities for formatting common units."
|
|
10
|
+
readme = "README.md"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "PlayGames-2020", email = "playgames16.01.2020@gmail.com" },
|
|
14
|
+
]
|
|
15
|
+
license = { file = "LICENSE" }
|
|
16
|
+
keywords = ["units", "formatting", "bytes", "length", "mass", "volume", "games", "money"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Development Status :: 3 - Alpha",
|
|
19
|
+
"Intended Audience :: Developers",
|
|
20
|
+
"License :: OSI Approved :: MIT License",
|
|
21
|
+
"Operating System :: OS Independent",
|
|
22
|
+
"Programming Language :: Python :: 3",
|
|
23
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
24
|
+
"Programming Language :: Python :: 3.9",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
30
|
+
]
|
|
31
|
+
dependencies = []
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=8.4.2"
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.urls]
|
|
39
|
+
Homepage = "https://github.com/PlayGames-2020/UnityManager"
|
|
40
|
+
Repository = "https://github.com/PlayGames-2020/UnityManager.git"
|
|
41
|
+
Documentation = "https://github.com/PlayGames-2020/UnityManager/blob/main/README.md"
|
|
42
|
+
Authors = "https://github.com/PlayGames-2020"
|
|
43
|
+
|
|
44
|
+
[tool.hatch.version]
|
|
45
|
+
path = "UnityManager/__init__.py"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["UnityManager", "games"]
|
|
49
|
+
exclude = [
|
|
50
|
+
"*.pyc"
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from UnityManager import (
|
|
2
|
+
format_bytes,
|
|
3
|
+
format_capacity,
|
|
4
|
+
format_length,
|
|
5
|
+
format_mass,
|
|
6
|
+
format_volume,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_public_api_formats_units():
|
|
11
|
+
assert format_bytes(1024) == "1.00 KB"
|
|
12
|
+
assert format_length(1000) == "1.00 km"
|
|
13
|
+
assert format_capacity(1000) == "1.00 kl"
|
|
14
|
+
assert format_mass(1000) == "1.00 kg"
|
|
15
|
+
assert format_volume(1) == "1.00 m³"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_formatters_support_names_and_case_modes():
|
|
19
|
+
assert format_length(1, "n") == "1.00 metre"
|
|
20
|
+
assert format_mass(1, "nupper") == "1.00 GRAM"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_non_numeric_values_are_returned_as_text():
|
|
24
|
+
assert format_bytes("unknown") == "unknown"
|
|
25
|
+
assert format_length(None) == "None"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from games import format_money
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def test_money_uses_compact_game_currency_symbols():
|
|
5
|
+
assert format_money(999) == "999.00"
|
|
6
|
+
assert format_money(1_500) == "1.50 K"
|
|
7
|
+
assert format_money(2_000_000) == "2.00 M"
|
|
8
|
+
assert format_money(3_000_000_000) == "3.00 B"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_money_supports_names_and_original_value_mode():
|
|
12
|
+
assert format_money(1_500, "n") == "1.50 thousand"
|
|
13
|
+
assert format_money(1_500, "v") == "1.50 (1500 gold)"
|
|
14
|
+
assert format_money(1_500, "nsupper") == "1.50 THOUSAND K"
|