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'], +]);