├── .gitignore ├── README.md ├── src ├── string │ ├── match.ts │ ├── __tests__ │ │ ├── reverse.spec.ts │ │ └── format.spec.ts │ ├── reverse.ts │ └── format.ts ├── __tests__ │ └── formatMoney.spec.ts └── formatMoney.ts ├── tsconfig.json ├── test-preprocessor.js ├── package.json └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/ 2 | node_modules/ 3 | target/ 4 | *.log -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Function Composition in Typescript 2 | 3 | The example of function composition in Typescript with Lodash. 4 | 5 | ## How to run 6 | ``` 7 | npm i 8 | npm test 9 | ``` -------------------------------------------------------------------------------- /src/string/match.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * String#match 3 | * 4 | * @param {string} string 5 | * @param {RegExp} regexp 6 | */ 7 | export default function match(string:string, regexp: RegExp):Array { 8 | return string.match(regexp) || []; 9 | } 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "sourceMap": true, 4 | "strictNullChecks": true, 5 | "skipLibCheck": true, 6 | "module": "commonjs", 7 | "target": "es6" 8 | }, 9 | "include": [ 10 | "./src/**/*" 11 | ], 12 | "exclude": [ 13 | "node_modules" 14 | ] 15 | } -------------------------------------------------------------------------------- /test-preprocessor.js: -------------------------------------------------------------------------------- 1 | const tsc = require('typescript'); 2 | const tsConfig = require('./tsconfig.json'); 3 | 4 | module.exports = { 5 | process(src, path) { 6 | if (path.endsWith('.ts') || path.endsWith('.tsx') || path.endsWith('.js')) { 7 | return tsc.transpile(src, tsConfig.compilerOptions, path, []); 8 | } 9 | return src; 10 | }, 11 | }; -------------------------------------------------------------------------------- /src/string/__tests__/reverse.spec.ts: -------------------------------------------------------------------------------- 1 | import reverse from "../reverse"; 2 | 3 | describe("string#reverse", ():void => { 4 | it("reverses string", ():void => { 5 | expect(reverse("abcdef")).toBe("fedcba"); 6 | expect(reverse("adam")).toBe("mada"); 7 | }) 8 | 9 | it("keeps upper letters", ():void => { 10 | expect(reverse("Adam")).toBe("madA"); 11 | expect(reverse("Mateusz")).toBe("zsuetaM"); 12 | }) 13 | 14 | it("retrieves empty string when given empty string or null", ():void => { 15 | expect(reverse("")).toBe(""); 16 | expect(reverse(null)).toBe(""); 17 | 18 | }) 19 | }) -------------------------------------------------------------------------------- /src/string/__tests__/format.spec.ts: -------------------------------------------------------------------------------- 1 | import format from "../format"; 2 | 3 | describe("string#format", ():void => { 4 | it("is a function factory", ():void => { 5 | const formatter:Function = format(/a-z/g, ","); 6 | 7 | expect(formatter).toBeInstanceOf(Function) 8 | }) 9 | 10 | it("appends separator to matching string", ():void => { 11 | const formatter:Function = format(/[0-9]{1,3}/g, ","); 12 | 13 | expect(formatter("1000")).toBe("1,000"); 14 | expect(formatter("10100")).toBe("10,100"); 15 | expect(formatter("100000000")).toBe("100,000,000"); 16 | }) 17 | 18 | it("return empty string if no match", ():void => { 19 | const formatter:Function = format(/[0-9]{1,3}/g, ","); 20 | 21 | expect(formatter("")).toBe(""); 22 | }) 23 | }) -------------------------------------------------------------------------------- /src/__tests__/formatMoney.spec.ts: -------------------------------------------------------------------------------- 1 | import formatMoney from "../formatMoney"; 2 | 3 | describe("formatMoney", ():void => { 4 | it("formates positive numbers", ():void => { 5 | expect(formatMoney(0)).toBe("0.00"); 6 | expect(formatMoney(1)).toBe("0.01"); 7 | expect(formatMoney(100)).toBe("1.00"); 8 | expect(formatMoney(1299)).toBe("12.99"); 9 | expect(formatMoney(10000000)).toBe("100,000.00"); 10 | }) 11 | 12 | it("formates negative numbers", ():void => { 13 | expect(formatMoney(-1)).toBe("-0.01"); 14 | expect(formatMoney(-100)).toBe("-1.00"); 15 | expect(formatMoney(-1299)).toBe("-12.99"); 16 | expect(formatMoney(-10000000)).toBe("-100,000.00"); 17 | }) 18 | 19 | it("throws error when non-integer numbers given", ():void => { 20 | expect(():string => formatMoney(0.1)).toThrowError("Number must be integer") 21 | }) 22 | }) -------------------------------------------------------------------------------- /src/string/reverse.ts: -------------------------------------------------------------------------------- 1 | import { curryRight, flow, join, split, reverse } from "lodash"; 2 | 3 | /** 4 | * Reversed curry of lodash#split function. 5 | * 6 | * @example 7 | * ``` 8 | * splitCurry(limit)(separator)(string) 9 | * ``` 10 | * @link https://github.com/lodash/lodash/blob/4.17.4/lodash.js#L6856 11 | */ 12 | const splitCurry:Function = curryRight(split); 13 | 14 | /** 15 | * Reversed curry of lodash#join function. 16 | * 17 | * @example 18 | * ``` 19 | * joinCurry(separator)(string) 20 | * ``` 21 | * @link https://github.com/lodash/lodash/blob/4.17.4/lodash.js#L7608 22 | */ 23 | const joinCurry:Function = curryRight(join); 24 | 25 | /** 26 | * Retrieves reverted string. 27 | * 28 | * @param {string} string 29 | * @returns {string} 30 | */ 31 | export default flow([ 32 | splitCurry(undefined)(""), // "abc" => ["a", "b", "c"] 33 | reverse, // ["a", "b", "c"] => ["c", "b", "a"] 34 | joinCurry(""), // ["c", "b", "a"] => "cba" 35 | ]); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "function-composition-typescript", 3 | "version": "1.0.0", 4 | "description": "a Function Composition Example In Typescript", 5 | "homepage": "https://medium.com/@mateuszsokola", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "./node_modules/.bin/jest" 9 | }, 10 | "jest": { 11 | "moduleFileExtensions": [ 12 | "ts", 13 | "tsx", 14 | "js" 15 | ], 16 | "transform": { 17 | "^.+\\.(ts|tsx)$": "/test-preprocessor.js" 18 | }, 19 | "testMatch": [ 20 | "**/__tests__/*.spec.(ts|tsx|js)" 21 | ] 22 | }, 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/mateuszsokola/function-composition-typescript.git" 26 | }, 27 | "author": "Mateusz Sokola", 28 | "license": "MIT", 29 | "dependencies": { 30 | "@types/lodash": "^4.14.80", 31 | "lodash": "^4.17.4", 32 | "typescript": "^2.5.3" 33 | }, 34 | "devDependencies": { 35 | "@types/jest": "^21.1.5", 36 | "jest": "^21.2.1" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/string/format.ts: -------------------------------------------------------------------------------- 1 | import { curryRight, flow, join } from "lodash"; 2 | 3 | import match from "./match"; 4 | import reverse from "./reverse"; 5 | 6 | /** 7 | * Reversed curry of string#match function. 8 | * 9 | * @example 10 | * ``` 11 | * matchCurry(RegExp)(string) 12 | * ``` 13 | */ 14 | const matchCurry:Function = curryRight(match); 15 | 16 | /** 17 | * Reversed curry of lodash#join function. 18 | * 19 | * @example 20 | * ``` 21 | * joinCurry(separator)(string) 22 | * ``` 23 | * @link https://github.com/lodash/lodash/blob/4.17.4/lodash.js#L7608 24 | */ 25 | const joinCurry:Function = curryRight(join); 26 | 27 | /** 28 | * A Format factory. 29 | * 30 | * @example 31 | * ``` 32 | * format(RegExp, separator)(string) 33 | * ``` 34 | * @param {RegExp} match 35 | * @param {string} string 36 | * @returns {Fuction} 37 | */ 38 | export default function format(match:RegExp, separator:string):Function { 39 | return flow([ 40 | reverse, 41 | matchCurry(match), 42 | joinCurry(separator), 43 | reverse, 44 | ]); 45 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mateusz Sokola 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/formatMoney.ts: -------------------------------------------------------------------------------- 1 | import { join } from "lodash"; 2 | 3 | import format from "./string/format"; 4 | 5 | /** 6 | * Decimal separator 7 | */ 8 | export const decimalSeparator:string = "."; 9 | 10 | /** 11 | * Thousand separator 12 | */ 13 | export const thousandSeparator:string = ","; 14 | 15 | /** 16 | * Thousand Regular Expression 17 | */ 18 | const thousandRegExp:RegExp = /[0-9]{1,3}/g; 19 | 20 | /** 21 | * Dollar formatter 22 | */ 23 | const formatDollars:Function = format(thousandRegExp, thousandSeparator); 24 | 25 | /** 26 | * Converts number to string. If amount of cents is lower than 10, appends "0" in front of the string. 27 | * 28 | * @example 29 | * ``` 30 | * formatCents(0) // 00 31 | * formatCents(9) // 09 32 | * formatCents(25) // 25 33 | * formatCents(250) // 250 34 | * ``` 35 | * @param {number} cents 36 | * @returns {string} 37 | */ 38 | function formatCents(cents:number): string { 39 | if (cents < 10) { 40 | return `0${cents}`; 41 | } 42 | 43 | return `${cents}`; 44 | } 45 | 46 | /** 47 | * Formats given amount (in cents) to the US money format. 48 | * NOTE: Use cents (100 = $1) 49 | * 50 | * @example 51 | * ``` 52 | * formatMoney(999) // 9.99 53 | * formatMoney(129999) // 1,299.99 54 | * ``` 55 | * @param {number} amountInCents 56 | * @returns {string} 57 | */ 58 | export default function formatMoney(amountInCents: number): string { 59 | // throws error if non-integer 60 | if (! Number.isInteger(amountInCents)) { 61 | throw new Error("Number must be integer"); 62 | } 63 | 64 | // absolute number, it supports number below 0. 65 | const inCents = Math.abs(amountInCents); 66 | 67 | // take cents out 68 | const cents:number = inCents % 100; 69 | // dollars = (total - cents) / 100 70 | const dollars:number = (inCents - cents) / 100; 71 | 72 | // format dollars and cents 73 | const moneyString:string = join([formatDollars(dollars), formatCents(cents)], decimalSeparator); 74 | 75 | // if below 0 append "-" in front of the output 76 | const amountChar:string = amountInCents < 0 ? "-" : ""; 77 | 78 | // e.g. -1234.99 79 | return `${amountChar}${moneyString}`; 80 | } 81 | --------------------------------------------------------------------------------