├── .babelrc
├── .gitignore
├── brower-entry.js
├── .eslintrc.js
├── .travis.yml
├── src
├── Exception
│ ├── NotGeezArgumentException.js
│ └── NotAnIntegerArgumentException.js
├── Converter
│ ├── AsciiConverter.js
│ ├── Converter.js
│ └── GeezConverter.js
├── Geezify.js
└── Helper
│ ├── GeezParser.js
│ └── GeezCalculator.js
├── webpack.config.js
├── test
├── AsciiConverterTest.js
├── GeezConverterTest.js
├── GeezifyTest.js
└── TestCase.js
├── LICENSE
├── package.json
├── README.md
└── dist
└── geezify.min.js
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-env"
4 | ]
5 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | npm-debug.log
2 |
3 | /.nyc_output
4 | /coverage
5 | /node_modules/
6 |
--------------------------------------------------------------------------------
/brower-entry.js:
--------------------------------------------------------------------------------
1 | const Geezify = require('./src/Geezify');
2 |
3 | window.Geezify = Geezify;
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: 'airbnb-base',
3 |
4 | rules: {
5 | camelcase: 'off',
6 | 'class-methods-use-this': 'off',
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 |
3 | node_js:
4 | - "node"
5 | - "lts/*"
6 |
7 | install:
8 | - npm install --only=dev
9 |
10 | after_success: npm run coverage
11 |
--------------------------------------------------------------------------------
/src/Exception/NotGeezArgumentException.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Class NotGeezArgumentException.
3 | *
4 | * @author Sam As End <4sam21{at}gmail.com>
5 | */
6 | module.exports = class NotGeezArgumentException extends Error {
7 | constructor($argument) {
8 | super(`Not a geez number!, ${$argument} given.`);
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/src/Exception/NotAnIntegerArgumentException.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Class NotAnIntegerArgumentException.
3 | *
4 | * @author Sam As End <4sam21{at}gmail.com>
5 | */
6 | module.exports = class NotAnIntegerArgumentException extends Error {
7 | constructor($argument) {
8 | super(`Not an integer!, ${typeof $argument} given.`);
9 | }
10 | };
11 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | entry: './brower-entry.js',
5 | output: {
6 | filename: 'geezify.min.js',
7 | path: path.resolve(__dirname, 'dist')
8 | },
9 | module: {
10 | rules: [
11 | { test: /\.js$/, exclude: /node_modules/, loader: "babel-loader" }
12 | ]
13 | }
14 | };
--------------------------------------------------------------------------------
/test/AsciiConverterTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | const TestCase = require('./TestCase');
4 | const AsciiConverter = require('../src/Converter/AsciiConverter');
5 | const NotGeezArgumentException = require('../src/Exception/NotGeezArgumentException');
6 |
7 | const asciiConverter = new AsciiConverter();
8 |
9 | /* global describe, it */
10 |
11 | describe('AsciiConverterTest', () => {
12 | describe('#test_ascii_converter()', () => {
13 | it('should convert geez number to ascii', () => {
14 | TestCase.geezNumberTestDataProvider().forEach(([$ascii, $geez]) => {
15 | const $result = asciiConverter.convert($geez);
16 | assert.equal($ascii, $result);
17 | });
18 | });
19 | });
20 |
21 | describe('#test_invalid_number_throw_exception()', () => {
22 | it('should throws NotGeezArgumentException', () => {
23 | TestCase.invalidNumberDataProvider().forEach(([$value]) => {
24 | assert.throws(() => {
25 | asciiConverter.convert($value);
26 | }, NotGeezArgumentException);
27 | });
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/test/GeezConverterTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | const TestCase = require('./TestCase');
4 | const GeezConverter = require('../src/Converter/GeezConverter');
5 | const NotAnIntegerArgumentException = require('../src/Exception/NotAnIntegerArgumentException');
6 |
7 | const geezConverter = new GeezConverter();
8 |
9 | /* global describe, it */
10 |
11 | describe('GeezConverterTest', () => {
12 | describe('#test_geez_converter()', () => {
13 | it('should convert ascii number to geez', () => {
14 | TestCase.geezNumberTestDataProvider().forEach(([$number, $geez_number]) => {
15 | const $result = geezConverter.convert($number);
16 | assert.equal($geez_number, $result);
17 | });
18 | });
19 | });
20 |
21 | describe('#test_invalid_number_throw_exception()', () => {
22 | it('should throws NotAnIntegerArgumentException', () => {
23 | TestCase.invalidNumberDataProvider().forEach(($number) => {
24 | assert.throws(() => {
25 | geezConverter.convert($number);
26 | }, NotAnIntegerArgumentException);
27 | });
28 | });
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 geezify
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.
22 |
--------------------------------------------------------------------------------
/src/Converter/AsciiConverter.js:
--------------------------------------------------------------------------------
1 | const Converter = require('./Converter');
2 | const GeezParser = require('../Helper/GeezParser');
3 | const GeezCalculator = require('../Helper/GeezCalculator');
4 |
5 | /**
6 | * AsciiConverter converts geez number like ፲፱፻፹፮
7 | * to equivalent ascii number like 1986.
8 | *
9 | * @author Sam As End <4sam21{at}gmail.com>
10 | */
11 | module.exports = class AsciiConverter extends Converter {
12 | /**
13 | * Accepts geez number and return an integer.
14 | *
15 | * @param $geez_number string to be converted
16 | *
17 | * @throws NotGeezArgumentException if the valid geez number
18 | *
19 | * @return int the ascii representation
20 | */
21 | convert($geez_number) {
22 | const $parsed = this.parse($geez_number);
23 |
24 | return this.calculate($parsed);
25 | }
26 |
27 | /**
28 | * Parse the geez number number to a queue.
29 | *
30 | * @param $geez_number
31 | *
32 | * @return Queue
33 | */
34 | parse($geez_number) {
35 | const $parser = new GeezParser($geez_number);
36 | $parser.parse();
37 |
38 | return $parser.getParsed();
39 | }
40 |
41 | /**
42 | * Calculate the ascii from the parsed queue.
43 | *
44 | * @param Queue $parsed
45 | *
46 | * @return int
47 | */
48 | calculate($parsed) {
49 | const $calculator = new GeezCalculator($parsed);
50 | $calculator.calculate();
51 |
52 | return $calculator.getCalculated();
53 | }
54 | };
55 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "geezify-js",
3 | "version": "1.1.2",
4 | "description": "library to convert ascii number like 3456 to geez number ፴፬፻፶፮ and vise versa",
5 | "main": "src/Geezify.js",
6 | "unpkg": "dist/geezify.min.js",
7 | "jsdelivr": "dist/geezify.min.js",
8 | "scripts": {
9 | "build": "webpack --mode=production --config webpack.config.js",
10 | "test": "nyc --reporter=html --reporter=text mocha",
11 | "coverage": "nyc report --reporter=text-lcov | coveralls"
12 | },
13 | "repository": {
14 | "type": "git",
15 | "url": "git+https://github.com/geezify/geezify-js.git"
16 | },
17 | "keywords": [
18 | "geezify",
19 | "ethiopian",
20 | "number",
21 | "geez"
22 | ],
23 | "author": "Sam As End <4sam21@gmail.com>",
24 | "license": "MIT",
25 | "bugs": {
26 | "url": "https://github.com/geezify/geezify-js/issues"
27 | },
28 | "homepage": "https://github.com/geezify/geezify-js#readme",
29 | "devDependencies": {
30 | "@babel/core": "^7.5.5",
31 | "@babel/preset-env": "^7.5.5",
32 | "babel-loader": "^8.0.6",
33 | "coveralls": "^3.0.6",
34 | "eslint": "^5.16.0",
35 | "eslint-config-airbnb-base": "^13.2.0",
36 | "eslint-plugin-import": "^2.18.2",
37 | "mocha": "^5.1.1",
38 | "nyc": "^14.1.1",
39 | "sinon": "^5.0.7",
40 | "webpack": "^4.39.2",
41 | "webpack-cli": "^3.3.7"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Geezify.js:
--------------------------------------------------------------------------------
1 | const GeezConverter = require('./Converter/GeezConverter');
2 | const AsciiConverter = require('./Converter/AsciiConverter');
3 |
4 | /**
5 | * Geezify converts numbers in ASCII to Geez and vise versa.
6 | *
7 | * @author Sam As End <4sam21{at}gmail.com>
8 | */
9 | module.exports = class Geezify {
10 | /**
11 | * Geezify constructor.
12 | *
13 | * @param geezConverter
14 | * @param asciiConverter
15 | */
16 | constructor(geezConverter, asciiConverter) {
17 | this.geez_converter = geezConverter;
18 | this.ascii_converter = asciiConverter;
19 | }
20 |
21 | /**
22 | * Return a new Geezify instance.
23 | *
24 | * @return Geezify
25 | */
26 | static create() {
27 | return new Geezify(new GeezConverter(), new AsciiConverter());
28 | }
29 |
30 | /**
31 | * Converts ASCII number to geez.
32 | *
33 | * @param $ascii_number
34 | *
35 | * @throws \Geezify\Exception\NotAnIntegerArgumentException
36 | *
37 | * @return string
38 | */
39 | toGeez($ascii_number) {
40 | return this.geez_converter.convert($ascii_number);
41 | }
42 |
43 | /**
44 | * Convert geez to ASCII.
45 | *
46 | * @param string $geez_number
47 | *
48 | * @throws \Geezify\Exception\NotGeezArgumentException
49 | *
50 | * @return int
51 | */
52 | toAscii($geez_number) {
53 | return this.ascii_converter.convert($geez_number);
54 | }
55 |
56 | /**
57 | * @return GeezConverter
58 | */
59 | getGeezConverter() {
60 | return this.geez_converter;
61 | }
62 |
63 | /**
64 | * @param GeezConverter $geez_converter
65 | */
66 | setGeezConverter($geez_converter) {
67 | this.geez_converter = $geez_converter;
68 | }
69 |
70 | /**
71 | * @return AsciiConverter
72 | */
73 | getAsciiConverter() {
74 | return this.ascii_converter;
75 | }
76 |
77 | /**
78 | * @param AsciiConverter $ascii_converter
79 | */
80 | setAsciiConverter($ascii_converter) {
81 | this.ascii_converter = $ascii_converter;
82 | }
83 | };
84 |
--------------------------------------------------------------------------------
/test/GeezifyTest.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 |
3 | const sinon = require('sinon');
4 |
5 | const Geezify = require('../src/Geezify');
6 | const GeezConverter = require('../src/Converter/GeezConverter');
7 | const AsciiConverter = require('../src/Converter/AsciiConverter');
8 |
9 | /* global describe, it */
10 |
11 | describe('GeezifyTest', () => {
12 | describe('#test_random_numbers()', () => {
13 | it('should convert random ascii number to geez and back to the original', () => {
14 | const $geezify = Geezify.create();
15 |
16 | for (let $i = 0; $i < 10000; $i += 1) {
17 | const $random_number = Math.floor(Math.random() * 9999999999);
18 |
19 | const $geez_number = $geezify.toGeez($random_number);
20 | const $ascii_number = $geezify.toAscii($geez_number);
21 |
22 | assert.equal($random_number, $ascii_number);
23 | }
24 | });
25 | });
26 |
27 | describe('#test_geezify_build_process()', () => {
28 | it('should use provided ascii and geez number converters', () => {
29 | const $geez = new GeezConverter();
30 | const $ascii = new AsciiConverter();
31 |
32 | sinon
33 | .stub($geez, 'convert')
34 | .withArgs(123)
35 | .returns('giber gaber');
36 | sinon
37 | .stub($ascii, 'convert')
38 | .withArgs('lorem ipsum')
39 | .returns(321);
40 |
41 | const $geezify = new Geezify($geez, $ascii);
42 |
43 | // assert the response
44 | assert.equal(321, $geezify.toAscii('lorem ipsum'));
45 | assert.equal('giber gaber', $geezify.toGeez(123));
46 | });
47 | });
48 |
49 | describe('#test_setter_and_getters()', () => {
50 | it('should be able to substitute implementations', () => {
51 | const $geezify = Geezify.create();
52 |
53 | const $geez_dummy = sinon.createStubInstance(GeezConverter);
54 | const $ascii_dummy = sinon.createStubInstance(AsciiConverter);
55 |
56 | $geezify.setGeezConverter($geez_dummy);
57 | $geezify.setAsciiConverter($ascii_dummy);
58 |
59 | assert.equal($geez_dummy, $geezify.getGeezConverter());
60 | assert.equal($ascii_dummy, $geezify.getAsciiConverter());
61 | });
62 | });
63 | });
64 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Geezify-js 
2 | ==========
3 |
4 | [](https://travis-ci.org/geezify/geezify-js)
5 | [](https://scrutinizer-ci.com/g/geezify/geezify-js/?branch=master)
6 | [](https://coveralls.io/github/geezify/geezify-js?branch=master)
7 | [](https://www.npmjs.com/package/geezify-js)
8 | [](https://www.npmjs.com/package/geezify-js)
9 |
10 |
11 | This package is a library to convert ascii number like '**3456**' to geez number '**፴፬፻፶፮**' and vise versa.
12 |
13 | > Ge'ez (ግዕዝ) is an ancient South Semitic language that originated in Eritrea and the northern region of Ethiopia in the Horn of Africa. It later became the official language of the Kingdom of Aksum and Ethiopian imperial court.
14 |
15 | click [here](https://en.wikipedia.org/wiki/Ge%27ez) to read more.
16 |
17 | Installation
18 | ------------
19 |
20 | ### Node
21 | ```sh
22 | npm install geezify-js
23 | ```
24 |
25 | ### Browser
26 | ```html
27 |
28 | ```
29 |
30 | Usage
31 | ----------------
32 | ```js
33 | const Geezify = require("geezify-js") // For Node only
34 |
35 | geez = Geezify.create();
36 |
37 | console.log(geez.toGeez(123)); // ፻፳፫
38 | console.log(geez.toGeez(1234)); // ፲፪፻፴፬
39 | console.log(geez.toGeez(1986)); // ፲፱፻፹፮
40 | console.log(geez.toGeez(1000000)); // ፻፼
41 |
42 | // or you can even do the reverse
43 | // this is the tricky part you wouldn't see else where
44 | // at least for now
45 |
46 | console.log(geez.toAscii('፻፳፫')); // 123
47 | console.log(geez.toAscii('፲፪፻፴፬')); // 1234
48 | console.log(geez.toAscii('፲፱፻፹፮')); // 1986
49 | console.log(geez.toAscii('፻፼')); // 1000000
50 | ```
51 |
52 | License
53 | -------
54 | Geezify-js is released under the MIT Licence. See the bundled LICENSE file for details.
55 |
--------------------------------------------------------------------------------
/src/Converter/Converter.js:
--------------------------------------------------------------------------------
1 | // i don't want readers to think it's a white
2 | // space, it's just an empty string
3 | const EMPTY_CHARACTER = '';
4 |
5 | const GEEZ_NUMBERS = {
6 | 0: '',
7 | 1: '፩',
8 | 2: '፪',
9 | 3: '፫',
10 | 4: '፬',
11 | 5: '፭',
12 | 6: '፮',
13 | 7: '፯',
14 | 8: '፰',
15 | 9: '፱',
16 | 10: '፲',
17 | 20: '፳',
18 | 30: '፴',
19 | 40: '፵',
20 | 50: '፶',
21 | 60: '፷',
22 | 70: '፸',
23 | 80: '፹',
24 | 90: '፺',
25 | 100: '፻',
26 | 10000: '፼',
27 | };
28 |
29 | /**
30 | * Converter class provide the base functionality
31 | * for the Ascii and Geez converters.
32 | *
33 | * @author Sam As End <4sam21{at}gmail.com>
34 | */
35 | module.exports = class Converter {
36 | /**
37 | * Check if a number is strictly ZERO.
38 | *
39 | * @return boolean if true it's zero
40 | * @param $number
41 | */
42 | isZero($number) {
43 | return $number === 0;
44 | }
45 |
46 | /**
47 | * Checks if the number is ፻.
48 | *
49 | * @return bool
50 | * @param $geez_number
51 | */
52 | isGeezNumberHundred($geez_number) {
53 | return this.isGeezNumber($geez_number, 100);
54 | }
55 |
56 | /**
57 | * Checks if the geez number character is equal to ascii number.
58 | *
59 | * @return boolean
60 | * @param $geez_number
61 | * @param $number
62 | */
63 | isGeezNumber($geez_number, $number) {
64 | return $geez_number === Converter.GEEZ_NUMBERS[$number];
65 | }
66 |
67 | /**
68 | * Checks if the number is ፩.
69 | *
70 | * @param $geez_number
71 | * @return bool
72 | */
73 | isGeezNumberOne($geez_number) {
74 | return this.isGeezNumber($geez_number, 1);
75 | }
76 |
77 | /**
78 | * Checks if the number is ፼
79 | *
80 | * @param $geez_number
81 | * @return bool
82 | */
83 | isGeezNumberTenThousand($geez_number) {
84 | return this.isGeezNumber($geez_number, 10000);
85 | }
86 | };
87 |
88 | Object.defineProperty(module.exports, 'EMPTY_CHARACTER', {
89 | value: EMPTY_CHARACTER,
90 | writable: false,
91 | enumerable: true,
92 | configurable: false,
93 | });
94 |
95 | Object.defineProperty(module.exports, 'GEEZ_NUMBERS', {
96 | value: GEEZ_NUMBERS,
97 | writable: false,
98 | enumerable: true,
99 | configurable: false,
100 | });
101 |
102 | // module.exports.EMPTY_CHARACTER = EMPTY_CHARACTER;
103 | // module.exports.GEEZ_NUMBERS = GEEZ_NUMBERS;
104 |
--------------------------------------------------------------------------------
/test/TestCase.js:
--------------------------------------------------------------------------------
1 | module.exports = class TestCase {
2 | static geezNumberTestDataProvider() {
3 | return [
4 | [1, '፩'],
5 | [10, '፲'],
6 | [100, '፻'],
7 | [1000, '፲፻'],
8 | [10000, '፼'], // (እልፍ)
9 | [100000, '፲፼'], // (አእላፍ)
10 | [1000000, '፻፼'], // (አእላፋት)
11 | [10000000, '፲፻፼'], // (ትእልፊት)
12 | [100000000, '፼፼'], // (ትእልፊታት)
13 | [1000000000, '፲፼፼'],
14 | [10000000000, '፻፼፼'],
15 | [100000000000, '፲፻፼፼'], // (ምእልፊት)
16 | [1000000000000, '፼፼፼'], // (ምእልፊታት)
17 | [100010000, '፼፩፼'],
18 | [100100000, '፼፲፼'],
19 | [100200000, '፼፳፼'],
20 | [100110000, '፼፲፩፼'],
21 | [1, '፩'],
22 | [11, '፲፩'],
23 | [111, '፻፲፩'],
24 | [1111, '፲፩፻፲፩'],
25 | [11111, '፼፲፩፻፲፩'],
26 | [111111, '፲፩፼፲፩፻፲፩'],
27 | [1111111, '፻፲፩፼፲፩፻፲፩'],
28 | [11111111, '፲፩፻፲፩፼፲፩፻፲፩'],
29 | [111111111, '፼፲፩፻፲፩፼፲፩፻፲፩'],
30 | [1111111111, '፲፩፼፲፩፻፲፩፼፲፩፻፲፩'],
31 | [11111111111, '፻፲፩፼፲፩፻፲፩፼፲፩፻፲፩'],
32 | [111111111111, '፲፩፻፲፩፼፲፩፻፲፩፼፲፩፻፲፩'],
33 | [1111111111111, '፼፲፩፻፲፩፼፲፩፻፲፩፼፲፩፻፲፩'],
34 | [1, '፩'],
35 | [12, '፲፪'],
36 | [123, '፻፳፫'],
37 | [1234, '፲፪፻፴፬'],
38 | [12345, '፼፳፫፻፵፭'],
39 | [7654321, '፯፻፷፭፼፵፫፻፳፩'],
40 | [17654321, '፲፯፻፷፭፼፵፫፻፳፩'],
41 | [51615131, '፶፩፻፷፩፼፶፩፻፴፩'],
42 | [15161513, '፲፭፻፲፮፼፲፭፻፲፫'],
43 | [10101011, '፲፻፲፼፲፻፲፩'],
44 | [101, '፻፩'],
45 | [1001, '፲፻፩'],
46 | [1010, '፲፻፲'],
47 | [1011, '፲፻፲፩'],
48 | [1100, '፲፩፻'],
49 | [1101, '፲፩፻፩'],
50 | [1111, '፲፩፻፲፩'],
51 | [10001, '፼፩'],
52 | [10010, '፼፲'],
53 | [10100, '፼፻'],
54 | [10101, '፼፻፩'],
55 | [10110, '፼፻፲'],
56 | [10111, '፼፻፲፩'],
57 | [100001, '፲፼፩'],
58 | [100010, '፲፼፲'],
59 | [100011, '፲፼፲፩'],
60 | [100100, '፲፼፻'],
61 | [101010, '፲፼፲፻፲'],
62 | [1000001, '፻፼፩'],
63 | [1000101, '፻፼፻፩'],
64 | [1000100, '፻፼፻'],
65 | [1010000, '፻፩፼'],
66 | [1010001, '፻፩፼፩'],
67 | [1100001, '፻፲፼፩'],
68 | [1010101, '፻፩፼፻፩'],
69 | [101010101, '፼፻፩፼፻፩'],
70 | [100010000, '፼፩፼'],
71 | [100010100, '፼፩፼፻'],
72 | [101010100, '፼፻፩፼፻'],
73 | [3, '፫'],
74 | [30, '፴'],
75 | [33, '፴፫'],
76 | [303, '፫፻፫'],
77 | [3003, '፴፻፫'],
78 | [3030, '፴፻፴'],
79 | [3033, '፴፻፴፫'],
80 | [3300, '፴፫፻'],
81 | [3303, '፴፫፻፫'],
82 | [3333, '፴፫፻፴፫'],
83 | [30003, '፫፼፫'],
84 | [30303, '፫፼፫፻፫'],
85 | [300003, '፴፼፫'],
86 | [303030, '፴፼፴፻፴'],
87 | [3000003, '፫፻፼፫'],
88 | [3000303, '፫፻፼፫፻፫'],
89 | [3030003, '፫፻፫፼፫'],
90 | [3300003, '፫፻፴፼፫'],
91 | [3030303, '፫፻፫፼፫፻፫'],
92 | [303030303, '፫፼፫፻፫፼፫፻፫'],
93 | [333333333, '፫፼፴፫፻፴፫፼፴፫፻፴፫'],
94 | ];
95 | }
96 |
97 | static invalidNumberDataProvider() {
98 | return [
99 | [0],
100 | [11.11],
101 | ['2a3'],
102 | ['11.11'],
103 | ['lorem ipsum'],
104 | [false],
105 | [[]],
106 | [{}],
107 | ['፷፭X፲፯'],
108 | ['፲፯ lorem ፷፭ ipsum ፳፩'],
109 | ];
110 | }
111 | };
112 |
--------------------------------------------------------------------------------
/src/Helper/GeezParser.js:
--------------------------------------------------------------------------------
1 | const Converter = require('../Converter/Converter');
2 | const NotGeezArgumentException = require('../Exception/NotGeezArgumentException');
3 |
4 | /**
5 | * GeezParser parse the geez number to a queue.
6 | */
7 | module.exports = class GeezParser {
8 | /**
9 | * GeezParser constructor.
10 | *
11 | * @param $geez_number
12 | *
13 | * @throws NotGeezArgumentException
14 | */
15 | constructor($geez_number) {
16 | this.setGeezNumber($geez_number);
17 | this.parsed = null;
18 | }
19 |
20 | /**
21 | * @param $geez_number
22 | *
23 | * @throws NotGeezArgumentException
24 | */
25 | setGeezNumber($geez_number) {
26 | if (typeof $geez_number !== typeof 'something') {
27 | throw new NotGeezArgumentException(typeof $geez_number);
28 | }
29 |
30 | this.geez_number = $geez_number;
31 | }
32 |
33 | getParsed() {
34 | return this.parsed;
35 | }
36 |
37 | /**
38 | * Swing the magic wand and say the spell.
39 | */
40 | parse() {
41 | this.parsed = [];
42 |
43 | let $block = 0;
44 |
45 | const $length = this.getLength(this.geez_number);
46 |
47 | for (let $index = 0; $index < $length; $index += 1) {
48 | $block = this.parseCharacter($index, $block);
49 | }
50 |
51 | this.pushToQueue($block, 1);
52 | }
53 |
54 | /**
55 | * Get the length of the string.
56 | *
57 | * @param $geez_number
58 | *
59 | * @return int
60 | */
61 | getLength($geez_number) {
62 | return $geez_number.length;
63 | }
64 |
65 | /**
66 | * Parse a geez character.
67 | *
68 | * @param $index integer
69 | * @param $block integer
70 | *
71 | * @throws \Geezify\Exception\NotGeezArgumentException
72 | */
73 | parseCharacter($index, $block) {
74 | const $ascii_number = this.parseGeezAtIndex($index);
75 |
76 | /* eslint-disable no-param-reassign */
77 | if (this.isNotGeezSeparator($ascii_number)) {
78 | $block += $ascii_number;
79 | } else {
80 | this.pushToQueue($block, $ascii_number);
81 | $block = 0;
82 | }
83 | /* eslint-enable no-param-reassign */
84 |
85 | return $block;
86 | }
87 |
88 | /**
89 | * Get the ascii number from geez number string.
90 | *
91 | * @param $index
92 | *
93 | * @throws \Geezify\Exception\NotGeezArgumentException
94 | *
95 | * @return int
96 | */
97 | parseGeezAtIndex($index) {
98 | const $geez_char = this.getCharacterAt(this.geez_number, $index);
99 |
100 | return parseInt(this.getAsciiNumber($geez_char), 10);
101 | }
102 |
103 | /**
104 | * Fetch z character at $index from the geez number string.
105 | *
106 | * @param $geez_number
107 | * @param $index
108 | *
109 | * @return string
110 | */
111 | getCharacterAt($geez_number, $index) {
112 | return $geez_number.charAt($index);
113 | }
114 |
115 | /**
116 | * Convert geez number character to ascii.
117 | *
118 | * @param $geez_number
119 | *
120 | * @throws NotGeezArgumentException
121 | *
122 | * @return int
123 | */
124 | getAsciiNumber($geez_number) {
125 | const $ascii_number = Object.values(Converter.GEEZ_NUMBERS).indexOf($geez_number);
126 |
127 | if ($ascii_number === -1) {
128 | throw new NotGeezArgumentException($geez_number);
129 | }
130 |
131 | return Object.keys(Converter.GEEZ_NUMBERS)[$ascii_number];
132 | }
133 |
134 | /**
135 | * @param $ascii_number
136 | *
137 | * @return bool
138 | */
139 | isNotGeezSeparator($ascii_number) {
140 | return $ascii_number < 99;
141 | }
142 |
143 | /**
144 | * Push to the queue.
145 | *
146 | * @param $block
147 | * @param $separator
148 | */
149 | pushToQueue($block, $separator) {
150 | this.parsed.push({ block: $block, separator: $separator });
151 | }
152 | };
153 |
--------------------------------------------------------------------------------
/src/Helper/GeezCalculator.js:
--------------------------------------------------------------------------------
1 | const ONE = 1;
2 | const HUNDRED = 100;
3 | const TEN_THOUSAND = 10000;
4 |
5 | /**
6 | * GeezCalculator calculate the ascii number from the parsed queue.
7 | *
8 | * @author Sam As End <4sam21{at}gmail.com>
9 | */
10 | module.exports = class GeezCalculator {
11 | constructor($queue) {
12 | this.queue = $queue;
13 | this.total = 0;
14 | this.sub_total = 0;
15 | }
16 |
17 | /**
18 | * Do the magic.
19 | */
20 | calculate() {
21 | this.resetSubTotalToZero();
22 |
23 | this.queue.forEach(($token) => {
24 | this.processToken($token);
25 | });
26 | }
27 |
28 | /**
29 | * set the sub total attribute to zero.
30 | */
31 | resetSubTotalToZero() {
32 | this.sub_total = 0;
33 | }
34 |
35 | /**
36 | * Process a single token from the Queue.
37 | *
38 | * @param $token
39 | */
40 | processToken($token) {
41 | const $block = $token.block;
42 | const $separator = $token.separator;
43 |
44 | this.processBySeparator($block, $separator);
45 | }
46 |
47 | /**
48 | * Process based on separator.
49 | *
50 | * @param $block
51 | * @param $separator
52 | */
53 | processBySeparator($block, $separator) {
54 | switch ($separator) {
55 | case ONE:
56 | this.addToTotal($block);
57 | break;
58 | case HUNDRED:
59 | this.updateSubTotal($block);
60 | break;
61 | case TEN_THOUSAND:
62 | default:
63 | this.updateTotal($block);
64 | break;
65 | }
66 | }
67 |
68 | /**
69 | * Add the sub total and the block to total
70 | * and reset sub total to zero.
71 | *
72 | * @param $block
73 | *
74 | * @return void
75 | */
76 | addToTotal($block) {
77 | this.total += this.sub_total + $block;
78 | this.resetSubTotalToZero();
79 | }
80 |
81 | /**
82 | * Is the leading block?
83 | *
84 | * @param $block
85 | *
86 | * @return bool
87 | */
88 | isLeading($block) {
89 | return this.isBlockZero($block) && this.isSubtotalZero();
90 | }
91 |
92 | /**
93 | * Is the value of block zero?
94 | *
95 | * @param $block
96 | *
97 | * @return bool
98 | */
99 | isBlockZero($block) {
100 | return this.isZero($block);
101 | }
102 |
103 | /**
104 | * Is a number zero?
105 | *
106 | * @param $number
107 | *
108 | * @return boolean
109 | */
110 | isZero($number) {
111 | return $number === 0;
112 | }
113 |
114 | /**
115 | * Is sub total attribute zero?
116 | *
117 | * @return bool
118 | */
119 | isSubtotalZero() {
120 | return this.isZero(this.sub_total);
121 | }
122 |
123 | /**
124 | * Add number to sun total.
125 | *
126 | * @param $number integer
127 | */
128 | addToSubTotal($number) {
129 | this.sub_total += $number;
130 | }
131 |
132 | /**
133 | * Is the leading 10k?
134 | *
135 | * @param $block
136 | *
137 | * @return bool
138 | */
139 | isLeadingTenThousand($block) {
140 | return this.isTotalZero() && this.isLeading($block);
141 | }
142 |
143 | /**
144 | * Is the total attribute zero?
145 | *
146 | * @return bool
147 | */
148 | isTotalZero() {
149 | return this.isZero(this.total);
150 | }
151 |
152 | /**
153 | * Multiply the total attribute by ten thousand.
154 | */
155 | multiplyTotalBy10k() {
156 | this.total *= TEN_THOUSAND;
157 | }
158 |
159 | /**
160 | * Return the calculated ascii number.
161 | *
162 | * @return int
163 | */
164 | getCalculated() {
165 | return this.total;
166 | }
167 |
168 | /**
169 | * Update the sub total attribute.
170 | *
171 | * @param $block
172 | */
173 | updateSubTotal($block) {
174 | /* eslint-disable no-param-reassign */
175 | if (this.isLeading($block)) {
176 | $block = ONE;
177 | }
178 |
179 | $block *= HUNDRED;
180 | /* eslint-enable no-param-reassign */
181 |
182 | this.addToSubTotal($block);
183 | }
184 |
185 | /**
186 | * Update the sub total attribute.
187 | *
188 | * @param $block
189 | */
190 | updateTotal($block) {
191 | if (this.isLeadingTenThousand($block)) {
192 | // eslint-disable-next-line no-param-reassign
193 | $block = ONE;
194 | }
195 |
196 | this.addToTotal($block);
197 | this.multiplyTotalBy10k();
198 | }
199 | };
200 |
--------------------------------------------------------------------------------
/src/Converter/GeezConverter.js:
--------------------------------------------------------------------------------
1 | const Converter = require('./Converter');
2 | const NotAnIntegerArgumentException = require('../Exception/NotAnIntegerArgumentException');
3 |
4 | /**
5 | * GeezConverter converts ascii number like 1986
6 | * to equivalent geez number like ፲፱፻፹፮.
7 | *
8 | * @author Sam As End <4sam21{at}gmail.com>
9 | */
10 | module.exports = class GeezConverter extends Converter {
11 | /**
12 | * Convert an ascii number like 1, 21, 3456 to
13 | * geez number ፩, ፳፩, ፴፬፻፶፮.
14 | *
15 | *
16 | * @throws NotAnIntegerArgumentException if the number is not an integer
17 | * @return string
18 | * @param $ascii_number
19 | */
20 | convert($ascii_number) {
21 | const $number = this.prepareForConversion($ascii_number);
22 | const $length = $number.length;
23 | let $result = Converter.EMPTY_CHARACTER;
24 |
25 | for (let $index = 0; $index < $length; $index += 2) {
26 | $result += this.parseEachTwoCharactersBlock($number, $index, $length);
27 | }
28 |
29 | return $result;
30 | }
31 |
32 | /**
33 | * - Validate the number
34 | * - Convert the number to a string
35 | * - Get the length of the number
36 | * - Prepend a space if the length is odd.
37 | *
38 | * @param $ascii_number
39 | *
40 | * @throws \Geezify\Exception\NotAnIntegerArgumentException
41 | *
42 | * @return array the $number and the $length
43 | */
44 | prepareForConversion($ascii_number) {
45 | let $validated_number = this.validateAsciiNumber($ascii_number);
46 |
47 | $validated_number = `${$validated_number}`;
48 |
49 | const $length = $validated_number.length;
50 |
51 | const $number = this.prependSpaceIfLengthIsEven($validated_number, $length);
52 |
53 | return $number;
54 | }
55 |
56 | /**
57 | * Validate if the number is ascii number.
58 | *
59 | * @param $ascii_number
60 | *
61 | * @throws NotAnIntegerArgumentException
62 | * @return int
63 | */
64 | validateAsciiNumber($ascii_number) {
65 | const number = parseInt($ascii_number, 10);
66 |
67 | if (Number.isNaN(number) || `${number}` !== `${$ascii_number}` || number < 1) {
68 | throw new NotAnIntegerArgumentException($ascii_number);
69 | }
70 |
71 | return $ascii_number;
72 | }
73 |
74 | /**
75 | * Prepend space if the length of the number is odd.
76 | *
77 | * @param $ascii_number
78 | * @param $length
79 | *
80 | * @return string
81 | */
82 | prependSpaceIfLengthIsEven($ascii_number, $length) {
83 | if (this.isOdd($length)) {
84 | return ` ${$ascii_number}`;
85 | }
86 |
87 | return $ascii_number;
88 | }
89 |
90 | /**
91 | * Is a number odd?
92 | *
93 | * @param $ascii_number
94 | *
95 | * @return bool
96 | */
97 | isOdd($ascii_number) {
98 | return !this.isEven($ascii_number);
99 | }
100 |
101 | /**
102 | * Is a number even?
103 | *
104 | * @param $number
105 | *
106 | * @return boolean
107 | */
108 | isEven($number) {
109 | return $number % 2 === 0;
110 | }
111 |
112 | /**
113 | * Parse each two character block.
114 | *
115 | * @param $number
116 | * @param $index
117 | * @param $length
118 | *
119 | * @return string
120 | */
121 | parseEachTwoCharactersBlock($number, $index, $length) {
122 | const $geez_number = this.getGeezNumberOfTheBlock($number, $index);
123 |
124 | const $bet = this.getBet($length, $index);
125 |
126 | const $geez_separator = this.getGeezSeparator($bet);
127 |
128 | return this.combineBlockAndSeparator($geez_number, $geez_separator, $index);
129 | }
130 |
131 | /**
132 | * Fetch the two character (00-99) block and convert it to geez.
133 | *
134 | * @param $number
135 | * @param $index
136 | *
137 | * @return string geez two character block
138 | */
139 | getGeezNumberOfTheBlock($number, $index) {
140 | const $block = this.getBlock($number, $index);
141 |
142 | const $tenth = Number.isNaN(parseInt($block[0], 10)) ? 0 : parseInt($block[0], 10);
143 | const $once = parseInt($block[1], 10);
144 |
145 | return Converter.GEEZ_NUMBERS[$tenth * 10] + Converter.GEEZ_NUMBERS[$once];
146 | }
147 |
148 | /**
149 | * Fetch two characters from the $number starting from $index.
150 | *
151 | * @param $number string the whole ascii number
152 | * @param $index integer the starting position
153 | *
154 | * @return string
155 | */
156 | getBlock($number, $index) {
157 | return $number.substr($index, 2);
158 | }
159 |
160 | /**
161 | * The ቤት of the block.
162 | *
163 | * @param $length integer the length of the ascii number
164 | * @param $index integer the character index
165 | *
166 | * @return int
167 | */
168 | getBet($length, $index) {
169 | const $reverse_index = $length - 1 - $index;
170 |
171 | return Math.floor($reverse_index / 2);
172 | }
173 |
174 | /**
175 | * Get the separator depending on the bet.
176 | *
177 | * @param $bet
178 | *
179 | * @return string return ፻,፼ or empty character
180 | */
181 | getGeezSeparator($bet) {
182 | if (this.isZero($bet)) {
183 | return Converter.EMPTY_CHARACTER;
184 | }
185 | if (this.isOdd($bet)) {
186 | return Converter.GEEZ_NUMBERS[100];
187 | }
188 |
189 | return Converter.GEEZ_NUMBERS[10000];
190 | }
191 |
192 | /**
193 | * Combines the block and the separator.
194 | *
195 | *
196 | * @return string
197 | * @param $geez_number
198 | * @param $separator
199 | * @param $index
200 | */
201 | combineBlockAndSeparator($geez_number, $separator, $index) {
202 | /* eslint-disable no-param-reassign */
203 | if (this.shouldRemoveGeezSeparator($geez_number, $separator)) {
204 | $separator = Converter.EMPTY_CHARACTER;
205 | }
206 |
207 | if (this.shouldRemoveGeezNumberBlock($geez_number, $separator, $index)) {
208 | $geez_number = Converter.EMPTY_CHARACTER;
209 | }
210 | /* eslint-enable no-param-reassign */
211 |
212 | return $geez_number + $separator;
213 | }
214 |
215 | /**
216 | * Returns true if the block is empty and the separator is 100.
217 | *
218 | * @return bool
219 | * @param $block
220 | * @param $separator
221 | */
222 | shouldRemoveGeezSeparator($block, $separator) {
223 | return $block.trim().length === 0 && this.isGeezNumberHundred($separator);
224 | }
225 |
226 | /**
227 | * Returns true if the ascii number is 100 or
228 | * if the ascii number is the leading 10000.
229 | *
230 | * @param $block
231 | * @param $separator
232 | * @param $index
233 | *
234 | * @return bool
235 | */
236 | shouldRemoveGeezNumberBlock($block, $separator, $index) {
237 | return (
238 | this.isOneHundred($block, $separator) || this.isLeadingTenThousand($block, $separator, $index)
239 | );
240 | }
241 |
242 | /**
243 | * Returns true if the number is 100.
244 | *
245 | * @param $block
246 | * @param $separator
247 | *
248 | * @return bool
249 | */
250 | isOneHundred($block, $separator) {
251 | return this.isGeezNumberHundred($separator) && this.isGeezNumberOne($block);
252 | }
253 |
254 | /**
255 | * Returns true if the number is the leading 10000.
256 | *
257 | * @param $block
258 | * @param $separator
259 | * @param $index
260 | *
261 | * @return bool
262 | */
263 | isLeadingTenThousand($block, $separator, $index) {
264 | return (
265 | this.isZero($index)
266 | && this.isGeezNumberOne($block)
267 | && this.isGeezNumberTenThousand($separator)
268 | );
269 | }
270 | };
271 |
--------------------------------------------------------------------------------
/dist/geezify.min.js:
--------------------------------------------------------------------------------
1 | !function(e){var t={};function n(r){if(t[r])return t[r].exports;var o=t[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,r){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:r})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(r,o,function(t){return e[t]}.bind(null,o));return r},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=1)}([function(e,t){function n(e,t){for(var n=0;n