├── .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 ![From Ethiopia](https://img.shields.io/badge/From-Ethiopia-brightgreen.svg) 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/geezify/geezify-js.svg?branch=master)](https://travis-ci.org/geezify/geezify-js) 5 | [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/geezify/geezify-js/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/geezify/geezify-js/?branch=master) 6 | [![Coverage Status](https://coveralls.io/repos/github/geezify/geezify-js/badge.svg?branch=master)](https://coveralls.io/github/geezify/geezify-js?branch=master) 7 | [![NPM Download](https://img.shields.io/npm/dt/geezify-js.svg?style=flat)](https://www.npmjs.com/package/geezify-js) 8 | [![npm](https://img.shields.io/npm/v/geezify-js.svg)](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