diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..742d2bc
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,16 @@
+
+
+### What:
+
+- [ ] Bug Fix
+- [ ] New Feature
+
+### Description:
+
+
+
+### Related:
+
+
diff --git a/.github/workflows/php-cs-fixer.yml b/.github/workflows/php-cs-fixer.yml
deleted file mode 100755
index 0cd6470..0000000
--- a/.github/workflows/php-cs-fixer.yml
+++ /dev/null
@@ -1,31 +0,0 @@
-name: Check & fix styling
-
-on:
- push:
- branches:
- - master
- - dev
-
-jobs:
- style:
- runs-on: ubuntu-latest
-
- steps:
- - name: Checkout code
- uses: actions/checkout@v2
-
- - name: Fix style
- uses: docker://oskarstark/php-cs-fixer-ga
- with:
- args: --config=.php_cs.dist --allow-risky=yes
-
- - name: Extract branch name
- shell: bash
- run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
- id: extract_branch
-
- - name: Commit changes
- uses: stefanzweifel/git-auto-commit-action@v2.3.0
- with:
- commit_message: Fix styling
- branch: ${{ steps.extract_branch.outputs.branch }}
diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml
deleted file mode 100755
index 82b45c6..0000000
--- a/.github/workflows/psalm.yml
+++ /dev/null
@@ -1,33 +0,0 @@
-name: Psalm
-
-on:
- push:
- paths:
- - '**.php'
- - 'psalm.xml'
-
-jobs:
- psalm:
- name: psalm
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v2
-
- - name: Setup PHP
- uses: shivammathur/setup-php@v2
- with:
- php-version: '7.4'
- extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
- coverage: none
-
- - name: Cache composer dependencies
- uses: actions/cache@v1
- with:
- path: vendor
- key: composer-${{ hashFiles('composer.lock') }}
-
- - name: Run composer require
- run: composer require -n --prefer-dist
-
- - name: Run psalm
- run: ./vendor/bin/psalm -c psalm.xml
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index cb189d2..d87ab00 100755
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -1,8 +1,13 @@
name: Run tests
on:
- push:
pull_request:
+ paths:
+ - "**.php"
+ - "phpunit.xml"
+ - ".github/workflows/run-tests.yml"
+ - "composer.lock"
+ - "composer.json"
jobs:
php-tests:
@@ -10,13 +15,13 @@ jobs:
strategy:
matrix:
- php: [7.4, 8.0]
- laravel: [8.*]
+ php: [8.2, 8.3, 8.4]
+ laravel: [11.*]
dependency-version: [prefer-lowest, prefer-stable]
os: [ubuntu-latest]
include:
- - laravel: 8.*
- testbench: 6.*
+ - laravel: 11.*
+ testbench: 9.*
name: P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.dependency-version }} - ${{ matrix.os }}
@@ -29,21 +34,12 @@ jobs:
with:
php-version: ${{ matrix.php }}
extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick
- coverage: none
+ coverage: pcov
- name: Install dependencies
run: |
composer require "laravel/framework:${{ matrix.laravel }}" "orchestra/testbench:${{ matrix.testbench }}" --no-interaction --no-update
composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest
- # - name: Execute tests
- # run: vendor/bin/phpunit
-
- - name: Send Slack notification
- uses: 8398a7/action-slack@v2
- if: failure()
- with:
- status: ${{ job.status }}
- author_name: ${{ github.actor }}
- env:
- SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK }}
+ - name: Execute tests
+ run: composer run-script test
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
deleted file mode 100755
index ba38a6e..0000000
--- a/.pre-commit-config.yaml
+++ /dev/null
@@ -1,14 +0,0 @@
-repos:
-- repo: https://github.com/digitalpulp/pre-commit-php.git
- sha: 1.3.0
- hooks:
- - id: php-lint
- # - id: php-unit
- - id: php-cbf
- files: \.(php)$
- exclude: \.(blade)\.(php)$
- args: [--standard=phpcs.xml -p]
- - id: php-cs
- files: \.(php)$
- exclude: \.(blade)\.(php)$
- args: [--standard=phpcs.xml -p]
diff --git a/.scrutinizer.yml b/.scrutinizer.yml
deleted file mode 100755
index 59dc837..0000000
--- a/.scrutinizer.yml
+++ /dev/null
@@ -1,154 +0,0 @@
-build:
- environment:
- php:
- version: "7.3.7"
-filter:
- paths:
- - 'src/*'
- excluded_paths:
- - 'config/*'
-tools:
- php_analyzer: true
- php_mess_detector: true
- php_changetracking: true
- php_loc:
- excluded_dirs:
- - vendor
- php_pdepend:
- excluded_dirs:
- - vendor
- - tests
-checks:
- php:
- code_rating: true
- duplication: true
- variable_existence: true
- useless_calls: true
- use_statement_alias_conflict: true
- unused_variables: true
- unused_properties: true
- unused_parameters: true
- unused_methods: true
- unreachable_code: true
- sql_injection_vulnerabilities: true
- security_vulnerabilities: true
- precedence_mistakes: true
- precedence_in_conditions: true
- parameter_non_unique: true
- no_property_on_interface: true
- no_non_implemented_abstract_methods: true
- deprecated_code_usage: true
- closure_use_not_conflicting: true
- closure_use_modifiable: true
- avoid_useless_overridden_methods: true
- avoid_conflicting_incrementers: true
- assignment_of_null_return: true
- verify_property_names: true
- verify_argument_usable_as_reference: true
- verify_access_scope_valid: true
- use_self_instead_of_fqcn: true
- too_many_arguments: true
- symfony_request_injection: true
- switch_fallthrough_commented: true
- spacing_of_function_arguments: true
- spacing_around_non_conditional_operators: true
- spacing_around_conditional_operators: true
- space_after_cast: true
- single_namespace_per_use: true
- simplify_boolean_return: true
- scope_indentation:
- spaces_per_level: '4'
- return_doc_comments: true
- require_scope_for_properties: true
- require_scope_for_methods: true
- require_php_tag_first: true
- require_braces_around_control_structures: true
- remove_trailing_whitespace: true
- remove_php_closing_tag: true
- remove_extra_empty_lines: true
- psr2_switch_declaration: true
- psr2_control_structure_declaration: true
- psr2_class_declaration: true
- property_assignments: true
- properties_in_camelcaps: true
- prefer_while_loop_over_for_loop: true
- phpunit_assertions: true
- php5_style_constructor: true
- parameters_in_camelcaps: true
- parameter_doc_comments: true
- return_doc_comment_if_not_inferrable: true
- param_doc_comment_if_not_inferrable: true
- overriding_private_members: true
- optional_parameters_at_the_end: true
- one_class_per_file: true
- non_commented_empty_catch_block: true
- no_unnecessary_if: true
- no_unnecessary_function_call_in_for_loop: true
- no_unnecessary_final_modifier: true
- no_underscore_prefix_in_properties: true
- no_underscore_prefix_in_methods: true
- no_trailing_whitespace: true
- no_space_inside_cast_operator: true
- no_space_before_semicolon: true
- no_space_around_object_operator: true
- no_goto: true
- no_global_keyword: true
- no_exit: true
- no_empty_statements: true
- no_else_if_statements: true
- no_duplicate_arguments: true
- no_debug_code: true
- no_commented_out_code: true
- newline_at_end_of_file: true
- naming_conventions:
- local_variable: '^[a-z][a-zA-Z0-9]*$'
- abstract_class_name: ^Abstract|Factory$
- utility_class_name: 'Utils?$'
- constant_name: '^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)*$'
- property_name: '^[a-z][a-zA-Z0-9]*$'
- method_name: '^(?:[a-z]|__)[a-zA-Z0-9]*$'
- parameter_name: '^[a-z][a-zA-Z0-9]*$'
- interface_name: '^[A-Z][a-zA-Z0-9]*Interface$'
- type_name: '^[A-Z][a-zA-Z0-9]*$'
- exception_name: '^[A-Z][a-zA-Z0-9]*Exception$'
- isser_method_name: '^(?:is|has|should|may|supports|was)'
- lowercase_php_keywords: true
- more_specific_types_in_doc_comments: true
- missing_arguments: true
- method_calls_on_non_object: true
- line_length:
- max_length: '120'
- lowercase_basic_constants: true
- instanceof_class_exists: true
- function_in_camel_caps: true
- function_body_start_on_new_line: true
- fix_use_statements:
- remove_unused: true
- preserve_multiple: false
- preserve_blanklines: false
- order_alphabetically: true
- foreach_traversable: true
- foreach_usable_as_reference: true
- fix_php_opening_tag: true
- fix_line_ending: true
- fix_identation_4spaces: true
- fix_doc_comments: true
- ensure_lower_case_builtin_functions: true
- encourage_postdec_operator: true
- classes_in_camel_caps: true
- catch_class_exists: true
- blank_line_after_namespace_declaration: true
- avoid_usage_of_logical_operators: true
- avoid_unnecessary_concatenation: true
- avoid_tab_indentation: true
- avoid_superglobals: true
- avoid_perl_style_comments: true
- avoid_multiple_statements_on_same_line: true
- avoid_fixme_comments: true
- avoid_length_functions_in_loops: true
- avoid_entity_manager_injection: true
- avoid_duplicate_types: true
- avoid_corrupting_byteorder_marks: true
- argument_type_checks: true
- avoid_aliased_php_functions: true
- deadlock_detection_in_loops: true
diff --git a/.styleci.yml b/.styleci.yml
deleted file mode 100755
index c3bb259..0000000
--- a/.styleci.yml
+++ /dev/null
@@ -1 +0,0 @@
-preset: laravel
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..b7c113f
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,20 @@
+# Release Note for 1.x
+
+## [v1.2.0](https://github.com/pwweb/Copper/compare/v1.1.0...v1.2.0) - 2025-02-12
+
+* 🧑🔬 Add support for SI Units and Prefixes by [@rabrowne85](https://github.com/rabrowne85) in [#4](https://github.com/pwweb/Copper/pull/4)
+
+## [v1.1.0](https://github.com/pwweb/Copper/compare/v1.0.1...v1.1.0) - 2023-08-16
+
+* Apply fixes from StyleCI by [@rabrowne85](https://github.com/rabrowne85) in [#2](https://github.com/pwweb/Copper/pull/2)
+* Add types to Copper
+* Drop PHP 7.x support
+
+## [v1.0.1](https://github.com/pwweb/Copper/compare/v1.0.0...v1.0.1) - 2021-01-15
+
+* Correction to Precision for Decimals
+
+## [v1.0.0](https://github.com/pwweb/Copper/compare/v1.0.0...v1.0.1) - 2021-01-15
+
+* First release
+
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..d875abc
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,51 @@
+# CONTRIBUTING
+
+Contributions are welcome, and are accepted via pull requests.
+Please review these guidelines before submitting any pull requests.
+
+## Process
+
+1. Fork the project
+2. Create a new branch
+3. Code, test, commit and push
+4. Open a pull request detailing your changes. Make sure to follow the [template](.github/PULL_REQUEST_TEMPLATE.md)
+
+## Guidelines
+
+* Please ensure the coding style running `composer lint`.
+* Send a coherent commit history, making sure each individual commit in your pull request is meaningful.
+* You may need to [rebase](https://git-scm.com/book/en/v2/Git-Branching-Rebasing) to avoid merge conflicts.
+* Please remember that we follow [SemVer](http://semver.org/).
+
+## Setup
+
+Clone your fork, then install the dev dependencies:
+```bash
+composer install
+```
+## Lint
+
+Lint your code:
+```bash
+composer lint
+```
+## Tests
+Run all tests:
+```bash
+composer test
+```
+
+Check types:
+```bash
+composer test:type:check
+```
+
+Unit tests:
+```bash
+composer test:unit
+```
+
+Integration tests:
+```bash
+composer test:integration
+```
diff --git a/README.md b/README.md
index 059091f..5226729 100755
--- a/README.md
+++ b/README.md
@@ -44,14 +44,15 @@ If you don't provide a `$style` it will default to `NumberFormatter::DECIMAL`. I
Then with this you can format in any of the following ways:
-| Format | Code | Output |
-| ---------- | --------------------------- | --------------------------------------------------------- |
-| Currency | `$value->currency('GBP')` | -£1,234.56 |
-| Decimal | `$value->decimal(2)` | -1,234.56 |
-| Percentage | `$value->percentage()` | -123,456% |
-| Scientific | `$value->scientific()` | -1.23456E3 |
+| Format | Code | Output |
+|------------|----------------------------|-----------------------------------------------------------|
+| Currency | `$value->currency('GBP')` | -£1,234.56 |
+| Decimal | `$value->decimal(2)` | -1,234.56 |
+| Percentage | `$value->percentage()` | -123,456% |
+| Scientific | `$value->scientific()` | -1.23456E3 |
| Accounting | `$value->accounting('GBP')` | (£1,234.56) |
-| SpellOut | `$value->spellOut()` | minus one thousand two hundred thirty-four point five six |
+| SpellOut | `$value->spellOut()` | minus one thousand two hundred thirty-four point five six |
+| Unit | `$value->unit(Unit::GRAM)` | 1.234 kg |
Since the `create()` function returns an instance of `Copper`, you can chain the methods together i.e. `Copper\Copper::create(-1234.56)->currency('EUR')` leads to `-€1,234.56`.
@@ -69,7 +70,7 @@ In addition to the base functions there are a few setters and getters:
## Change log
-Please see the [changelog](changelog.md) for more information on what has changed recently.
+Please see the [changelog](CHANGELOG) for more information on what has changed recently.
## Contributing
diff --git a/composer.json b/composer.json
index b39589b..c664639 100755
--- a/composer.json
+++ b/composer.json
@@ -7,7 +7,7 @@
"format",
"NumberFormatter"
],
- "homepage": "http://copper.pw-websolutions.com",
+ "homepage": "https://github.com/pwweb/Copper",
"support": {
"issues": "https://github.com/pwweb/Copper/issues",
"source": "https://github.com/pwweb/Copper"
@@ -16,27 +16,29 @@
"authors": [{
"name": "Richard Browne",
"email": "richard.browne@pw-websolutions.com",
- "homepage": "http://www.pw-websolutions.com"
+ "homepage": "https://www.pw-websolutions.com"
},
{
"name": "Frank Pillukeit",
"email": "frank.pillukeit@pw-websolutions.com",
- "homepage": "http://www.pw-websolutions.com"
+ "homepage": "https://www.pw-websolutions.com"
}
],
"prefer-stable": true,
"minimum-stability": "dev",
"require": {
- "php": "^8.0",
- "ext-intl": "*"
+ "php": "^8.2",
+ "ext-intl": "*",
+ "laravel/framework": "^11.0"
},
"require-dev": {
- "friendsofphp/php-cs-fixer": "^2.14 || ^3.0",
- "kylekatarnls/multi-tester": "^1.1",
- "phpmd/phpmd": "^2.8",
- "phpunit/phpunit": "^7.5 || ^8.0",
- "psalm/plugin-laravel": "^1.2",
- "squizlabs/php_codesniffer": "^3.4"
+ "laravel/pint": "^1.20",
+ "pestphp/pest": "^v3.7.4",
+ "phpstan/phpstan": "^2.1",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "rector/rector": "^2.0",
+ "squizlabs/php_codesniffer": "^3.4",
+ "thecodingmachine/safe": "3.0.0-alpha1 as 2.5.0"
},
"autoload": {
"psr-4": {
@@ -50,28 +52,31 @@
},
"config": {
"process-timeout": 0,
- "sort-packages": true
+ "sort-packages": true,
+ "allow-plugins": {
+ "pestphp/pest-plugin": true
+ }
},
"scripts": {
+ "refacto": "rector",
+ "lint": "pint",
+ "test:refacto": "rector --dry-run",
+ "test:lint": "pint --test",
+ "test:type:check": "phpstan analyse --ansi --memory-limit=-1 --debug",
+ "test:type:coverage": "php -d memory_limit=-1 ./vendor/bin/pest --coverage --min=100",
+ "test:unit": "php ./vendor/bin/pest --colors=always --exclude-group=integration --compact",
"test": [
- "@phpunit",
- "@style-check"
- ],
- "style-check": [
- "@phpcs",
- "@phpstan",
- "@phpmd"
- ],
- "phpunit": "phpunit --verbose",
- "phpcs": "php-cs-fixer fix -v --diff --dry-run",
- "phpstan": "phpstan analyse --configuration phpstan.neon --level 3 src tests",
- "phpmd": "phpmd src text /phpmd.xml",
- "phpdoc": "php phpdoc.php"
+ "@test:refacto",
+ "@test:lint",
+ "@test:type:check",
+ "@test:type:coverage",
+ "@test:unit"
+ ]
},
"extra": {
"laravel": {
"providers": [
- "Copper\\Laravel\\ServiceProvider"
+ "Copper\\Laravel\\CopperServiceProvider"
]
}
}
diff --git a/phpcs.xml b/phpcs.xml
deleted file mode 100755
index d36c903..0000000
--- a/phpcs.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-
-
- The coding standard for PWWEB/Artomator.
-
- src
- config
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- error
-
-
-
-
- error
-
-
\ No newline at end of file
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..c061343
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,9 @@
+includes:
+ - vendor/phpstan/phpstan-strict-rules/rules.neon
+
+parameters:
+ level: max
+ paths:
+ - src
+
+ reportUnmatchedIgnoredErrors: true
diff --git a/phpunit.xml b/phpunit.xml
new file mode 100644
index 0000000..fb95829
--- /dev/null
+++ b/phpunit.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ ./tests
+
+
+
+
+ ./src
+
+
+ ./src/Copper/Laravel
+
+
+
diff --git a/pint.json b/pint.json
new file mode 100644
index 0000000..edd174b
--- /dev/null
+++ b/pint.json
@@ -0,0 +1,7 @@
+{
+ "preset": "laravel",
+ "rules": {
+ "yoda_style": true,
+ "declare_strict_types": true
+ }
+}
diff --git a/psalm.xml b/psalm.xml
deleted file mode 100755
index 82f4d49..0000000
--- a/psalm.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/rector.php b/rector.php
new file mode 100644
index 0000000..191989d
--- /dev/null
+++ b/rector.php
@@ -0,0 +1,23 @@
+withPaths([
+ __DIR__.'/src',
+ ])
+ ->withSkip([
+ __DIR__.'/src/Plugins/Parallel/Paratest/WrapperRunner.php',
+ ReturnNeverTypeRector::class,
+ ])
+ ->withPreparedSets(
+ deadCode: true,
+ codeQuality: true,
+ typeDeclarations: true,
+ privatization: true,
+ earlyReturn: true,
+ )
+ ->withPhpSets();
diff --git a/src/Copper/Copper.php b/src/Copper/Copper.php
index 77cb35a..cbc26f8 100755
--- a/src/Copper/Copper.php
+++ b/src/Copper/Copper.php
@@ -1,5 +1,7 @@
setAttribute(NumberFormatter::FRACTION_DIGITS, $precision);
@@ -111,9 +103,9 @@ public static function decimal(?int $precision = null): string
* Format the number using CURRENCY.
*
* @param string $iso The 3-letter ISO 4217 currency code indicating the currency to use.
- * @return string Formatted number.
+ * @return string|bool Formatted number.
*/
- public static function currency(string $iso): string
+ public static function currency(string $iso): string|bool
{
if (NumberFormatter::CURRENCY !== self::$style) {
self::setStyle(NumberFormatter::CURRENCY);
@@ -125,9 +117,9 @@ public static function currency(string $iso): string
/**
* Format the number using SPELLOUT.
*
- * @return string Formatted number.
+ * @return string|bool Formatted number.
*/
- public static function spellOut(): string
+ public static function spellOut(): string|bool
{
if (NumberFormatter::SPELLOUT !== self::$style) {
self::setStyle(NumberFormatter::SPELLOUT);
@@ -139,9 +131,9 @@ public static function spellOut(): string
/**
* Format the number using PERCENT.
*
- * @return string Formatted number.
+ * @return string|bool Formatted number.
*/
- public static function percentage(): string
+ public static function percentage(): string|bool
{
if (NumberFormatter::PERCENT !== self::$style) {
self::setStyle(NumberFormatter::PERCENT);
@@ -154,9 +146,9 @@ public static function percentage(): string
* Format the number using CURRENCY_ACCOUNTING.
*
* @param string $iso The 3-letter ISO 4217 currency code indicating the currency to use.
- * @return string Formatted number.
+ * @return string|bool Formatted number.
*/
- public static function accounting(string $iso): string
+ public static function accounting(string $iso): string|bool
{
if (NumberFormatter::CURRENCY_ACCOUNTING !== self::$style) {
self::setStyle(NumberFormatter::CURRENCY_ACCOUNTING);
@@ -168,9 +160,9 @@ public static function accounting(string $iso): string
/**
* Format the number using SCIENTIFIC.
*
- * @return string Formatted number.
+ * @return string|bool Formatted number.
*/
- public static function scientific(): string
+ public static function scientific(): string|bool
{
if (NumberFormatter::SCIENTIFIC !== self::$style) {
self::setStyle(NumberFormatter::SCIENTIFIC);
@@ -179,13 +171,56 @@ public static function scientific(): string
return self::$formatter->format(self::$value);
}
+ /**
+ * Format the number using SI units and prefixes.
+ *
+ * @param Unit $unit SI Unit to display using.
+ *
+ * @parm bool $usePrefix Set whether to use prefixes.
+ *
+ * @param bool $useThrees Set whether to use only multiples of three in prefixes.
+ * @param int|null $precision Set the precision of the number.
+ * @return string Formatted number.
+ */
+ public static function unit(Unit $unit, bool $usePrefix = true, bool $useThrees = true, ?int $precision = null): string
+ {
+ if (false === is_null($precision)) {
+ self::$formatter->setAttribute(NumberFormatter::FRACTION_DIGITS, $precision);
+ }
+ if (NumberFormatter::DECIMAL !== self::$style) {
+ self::setStyle(NumberFormatter::DECIMAL);
+ }
+
+ $value = self::$value;
+ $exponent = 0;
+
+ if ($usePrefix) {
+ $exponent = (int) (floor(log10(abs($value))));
+
+ if ($useThrees || $exponent >= 3) {
+ $options = [
+ (int) floor($exponent / 3) * 3,
+ (int) ceil($exponent / 3) * 3,
+ ];
+ $exponent =
+ abs($exponent - $options[0]) < abs($options[1] - $exponent)
+ ? $options[0]
+ : $options[1];
+ }
+
+ $value /= (10 ** $exponent);
+ }
+
+ return self::$formatter->format($value).' '.Prefix::from($exponent)->symbol().$unit->value;
+ }
+
/**
* Set the Locale.
*
* @param string $locale Locale in which the number would be formatted.
- * @return Copper Copper instance.
+ * @return ?Copper Copper instance.
*/
- public static function setLocale(string $locale): Copper
+ public static function setLocale(string $locale): ?Copper
{
self::$locale = $locale;
self::create();
@@ -207,9 +242,9 @@ public static function getLocale(): string
* Set the Style.
*
* @param int $style Style of the formatting.
- * @return Copper Copper instance.
+ * @return ?Copper Copper instance.
*/
- public static function setStyle(int $style): Copper
+ public static function setStyle(int $style): ?Copper
{
self::$style = $style;
self::create();
diff --git a/src/Copper/Enums/Prefix.php b/src/Copper/Enums/Prefix.php
new file mode 100644
index 0000000..865efeb
--- /dev/null
+++ b/src/Copper/Enums/Prefix.php
@@ -0,0 +1,70 @@
+ 'Q',
+ self::RONNA => 'R',
+ self::YOTTA => 'Y',
+ self::ZETTA => 'Z',
+ self::EXA => 'E',
+ self::PETA => 'P',
+ self::TERA => 'T',
+ self::GIGA => 'G',
+ self::MEGA => 'M',
+ self::KILO => 'k',
+ self::HECTO => 'h',
+ self::DECA => 'da',
+ self::DECI => 'd',
+ self::CENTI => 'c',
+ self::MILLI => 'm',
+ self::MICRO => 'µ',
+ self::NANO => 'n',
+ self::PICO => 'p',
+ self::FEMTO => 'f',
+ self::ATTO => 'a',
+ self::ZEPTO => 'z',
+ self::YOCTO => 'y',
+ self::RONTO => 'r',
+ self::QUECTO => 'q',
+ self::BASE => '',
+ };
+ }
+
+ public function multipleOfThree(): bool
+ {
+ return 0 === (abs($this->value) % 3);
+ }
+}
diff --git a/src/Copper/Enums/Unit.php b/src/Copper/Enums/Unit.php
new file mode 100644
index 0000000..d591ee2
--- /dev/null
+++ b/src/Copper/Enums/Unit.php
@@ -0,0 +1,73 @@
+ 'Second',
+ self::METRE => 'Metre',
+ self::GRAM => 'Gram',
+ self::AMPERE => 'Ampere',
+ self::KELVIN => 'Kelvin',
+ self::MOLE => 'Mole',
+ self::CANDELA => 'Candela',
+ self::RADIAN => 'Radian',
+ self::STERADIAN => 'Steradian',
+ self::HERTZ => 'Hertz',
+ self::NEWTON => 'Newton',
+ self::PASCAL => 'Pascal',
+ self::JOULE => 'Joule',
+ self::WATT => 'Watt',
+ self::COULOMB => 'Coulomb',
+ self::VOLT => 'Volt',
+ self::FARAD => 'Farad',
+ self::OHM => 'Ohm',
+ self::SIEMENS => 'Siemens',
+ self::WEBER => 'Weber',
+ self::TESLA => 'Tesla',
+ self::HENRY => 'Henry',
+ self::CELSIUS => 'Celsius',
+ self::LUMEN => 'Lumen',
+ self::LUX => 'Lux',
+ self::BECQUEREL => 'Becquerel',
+ self::GRAY => 'Gray',
+ self::SIEVERT => 'Sievert',
+ self::KATAL => 'Katal',
+ };
+ }
+}
diff --git a/src/Copper/Laravel/CopperServiceProvider.php b/src/Copper/Laravel/CopperServiceProvider.php
new file mode 100755
index 0000000..544b19f
--- /dev/null
+++ b/src/Copper/Laravel/CopperServiceProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Copper\Laravel;
+
+use Copper\Copper;
+use Illuminate\Foundation\Events\LocaleUpdated;
+use Illuminate\Support\Facades\Event;
+use Illuminate\Support\ServiceProvider;
+
+/**
+ * A simple API extension for NumberFormatter.
+ */
+class CopperServiceProvider extends ServiceProvider
+{
+ public function boot(): void
+ {
+ $this->updateLocale();
+
+ Event::listen(LocaleUpdated::class, fn ($event) => $this->updateLocale());
+ }
+
+ public function updateLocale(): void
+ {
+ $app = $this->app;
+ $locale = $app->getLocale();
+ Copper::setLocale($locale);
+ }
+}
diff --git a/src/Copper/Laravel/ServiceProvider.php b/src/Copper/Laravel/ServiceProvider.php
deleted file mode 100755
index ad1fb8a..0000000
--- a/src/Copper/Laravel/ServiceProvider.php
+++ /dev/null
@@ -1,84 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Copper\Laravel;
-
-use Copper\Copper;
-use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
-use Illuminate\Events\Dispatcher;
-use Illuminate\Events\EventDispatcher;
-
-/**
- * A simple API extension for NumberFormatter.
- */
-class ServiceProvider extends \Illuminate\Support\ServiceProvider
-{
- /**
- * Service provider boot loader.
- *
- * @return void
- */
- public function boot()
- {
- $this->updateLocale();
-
- if (false === $this->app->bound('events')) {
- return;
- }
-
- $service = $this;
- $events = $this->app['events'];
-
- if (true === $this->isEventDispatcher($events)) {
- $events->listen(
- (true === class_exists('Illuminate\Foundation\Events\LocaleUpdated')) ? 'Illuminate\Foundation\Events\LocaleUpdated' : 'locale.changed',
- function () use ($service) {
- $service->updateLocale();
- }
- );
- }
- }
-
- /**
- * Function to update the locale used by Copper.
- *
- * @return void
- */
- public function updateLocale()
- {
- $app = $this->app;
- $locale = $app->getLocale();
- Copper::setLocale($locale);
- }
-
- /**
- * Registration function for Laravel <5.3.
- *
- * @return void
- */
- public function register()
- {
- // Needed for Laravel < 5.3 compatibility
- }
-
- /**
- * Check function for Event Dispatcher.
- *
- * @param Illuminate\Contracts\Events\Dispatcher|Illuminate\Events\Dispatcher|Illuminate\Events\EventDispatcher $instance Event dispatcher
- * @return bool
- */
- protected function isEventDispatcher($instance)
- {
- return $instance instanceof EventDispatcher
- || $instance instanceof Dispatcher
- || $instance instanceof DispatcherContract;
- }
-}
diff --git a/tests/Pest.php b/tests/Pest.php
new file mode 100644
index 0000000..4e6310e
--- /dev/null
+++ b/tests/Pest.php
@@ -0,0 +1,47 @@
+extend(Tests\TestCase::class)->in('Feature');
+
+/*
+|--------------------------------------------------------------------------
+| Expectations
+|--------------------------------------------------------------------------
+|
+| When you're writing tests, you often need to check that values meet certain conditions. The
+| "expect()" function gives you access to a set of "expectations" methods that you can use
+| to assert different things. Of course, you may extend the Expectation API at any time.
+|
+*/
+
+expect()->extend('toBeOne', function () {
+ return $this->toBe(1);
+});
+
+/*
+|--------------------------------------------------------------------------
+| Functions
+|--------------------------------------------------------------------------
+|
+| While Pest is very powerful out-of-the-box, you may have some testing code specific to your
+| project that you don't want to repeat in every file. Here you can also expose helpers as
+| global functions to help you to reduce the number of lines of code in your test files.
+|
+*/
+
+function something()
+{
+ // ..
+}
diff --git a/tests/TestCase.php b/tests/TestCase.php
new file mode 100644
index 0000000..d42f312
--- /dev/null
+++ b/tests/TestCase.php
@@ -0,0 +1,12 @@
+toBeInstanceOf(Copper::class)
+ ->and(Copper::getLocale())
+ ->toBe('en-GB')
+ ->and(Copper::getStyle())
+ ->toBe(NumberFormatter::DECIMAL);
+});
+
+it('creates an instance with custom values', function () {
+ $copper = Copper::create(1234.56, NumberFormatter::CURRENCY, 'fr-FR');
+ expect($copper)
+ ->toBeInstanceOf(Copper::class)
+ ->and(Copper::getLocale())
+ ->toBe('fr-FR')
+ ->and(Copper::getStyle())
+ ->toBe(NumberFormatter::CURRENCY);
+});
+
+it('formats a number as decimal', function () {
+ Copper::create(1234.56, locale: 'en-GB');
+ $result = Copper::decimal(2);
+ expect($result)->toBe('1,234.56');
+});
+
+it('formats a number as currency', function () {
+ Copper::create(1234.56);
+ $result = Copper::currency('USD');
+ expect($result)->toBe('US$1,234.56');
+});
+
+it('formats a number as spellout', function () {
+ Copper::create(1234.56);
+ $result = Copper::spellOut();
+ expect($result)->toBe('one thousand two hundred thirty-four point five six');
+});
+
+it('formats a number as percentage', function () {
+ Copper::create(0.56);
+ $result = Copper::percentage();
+ expect($result)->toBe('56%');
+});
+
+it('formats a number as scientific', function () {
+ Copper::create(1234.56);
+ $result = Copper::scientific();
+ expect($result)->toBe('1.23456E3');
+});
+
+it('formats a number as accounting currency', function () {
+ Copper::create(1234.56);
+ $result = Copper::accounting('USD');
+ expect($result)->toBe('US$1,234.56');
+});
+
+it('formats a number with SI units and prefixes', function (float $value, string $output) {
+ Copper::create($value);
+ $result = Copper::unit(Unit::METRE);
+ expect($result)->toBe($output);
+})->with([
+ [123_456.99, '0.123 Mm'],
+ [12_345.67, '12.346 km'],
+ [1_234.56, '1.235 km'],
+ [123.45, '0.123 km'],
+ [12.34, '12.34 m'],
+]);
+
+it('formats a number with SI units and prefixes whilst using the non-multiples of three', function (float $value, string $output) {
+ Copper::create($value);
+ $result = Copper::unit(Unit::METRE, useThrees: false);
+ expect($result)->toBe($output);
+})->with([
+ [123_456.99, '0.123 Mm'],
+ [12_345.67, '12.346 km'],
+ [1_234.56, '1.235 km'],
+ [123.45, '1.234 hm'],
+ [12.34, '1.234 dam'],
+]);
+
+it('formats a number with SI units and prefixes with specific precision', function () {
+ Copper::create(123456);
+ $result = Copper::unit(Unit::METRE, precision: 2);
+ expect($result)->toBe('0.12 Mm');
+});
+
+it('sets and gets the locale', function () {
+ Copper::setLocale('de-DE');
+ expect(Copper::getLocale())->toBe('de-DE');
+});
+
+it('sets and gets the style', function () {
+ Copper::setStyle(NumberFormatter::CURRENCY);
+ expect(Copper::getStyle())->toBe(NumberFormatter::CURRENCY);
+});
diff --git a/tests/Unit/Enums/PrefixTest.php b/tests/Unit/Enums/PrefixTest.php
new file mode 100644
index 0000000..fbd3547
--- /dev/null
+++ b/tests/Unit/Enums/PrefixTest.php
@@ -0,0 +1,65 @@
+symbol())->toBe($expectedSymbol);
+})->with([
+ [Prefix::QUETTA, 'Q'],
+ [Prefix::RONNA, 'R'],
+ [Prefix::YOTTA, 'Y'],
+ [Prefix::ZETTA, 'Z'],
+ [Prefix::EXA, 'E'],
+ [Prefix::PETA, 'P'],
+ [Prefix::TERA, 'T'],
+ [Prefix::GIGA, 'G'],
+ [Prefix::MEGA, 'M'],
+ [Prefix::KILO, 'k'],
+ [Prefix::HECTO, 'h'],
+ [Prefix::DECA, 'da'],
+ [Prefix::DECI, 'd'],
+ [Prefix::CENTI, 'c'],
+ [Prefix::MILLI, 'm'],
+ [Prefix::MICRO, 'µ'],
+ [Prefix::NANO, 'n'],
+ [Prefix::PICO, 'p'],
+ [Prefix::FEMTO, 'f'],
+ [Prefix::ATTO, 'a'],
+ [Prefix::ZEPTO, 'z'],
+ [Prefix::YOCTO, 'y'],
+ [Prefix::RONTO, 'r'],
+ [Prefix::QUECTO, 'q'],
+ [Prefix::BASE, ''],
+]);
+
+it('checks if prefix is multiple of three', function (Prefix $prefix, bool $expectedResult) {
+ expect($prefix->multipleOfThree())->toBe($expectedResult);
+})->with([
+ [Prefix::QUETTA, true],
+ [Prefix::RONNA, true],
+ [Prefix::YOTTA, true],
+ [Prefix::ZETTA, true],
+ [Prefix::EXA, true],
+ [Prefix::PETA, true],
+ [Prefix::TERA, true],
+ [Prefix::GIGA, true],
+ [Prefix::MEGA, true],
+ [Prefix::KILO, true],
+ [Prefix::HECTO, false],
+ [Prefix::DECA, false],
+ [Prefix::DECI, false],
+ [Prefix::CENTI, false],
+ [Prefix::MILLI, true],
+ [Prefix::MICRO, true],
+ [Prefix::NANO, true],
+ [Prefix::PICO, true],
+ [Prefix::FEMTO, true],
+ [Prefix::ATTO, true],
+ [Prefix::ZEPTO, true],
+ [Prefix::YOCTO, true],
+ [Prefix::RONTO, true],
+ [Prefix::QUECTO, true],
+ [Prefix::BASE, true],
+]);
diff --git a/tests/Unit/Enums/UnitTest.php b/tests/Unit/Enums/UnitTest.php
new file mode 100644
index 0000000..b69c81d
--- /dev/null
+++ b/tests/Unit/Enums/UnitTest.php
@@ -0,0 +1,60 @@
+name())->toBe($expectedName);
+})->with([
+ [Unit::SECOND, 'Second'],
+ [Unit::METRE, 'Metre'],
+ [Unit::GRAM, 'Gram'],
+ [Unit::AMPERE, 'Ampere'],
+ [Unit::KELVIN, 'Kelvin'],
+ [Unit::MOLE, 'Mole'],
+ [Unit::CANDELA, 'Candela'],
+ [Unit::RADIAN, 'Radian'],
+ [Unit::STERADIAN, 'Steradian'],
+ [Unit::HERTZ, 'Hertz'],
+ [Unit::NEWTON, 'Newton'],
+ [Unit::PASCAL, 'Pascal'],
+ [Unit::JOULE, 'Joule'],
+ [Unit::WATT, 'Watt'],
+ [Unit::COULOMB, 'Coulomb'],
+ [Unit::VOLT, 'Volt'],
+ [Unit::FARAD, 'Farad'],
+ [Unit::OHM, 'Ohm'],
+ [Unit::SIEMENS, 'Siemens'],
+ [Unit::WEBER, 'Weber'],
+ [Unit::TESLA, 'Tesla'],
+ [Unit::HENRY, 'Henry'],
+ [Unit::CELSIUS, 'Celsius'],
+ [Unit::LUMEN, 'Lumen'],
+ [Unit::LUX, 'Lux'],
+ [Unit::BECQUEREL, 'Becquerel'],
+ [Unit::GRAY, 'Gray'],
+ [Unit::SIEVERT, 'Sievert'],
+ [Unit::KATAL, 'Katal'],
+]);
+
+it('throws exception for invalid unit conversion', function () {
+ expect(fn () => Copper::create(1234.56)->unit('invalid_unit'))
+ ->toThrow(TypeError::class);
+});
+
+it('formats a number with SI units and prefixes for edge cases', function (float $value, string $output) {
+ Copper::create($value);
+ $result = Copper::unit(Unit::METRE);
+ expect($result)->toBe($output);
+})->with([
+ [0.001, '1 mm'],
+ [0.000001, '1 µm'],
+ [0.000000001, '1 nm'],
+ [0.000000000001, '1 pm'],
+ [0.000000000000001, '1 fm'],
+ [0.000000000000000001, '1 am'],
+ [0.000000000000000000001, '1 zm'],
+ [0.000000000000000000000001, '1 ym'],
+]);