├── .github
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── .swcrc
├── CHANGELOG.md
├── LICENSE
├── README.md
├── jest.config.js
├── package.json
├── scripts
├── copy.js
├── mv.js
└── rm.js
├── src
├── index.ts
├── match-pattern.ts
├── optional-pattern.ts
└── string
│ ├── index.ts
│ └── naming-convention.ts
├── tests
├── match-pattern.test.ts
└── optional-pattern.test.ts
├── tsconfig.cjs.json
└── tsconfig.json
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | name: Build Boost TS
2 |
3 | on:
4 | pull_request:
5 | types: [opened, synchronize, closed]
6 | branches:
7 | - main
8 | push:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | build:
14 | runs-on: ubuntu-latest
15 |
16 | steps:
17 | - name: Checkout code
18 | uses: actions/checkout@v3
19 |
20 | - name: Use Node.js ${{ matrix.node-version }}
21 | uses: actions/setup-node@v3
22 | with:
23 | node-version: '18.10.0'
24 |
25 | - name: Install Dependencies
26 | working-directory: ${{github.workspace}}
27 | run: npm install
28 |
29 | - name: Unit Tests
30 | working-directory: ${{github.workspace}}/tests
31 | run: npm test
32 | env:
33 | CI: true
34 |
35 | # Generate changelog
36 | - name: Generate changelog
37 | id: changelog
38 | working-directory: ${{ github.workspace }}
39 | run: |
40 | # extract the changelog
41 | echo "::set-output name=tag::$(grep -oP '^## \[(\d+\.\d+\.\d+)\]' CHANGELOG.md | head -n 1 | cut -d '[' -f 2 | cut -d ']' -f 1)"
42 | echo v${{ steps.changelog.outputs.tag }}
43 |
44 | # Create git release
45 | - name: Github release
46 | uses: actions/create-release@v1
47 | if: github.event.pull_request.merged == true
48 | env:
49 | GITHUB_TOKEN: ${{ github.token }}
50 | with:
51 | tag_name: v${{ steps.changelog.outputs.tag }}
52 | release_name: ${{ steps.changelog.outputs.tag }}
53 | body: |
54 | Changes in this Release
55 | - ${{ steps.changelog.outputs.tag }}
56 | draft: false
57 | prerelease: false
58 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Regular folders
2 | dist/
3 | node_modules/
4 | lib
5 |
6 | # Ignore lock files
7 | *-lock.json
8 |
9 | # main used for test
10 | main.ts
11 |
12 | # Ignore other files
13 | *.tgz
14 |
15 |
--------------------------------------------------------------------------------
/.swcrc:
--------------------------------------------------------------------------------
1 | {
2 | "jsc": {
3 | "parser": {
4 | "syntax": "typescript",
5 | "decorators": true
6 | },
7 | "transform": {
8 | "legacyDecorator": true,
9 | "decoratorMetadata": true
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## 1.3.0 (2024-8-1)
4 |
5 |
6 | ### Features
7 |
8 | * adjust release script ([8601abf](https://github.com/expressots/boost-ts/commit/8601abf693049009387522ea77a9f0304f62a179))
9 | * update scripts and build system ([#7](https://github.com/expressots/boost-ts/issues/7)) ([ac38096](https://github.com/expressots/boost-ts/commit/ac38096f6f0dc10a4341f5fb85a625f2eb843140))
10 |
11 |
12 | ### Code Refactoring
13 |
14 | * update deps and scripts ([61556dc](https://github.com/expressots/boost-ts/commit/61556dcbdaa9d180778ca9ee499bff26663bc3a9))
15 | * update engine version update ([183d15e](https://github.com/expressots/boost-ts/commit/183d15ea617ce983302b1fe2467c0840c3435881))
16 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Richard Zampieri
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | [![Codecov][codecov-shield]][codecov-url]
6 | [![NPM][npm-shield]][npm-url]
7 | ![Build][build-shield]
8 | [![Contributors][contributors-shield]][contributors-url]
9 | [![Forks][forks-shield]][forks-url]
10 | [![Stargazers][stars-shield]][stars-url]
11 | [![Issues][issues-shield]][issues-url]
12 | [![MIT License][license-shield]][license-url]
13 | [![LinkedIn][linkedin-shield]][linkedin-url]
14 |
15 |
16 |
17 |
37 |
38 |
39 |
40 | Table of Contents
41 |
42 | - About The Project
43 | - Getting Started
44 | - Contributing
45 | - Support the project
46 | - License
47 |
48 |
49 |
50 |
51 |
52 | # About The Project
53 |
54 | ExpressoTS is a [Typescript](https://www.typescriptlang.org/) + [Node.js](https://nodejs.org/en/) lightweight framework for quick building scalable, easy to read and maintain, server-side applications 🐎
55 |
56 | ## Getting Started
57 |
58 | - Here is our [Site](https://expresso-ts.com/)
59 | - You can find our [Documentation here](https://doc.expresso-ts.com/)
60 | - Checkout our [First Steps documentation](https://doc.expresso-ts.com/docs/overview/first-steps)
61 | - Our [CLI Documentation](https://doc.expresso-ts.com/docs/cli/overview)
62 |
63 | ## Boost-TS
64 |
65 | Boost is a collection of libraries for the TypeScript programming language designed to enhance its capabilities across various domains. These libraries provide a wide range of functionality, enabling developers to leverage the full potential of TypeScript and streamline their development process. With Boost, TypeScript developers can efficiently tackle complex tasks, improve code readability, and maintainability, while also benefiting from advanced features and best practices.
66 |
67 | ## Available Libraries
68 |
69 | - Match Pattern
70 | - Optional Pattern
71 |
72 | ## Installation
73 |
74 | ```bash
75 | npm i @expressots/boost-ts
76 | ```
77 |
78 | ## Match Pattern Usage
79 |
80 | - Using Match pattern with enums
81 |
82 | ```typescript
83 | import { match } from "./match-pattern";
84 |
85 | const enum Coin {
86 | Penny,
87 | Nickel,
88 | Dime,
89 | Quarter,
90 | }
91 |
92 | function valueInCents(coin: Coin): number {
93 | return match(coin, {
94 | [Coin.Penny]: () => 1,
95 | [Coin.Nickel]: () => 5,
96 | [Coin.Dime]: () => 10,
97 | [Coin.Quarter]: () => 25,
98 | });
99 | }
100 |
101 | console.log(valueInCents(Coin.Penny)); // 1
102 | ```
103 |
104 | - Using Match pattern with numbers
105 |
106 | ```typescript
107 | let isNumber: number = 1;
108 |
109 | const result = match(isNUmber, {
110 | 1: () => 1,
111 | 2: () => 2,
112 | _: () => "No number found",
113 | });
114 |
115 | console.log(result); // 1
116 | ```
117 |
118 | - Using Match pattern with boolean
119 |
120 | ```typescript
121 | let isOn: boolean = true;
122 |
123 | const result = match(isOn, {
124 | true: () => "The light is on",
125 | false: () => "The light is off",
126 | });
127 |
128 | console.log(result); // The light is off
129 | ```
130 |
131 | - Using Match pattern with Optional pattern
132 |
133 | ```typescript
134 | import { Some, None, Optional, matchOptional } from "./optional-pattern";
135 | import { match } from "./match-pattern";
136 |
137 | const v1: Optional = Some(1);
138 | const v2: Optional = None();
139 |
140 | const result = match(v1, {
141 | Some: (x) => x + 1,
142 | None: 0,
143 | });
144 |
145 | console.log(result); // 2
146 | ```
147 |
148 | - Other possible combinations
149 | - "expressions..=expressions" -> numbers or characters
150 | - "isOr: this | that | other"
151 | - "Regex: "/[a-z]/"
152 |
153 | ```typescript
154 | const result = match("a", {
155 | "1..=13": () => "Between 1 and 13",
156 | "25 | 50 | 100": () => "A bill",
157 | "a..=d": () => "A letter",
158 | "/[a-z]/": () => "A lowercase letter",
159 | _: () => "Default",
160 | });
161 | console.log(result); // A letter
162 | ```
163 |
164 | ## Optional Pattern Usage
165 |
166 | ```typescript
167 | const someValue: Optional = Some(1);
168 |
169 | function plusOne(x: Optional): number {
170 | return match(x, {
171 | Some: (x) => x + 3,
172 | None: 0,
173 | });
174 | }
175 |
176 | console.log(plusOne(None())); // 0
177 | ```
178 |
179 | ## Contributing
180 |
181 | Welcome to the ExpressoTS community, a place bustling with innovative minds just like yours. We're absolutely thrilled to have you here!
182 | ExpressoTS is more than just a TypeScript framework; it's a collective effort by developers who are passionate about creating a more efficient, secure, and robust web ecosystem. We firmly believe that the best ideas come from a diversity of perspectives, backgrounds, and skills.
183 |
184 | Why Contribute to Documentation?
185 |
186 | - **Share Knowledge**: If you've figured out something cool, why keep it to yourself?
187 | - **Build Your Portfolio**: Contributing to an open-source project like ExpressoTS is a great way to showcase your skills.
188 | - **Join a Network**: Get to know a community of like-minded developers.
189 | - **Improve the Product**: Help us fill in the gaps, correct errors, or make complex topics easier to understand.
190 |
191 | Ready to contribute?
192 |
193 | - [Contributing Guidelines](https://github.com/expressots/expressots/blob/main/CONTRIBUTING.md)
194 | - [How to Contribute](https://github.com/expressots/expressots/blob/main/CONTRIBUTING_HOWTO.md)
195 | - [Coding Guidelines](https://github.com/rsaz/TypescriptCodingGuidelines)
196 |
197 | ## Support the project
198 |
199 | ExpressoTS is an independent open source project with ongoing development made possible thanks to your support. If you'd like to help, please consider:
200 |
201 | - Become a **[sponsor on GitHub](https://github.com/sponsors/expressots)**
202 | - Follow the **[organization](https://github.com/expressots)** on GitHub and Star ⭐ the project
203 | - Subscribe to the Twitch channel: **[Richard Zampieri](https://www.twitch.tv/richardzampieri)**
204 | - Join our **[Discord](https://discord.com/invite/PyPJfGK)**
205 | - Contribute submitting **[issues and pull requests](https://github.com/expressots/expressots/issues)**
206 | - Share the project with your friends and colleagues
207 |
208 | ## License
209 |
210 | Distributed under the MIT License. See [`LICENSE.txt`](https://github.com/expressots/expressots/blob/main/LICENSE) for more information.
211 |
212 | (back to top)
213 |
214 |
215 |
216 |
217 | [codecov-url]: https://codecov.io/gh/expressots/boost-ts
218 | [codecov-shield]: https://img.shields.io/codecov/c/gh/expressots/boost-ts/main?style=for-the-badge&logo=codecov&labelColor=FB9AD1
219 | [npm-url]: https://www.npmjs.com/package/@expressots/boost-ts
220 | [npm-shield]: https://img.shields.io/npm/v/@expressots/boost-ts?style=for-the-badge&logo=npm&color=9B3922
221 | [build-shield]: https://img.shields.io/github/actions/workflow/status/expressots/boost-ts/build.yaml?branch=main&style=for-the-badge&logo=github
222 | [contributors-shield]: https://img.shields.io/github/contributors/expressots/boost-ts?style=for-the-badge
223 | [contributors-url]: https://github.com/expressots/boost-ts/graphs/contributors
224 | [forks-shield]: https://img.shields.io/github/forks/expressots/boost-ts?style=for-the-badge
225 | [forks-url]: https://github.com/expressots/boost-ts/forks
226 | [stars-shield]: https://img.shields.io/github/stars/expressots/boost-ts?style=for-the-badge
227 | [stars-url]: https://github.com/expressots/boost-ts/stargazers
228 | [issues-shield]: https://img.shields.io/github/issues/expressots/boost-ts?style=for-the-badge
229 | [issues-url]: https://github.com/expressots/boost-ts/issues
230 | [license-shield]: https://img.shields.io/github/license/expressots/boost-ts?style=for-the-badge
231 | [license-url]: https://github.com/expressots/boost-ts/blob/main/LICENSE
232 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555
233 | [linkedin-url]: https://www.linkedin.com/company/expresso-ts/
234 | [product-screenshot]: images/screenshot.png
235 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | transform: {
3 | "^.+\\.(t|j)sx?$": "@swc/jest",
4 | },
5 | testEnvironment: "node",
6 | testMatch: ["**/*.spec.ts", "**/*.test.ts"],
7 | };
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@expressots/boost-ts",
3 | "version": "1.3.0",
4 | "description": "Expressots: Boost is a collection of libraries for the TypeScript programming language designed to enhance its capabilities across various domains. (@boost-ts)",
5 | "main": "./lib/cjs/index.js",
6 | "types": "./lib/cjs/types/index.d.ts",
7 | "exports": {
8 | ".": {
9 | "import": {
10 | "types": "./lib/esm/types/index.d.ts",
11 | "default": "./lib/esm/index.mjs"
12 | },
13 | "require": {
14 | "types": "./lib/cjs/types/index.d.ts",
15 | "default": "./lib/cjs/index.js"
16 | }
17 | }
18 | },
19 | "files": [
20 | "lib/**/*"
21 | ],
22 | "license": "MIT",
23 | "keywords": [
24 | "pattern-matching",
25 | "text-utils",
26 | "expressots"
27 | ],
28 | "repository": {
29 | "type": "git",
30 | "url": "git+https://github.com/expressots/boost-ts.git"
31 | },
32 | "publishConfig": {
33 | "access": "public"
34 | },
35 | "engines": {
36 | "node": ">=18.0.0"
37 | },
38 | "author": "Richard Zampieri",
39 | "scripts": {
40 | "prepare": "husky",
41 | "clean": "node scripts/rm.js lib",
42 | "copy": "node scripts/copy.js package.json README.md CHANGELOG.md lib",
43 | "build": "npm run clean && npm run build:cjs && npm run copy",
44 | "build:cjs": "tsc -p tsconfig.cjs.json",
45 | "release": "release-it",
46 | "prepublish": "npm run build && npm pack",
47 | "publish": "npm publish --tag latest",
48 | "test": "vitest run --reporter default",
49 | "test:watch": "vitest run --watch",
50 | "coverage": "vitest run --coverage",
51 | "format": "prettier --write \"src/**/*.ts\" --cache",
52 | "lint": "eslint \"src/**/*.ts\"",
53 | "lint:fix": "eslint \"src/**/*.ts\" --fix"
54 | },
55 | "devDependencies": {
56 | "@swc/core": "1.3.37",
57 | "@swc/jest": "0.2.24",
58 | "@types/jest": "29.5.0",
59 | "jest": "29.5.0",
60 | "ts-node-dev": "2.0.0",
61 | "@codecov/vite-plugin": "0.0.1-beta.6",
62 | "@commitlint/cli": "19.2.1",
63 | "@commitlint/config-conventional": "19.2.2",
64 | "@expressots/core": "2.15.0",
65 | "@release-it/conventional-changelog": "8.0.1",
66 | "@types/express": "4.17.21",
67 | "@types/node": "22.0.2",
68 | "@typescript-eslint/eslint-plugin": "7.16.1",
69 | "@typescript-eslint/parser": "7.16.1",
70 | "@vitest/coverage-v8": "2.0.3",
71 | "eslint": "8.57.0",
72 | "eslint-config-prettier": "9.1.0",
73 | "husky": "9.1.1",
74 | "prettier": "3.3.3",
75 | "release-it": "17.6.0",
76 | "typescript": "5.5.3",
77 | "vite": "5.3.4",
78 | "vite-tsconfig-paths": "4.3.2",
79 | "vitest": "2.0.3"
80 | },
81 | "dependencies": {
82 | "rimraf": "6.0.1"
83 | },
84 | "release-it": {
85 | "git": {
86 | "commitMessage": "chore(release): ${version}"
87 | },
88 | "github": {
89 | "release": true
90 | },
91 | "npm": {
92 | "publish": false
93 | },
94 | "plugins": {
95 | "@release-it/conventional-changelog": {
96 | "infile": "CHANGELOG.md",
97 | "preset": {
98 | "name": "conventionalcommits",
99 | "types": [
100 | {
101 | "type": "feat",
102 | "section": "Features"
103 | },
104 | {
105 | "type": "fix",
106 | "section": "Bug Fixes"
107 | },
108 | {
109 | "type": "perf",
110 | "section": "Performance Improvements"
111 | },
112 | {
113 | "type": "revert",
114 | "section": "Reverts"
115 | },
116 | {
117 | "type": "docs",
118 | "section": "Documentation"
119 | },
120 | {
121 | "type": "refactor",
122 | "section": "Code Refactoring"
123 | },
124 | {
125 | "type": "test",
126 | "section": "Tests"
127 | },
128 | {
129 | "type": "build",
130 | "section": "Build System"
131 | },
132 | {
133 | "type": "ci",
134 | "section": "Continuous Integrations"
135 | }
136 | ]
137 | }
138 | }
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/scripts/copy.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 |
4 | function copyRecursiveSync(src, dest) {
5 | const exists = fs.existsSync(src);
6 | const stats = exists && fs.statSync(src);
7 | const isDirectory = exists && stats.isDirectory();
8 |
9 | if (isDirectory) {
10 | fs.mkdirSync(dest);
11 | fs.readdirSync(src).forEach(function (childItemName) {
12 | copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName));
13 | });
14 | } else {
15 | fs.copyFileSync(src, dest);
16 | }
17 | }
18 |
19 | if (process.argv.length < 4) {
20 | process.stderr.write("Usage: node copy.js ... \n");
21 | process.exit(1);
22 | }
23 |
24 | const destination = process.argv[process.argv.length - 1];
25 |
26 | for (let i = 2; i < process.argv.length - 1; i++) {
27 | const origin = process.argv[i];
28 | copyRecursiveSync(origin, path.join(destination, path.basename(origin)));
29 | process.stdout.write(`Copied: ${origin} to ${destination}\n`);
30 | }
31 |
--------------------------------------------------------------------------------
/scripts/mv.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs").promises;
2 |
3 | const moveFile = async (origin, destination) => {
4 | try {
5 | await fs.access(origin);
6 | } catch (error) {
7 | process.stderr.write(`Error: Origin '${origin}' not found\n`);
8 | process.exit(1);
9 | }
10 |
11 | try {
12 | await fs.rename(origin, destination);
13 | process.stdout.write(`Move: ${origin} to ${destination}\n`);
14 | } catch (error) {
15 | process.stderr.write(`Error: Unable to move '${origin}' to '${destination}'\n`);
16 | process.exit(1);
17 | }
18 | };
19 |
20 | if (process.argv.length !== 4) {
21 | process.stderr.write("Usage: node mv.js \n");
22 | process.exit(1);
23 | }
24 |
25 | const origin = process.argv[2];
26 | const destination = process.argv[3];
27 |
28 | moveFile(origin, destination);
29 |
--------------------------------------------------------------------------------
/scripts/rm.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs").promises;
2 |
3 | const removeTarget = async (target) => {
4 | try {
5 | await fs.rm(target, { recursive: true, force: true });
6 | process.stdout.write(`Removed: ${target}\n`);
7 | } catch (error) {
8 | process.stderr.write(`Error: Unable to remove '${target}'\n`);
9 | process.exit(1);
10 | }
11 | };
12 |
13 | if (process.argv.length !== 3) {
14 | process.stderr.write("Usage: node rm.js \n");
15 | process.exit(1);
16 | }
17 |
18 | const target = process.argv[2];
19 | removeTarget(target);
20 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./match-pattern";
2 | export * from "./optional-pattern";
3 | export * from "./string";
--------------------------------------------------------------------------------
/src/match-pattern.ts:
--------------------------------------------------------------------------------
1 | import { None, Some } from "./optional-pattern";
2 |
3 | function isSome(value: any): value is Some {
4 | return value && value.tag === "Some";
5 | }
6 |
7 | function isNone(value: any): value is None {
8 | return value && value.tag === "None";
9 | }
10 |
11 | type OptionalPattern = {
12 | Some?: (value: U) => T;
13 | None?: T;
14 | };
15 |
16 | type MatchPatterns =
17 | | {
18 | [key: string]: () => T;
19 | }
20 | | OptionalPattern;
21 |
22 | export function match(value: any, patterns: MatchPatterns): T {
23 | if (isSome(value) && patterns.Some) {
24 | return patterns.Some(value.value);
25 | } else if (isNone(value) && patterns.None !== undefined) {
26 | return patterns.None as T;
27 | }
28 |
29 | for (const p in patterns) {
30 | if (p === "Some" || p === "None") continue;
31 |
32 | const patternKey = String(p);
33 | const isRange = patternKey.includes("..=");
34 | const isOr = patternKey.includes("|");
35 | const isRegex = patternKey.startsWith("/") && patternKey.endsWith("/");
36 |
37 | if (isRange) {
38 | const [start, end] = patternKey
39 | .split("..=")
40 | .map((s) => (isNaN(Number(s)) ? s : Number(s)));
41 |
42 | if (
43 | (typeof value === "number" &&
44 | typeof start === "number" &&
45 | typeof end === "number" &&
46 | value >= start &&
47 | value <= end) ||
48 | (typeof value === "string" &&
49 | typeof start === "string" &&
50 | typeof end === "string" &&
51 | value >= start &&
52 | value <= end)
53 | ) {
54 | return patterns[p]();
55 | }
56 | } else if (isOr) {
57 | const patternValues = patternKey
58 | .split("|")
59 | .map((s) => (isNaN(Number(s)) ? s : Number(s)));
60 |
61 | if (patternValues.includes(value)) {
62 | return patterns[p]();
63 | }
64 | } else if (isRegex) {
65 | const regex = new RegExp(patternKey.slice(1, -1));
66 |
67 | if (regex.test(value)) {
68 | return patterns[p]();
69 | }
70 | } else {
71 | if (value.toString() === patternKey) {
72 | return patterns[p]();
73 | }
74 | }
75 | }
76 |
77 | if (patterns["_"]) {
78 | return patterns["_"]();
79 | }
80 |
81 | throw new Error(`No match found for the given value ${value}`);
82 | }
83 |
--------------------------------------------------------------------------------
/src/optional-pattern.ts:
--------------------------------------------------------------------------------
1 | export type None = { tag: "None" };
2 | export type Some = { tag: "Some", value: T};
3 | export type Optional = Some | None;
4 |
5 | export function Some(value: T): Some {
6 | return { tag: "Some", value };
7 | }
8 |
9 | export function None(): None {
10 | return { tag: "None" };
11 | }
12 |
13 | type MatchPatterns = {
14 | Some?: (value: T extends Some ? V : never) => U;
15 | None?: U;
16 | }
17 |
18 | export function matchOptional, U>(value: T, patterns: MatchPatterns): U {
19 |
20 | if (patterns[value.tag]) {
21 | if (value.tag === "Some") {
22 | return (patterns[value.tag] as (value: T extends Some ? V : never) => U)(value.value);
23 | } else {
24 | return patterns[value.tag] as U;
25 | }
26 | }
27 |
28 | if (patterns["_"]) {
29 | return patterns["_"] as U;
30 | }
31 |
32 | throw new Error(`No match found for the given value ${value}`);
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/src/string/index.ts:
--------------------------------------------------------------------------------
1 | export * from "./naming-convention";
--------------------------------------------------------------------------------
/src/string/naming-convention.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to camelCase.
3 | * @param str - The input string to be converted.
4 | * @returns The converted string in camelCase.
5 | */
6 | export function anyCaseToCamelCase(str: string): string {
7 | return str
8 | .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
9 | .replace(/^[A-Z]/, (char) => char.toLowerCase());
10 | }
11 |
12 | /**
13 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to kebab-case.
14 | * @param str - The input string to be converted.
15 | * @returns The converted string in kebab-case.
16 | */
17 | export function anyCaseToKebabCase(str: string): string {
18 | return str
19 | .replace(/([a-z0-9])([A-Z])/g, '$1-$2') // Convert camelCase and PascalCase to kebab-case
20 | .replace(/_/g, '-') // Convert snake_case to kebab-case
21 | .toLowerCase(); // Ensure all characters are lowercase
22 | }
23 |
24 | /**
25 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to PascalCase.
26 | * @param str - The input string to be converted.
27 | * @returns The converted string in PascalCase.
28 | */
29 | export function anyCaseToPascalCase(str: string): string {
30 | return str
31 | .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : ''))
32 | .replace(/^[a-z]/, (char) => char.toUpperCase());
33 | }
34 |
35 | /**
36 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to snake_case.
37 | * @param str - The input string to be converted.
38 | * @returns The converted string in snake_case.
39 | */
40 | export function anyCaseToSnakeCase(str: string): string {
41 | return str
42 | .replace(/([a-z0-9])([A-Z])/g, '$1_$2')
43 | .replace(/[-]+/g, '_')
44 | .toLowerCase();
45 | }
46 |
47 | /**
48 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to UPPER CASE.
49 | * @param str - The input string to be converted.
50 | * @returns The converted string in UPPER CASE.
51 | */
52 | export function anyCaseToUpperCase(str: string): string {
53 | return str.replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')).toUpperCase();
54 | }
55 |
56 | /**
57 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to lower case.
58 | * @param str - The input string to be converted.
59 | * @returns The converted string in lower case.
60 | */
61 | export function anyCaseToLowerCase(str: string): string {
62 | return str.replace(/[-_]+(.)?/g, (_, char) => (char ? char.toLowerCase() : '')).toLowerCase();
63 | }
64 |
65 |
--------------------------------------------------------------------------------
/tests/match-pattern.test.ts:
--------------------------------------------------------------------------------
1 | import { match } from "../src/match-pattern";
2 | import { describe, test, expect } from "vitest";
3 |
4 | describe("testing match pattern function", () => {
5 | const matchValidates = {
6 | 0: () => "this is 0",
7 | 99: () => "this is 99",
8 | "11..=22": () => "this number is in between 11 and 22",
9 | "/abc/": () => "this is a regex",
10 | "2 | 3 | 5 | 8": () => "this number is a prime number",
11 | false: () => "this is a boolean",
12 | "a..=d": () => "this is a char",
13 | "1..=13": () => "Between 1 and 13",
14 | "25 | 50 | 100": () => "A bill",
15 | "/[a-z]/": () => "A lowercase letter",
16 | _: () => "default",
17 | };
18 |
19 | const enum Coin {
20 | Penny,
21 | Nickel,
22 | Dime,
23 | Quarter,
24 | }
25 |
26 | test("match pattern - validates", () => {
27 | expect(match(0, matchValidates)).toBe("this is 0");
28 | expect(match(99, matchValidates)).toBe("this is 99");
29 | expect(match(11, matchValidates)).toBe("this number is in between 11 and 22");
30 | expect(match(/abc/, matchValidates)).toBe("this is a regex");
31 | expect(match(false, matchValidates)).toBe("this is a boolean");
32 | expect(match("a", matchValidates)).toBe("this is a char");
33 | expect(match(5, matchValidates)).toBe("this number is a prime number");
34 | expect(match(10, matchValidates)).toBe("Between 1 and 13");
35 | expect(match(25, matchValidates)).toBe("A bill");
36 | expect(match("z", matchValidates)).toBe("A lowercase letter");
37 | expect(match("1", matchValidates)).toBe("default");
38 | });
39 |
40 | test("match pattern - enum", () => {
41 | expect(
42 | match(Coin.Dime, {
43 | [Coin.Penny]: () => 1,
44 | [Coin.Nickel]: () => 5,
45 | [Coin.Dime]: () => 10,
46 | [Coin.Quarter]: () => 25,
47 | })
48 | ).toBe(10);
49 | });
50 | });
51 |
--------------------------------------------------------------------------------
/tests/optional-pattern.test.ts:
--------------------------------------------------------------------------------
1 | import { Some, None, Optional } from "../src/optional-pattern";
2 | import { match } from "../src/match-pattern";
3 | import { describe, test, expect } from "vitest";
4 |
5 | describe("testing optional pattern function", () => {
6 | const v1: Optional = Some(1);
7 | const v2: Optional = None();
8 |
9 | test("optional pattern", () => {
10 | expect(
11 | match(v1, {
12 | Some: (x) => x + 1,
13 | None: 0,
14 | })
15 | ).toBe(2);
16 |
17 | expect(
18 | match(v2, {
19 | Some: (x) => x + 1,
20 | None: 0,
21 | })
22 | ).toBe(0);
23 | });
24 | });
25 |
--------------------------------------------------------------------------------
/tsconfig.cjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs",
5 | "target": "ES2021",
6 | "moduleResolution": "node",
7 | "outDir": "lib/cjs",
8 | "declarationDir": "lib/cjs/types"
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "strict": true,
4 | "esModuleInterop": true,
5 | "forceConsistentCasingInFileNames": true,
6 | "skipLibCheck": true,
7 | "strictNullChecks": false,
8 | "checkJs": true,
9 | "allowJs": true,
10 | "declaration": true,
11 | "noImplicitAny": false,
12 | "allowSyntheticDefaultImports": true,
13 | "experimentalDecorators": true,
14 | "emitDecoratorMetadata": true,
15 | "types": ["node", "vitest/globals"]
16 | },
17 | "include": ["./src/**/*.ts"],
18 | "exclude": ["node_modules", "**/__tests__/*.spec.ts", "scripts/**/*"]
19 | }
20 |
--------------------------------------------------------------------------------