├── src ├── index.mjs ├── index.cjs.js ├── __tests__ │ └── index.test.js ├── @types │ └── regexcraft.d.ts └── index.js ├── .babelrc ├── babel.config.js ├── .gitignore ├── LICENSE ├── package.json └── readme.md /src/index.mjs: -------------------------------------------------------------------------------- 1 | import RegexCraft from "./index.js"; 2 | 3 | export default RegexCraft; 4 | -------------------------------------------------------------------------------- /src/index.cjs.js: -------------------------------------------------------------------------------- 1 | const RegexCraft = require("./index.js"); 2 | 3 | module.exports = RegexCraft; 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript" 5 | ] 6 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | "@babel/preset-env", 4 | "@babel/preset-typescript" 5 | ] 6 | }; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | package-lock.json 4 | yarn.lock 5 | 6 | # Build 7 | dist/ 8 | build/ 9 | 10 | # IDE 11 | .idea/ 12 | .vscode/ 13 | *.sublime-project 14 | *.sublime-workspace 15 | 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | 23 | # Operating System 24 | .DS_Store 25 | Thumbs.db -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Eliezer Nsengi 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. -------------------------------------------------------------------------------- /src/__tests__/index.test.js: -------------------------------------------------------------------------------- 1 | import RegexCraft from "../index.js"; 2 | 3 | describe("RegexCraft", () => { 4 | let regex; 5 | 6 | beforeEach(() => { 7 | regex = new RegexCraft(); 8 | }); 9 | 10 | describe("Password Validation", () => { 11 | test("should validate password with medium preset", () => { 12 | const validator = regex.usePreset("password", "medium").build(); 13 | expect(validator.test("Password123")).toBeTruthy(); 14 | expect(validator.test("weak")).toBeFalsy(); 15 | }); 16 | }); 17 | 18 | describe("Email Validation", () => { 19 | test("should validate email format", () => { 20 | const validator = regex.isEmail().build(); 21 | expect(validator.test("test@example.com")).toBeTruthy(); 22 | expect(validator.test("invalid-email")).toBeFalsy(); 23 | }); 24 | }); 25 | 26 | describe("Phone Validation", () => { 27 | test("should validate US phone numbers", () => { 28 | const validator = regex.isPhone("US").build(); 29 | expect(validator.test("+1-555-555-5555")).toBeTruthy(); 30 | expect(validator.test("invalid")).toBeFalsy(); 31 | }); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "regexcraft", 3 | "version": "1.0.20", 4 | "description": "A powerful utility class for building and managing regular expressions with a fluent, chainable API", 5 | "main": "src/index.cjs.js", 6 | "module": "src/index.mjs", 7 | "types": "src/@types/regexcraft.d.ts", 8 | "scripts": { 9 | "test": "jest", 10 | "build": "babel src -d dist", 11 | "prepare": "npm run build" 12 | }, 13 | "keywords": [ 14 | "regex", 15 | "regular-expressions", 16 | "validation", 17 | "pattern-matching", 18 | "form-validation" 19 | ], 20 | "author": "Eliezer Nsengi", 21 | "license": "MIT", 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/iAmNsengi/regexcraft.git" 25 | }, 26 | "bugs": { 27 | "url": "https://github.com/iAmNsengi/regexcraft/issues" 28 | }, 29 | "homepage": "https://regexcraft.onrender.com/", 30 | "devDependencies": { 31 | "@babel/cli": "^7.23.9", 32 | "@babel/core": "^7.23.9", 33 | "@babel/preset-env": "^7.26.0", 34 | "@babel/preset-typescript": "^7.26.0", 35 | "babel-jest": "^29.7.0", 36 | "jest": "^29.7.0" 37 | }, 38 | "exports": { 39 | "import": { 40 | "default": "./src/index.mjs", 41 | "types": "./src/@types/regexcraft.d.ts" 42 | }, 43 | "require": { 44 | "default": "./src/index.cjs.js", 45 | "types": "./src/@types/regexcraft.d.ts" 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/@types/regexcraft.d.ts: -------------------------------------------------------------------------------- 1 | declare module "regexcraft" { 2 | interface PresetPattern { 3 | pattern: string; 4 | message: string; 5 | } 6 | 7 | interface Presets { 8 | password: { 9 | low: PresetPattern[]; 10 | medium: PresetPattern[]; 11 | high: PresetPattern[]; 12 | }; 13 | username: { 14 | low: PresetPattern[]; 15 | medium: PresetPattern[]; 16 | high: PresetPattern[]; 17 | }; 18 | } 19 | 20 | // type; 21 | 22 | interface URLOptions { 23 | protocol?: boolean; 24 | } 25 | 26 | interface TestResult { 27 | value: string; 28 | isValid: boolean; 29 | failedRequirements: string[]; 30 | } 31 | 32 | interface VisualizationResult { 33 | pattern: string; 34 | requirements: string[]; 35 | } 36 | 37 | class RegexCraft { 38 | constructor(); 39 | 40 | hasMinLength(length: number, message?: string): RegexCraft; 41 | hasMaxLength(length: number, message?: string): RegexCraft; 42 | hasLengthBetween(min: number, max: number, message?: string): RegexCraft; 43 | hasExactLength(length: number, message?: string): RegexCraft; 44 | hasLetter(count?: number, message?: string): RegexCraft; 45 | hasLowerCase(count?: number, message?: string): RegexCraft; 46 | hasUpperCase(count?: number, message?: string): RegexCraft; 47 | hasNumber(count?: number, message?: string): RegexCraft; 48 | hasSpecialCharacter(count?: number, message?: string): RegexCraft; 49 | 50 | isEmail(message?: string): RegexCraft; 51 | isURL(options?: URLOptions, message?: string): RegexCraft; 52 | isIPv4(message?: string): RegexCraft; 53 | isDate(format?: string, message?: string): RegexCraft; 54 | 55 | isPhone(country?: string, message?: string): RegexCraft; 56 | 57 | field(name: string, rules: string): RegexCraft; 58 | usePreset( 59 | type: "password" | "username", 60 | level?: "low" | "medium" | "high" 61 | ): RegexCraft; 62 | 63 | testOne(example: string): TestResult; 64 | test(examples: string[]): TestResult[]; 65 | visualize(): VisualizationResult; 66 | build(): RegExp; 67 | 68 | private addPattern(pattern: string, message: string): RegexCraft; 69 | private getFailedRequirements(value: string): string[]; 70 | } 71 | 72 | // Added a function to use Presets 73 | function usePresets(presets: Presets): void { 74 | // Placeholder function 75 | } 76 | 77 | export default RegexCraft; 78 | } 79 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # RegexCraft 🛠️ 2 | 3 | [](https://badge.fury.io/js/regexcraft) 4 | [](https://opensource.org/licenses/MIT) 5 | 6 | RegexCraft is a powerful utility class for building and managing regular expressions with a fluent, chainable API. It simplifies the process of creating complex regex patterns while providing clear validation messages. 7 | 8 | ## Table of Contents 9 | 10 | - [Features](#features-) 11 | - [Installation](#installation-) 12 | - [Quick Start](#quick-start-) 13 | - [Usage Examples](#usage-examples-) 14 | - [API Reference](#api-reference-) 15 | - [Contributing](#contributing-) 16 | - [License](#license-) 17 | - [Support](#support-) 18 | - [Author](#author-) 19 | 20 | ## Features ✨ 21 | 22 | - 🔗 Chainable API for building complex patterns 23 | - 📝 Built-in validation presets for common use cases 24 | - 🎯 Custom error messages for each validation rule 25 | - 📱 Phone number validation for multiple countries (RW, DRC, US, UK, KE, UG, TZ, NG, ZA, GH) 26 | - 🔒 Password strength validation 27 | - 👤 Username format validation 28 | - 📧 Email validation 29 | - 🌐 URL validation 30 | - 📅 Date format validation 31 | - 🔍 Pattern testing and visualization 32 | 33 | ## Installation 📦 34 | 35 | ### Requirements 36 | 37 | - Node.js 12.x or higher 38 | - npm 6.x or higher 39 | 40 | ```bash 41 | npm install regexcraft 42 | # or 43 | yarn add regexcraft 44 | ``` 45 | 46 | ## Quick Start 🚀 47 | 48 | ```javascript 49 | import RegexCraft from "regexcraft"; 50 | 51 | // Create a new instance 52 | const regex = new RegexCraft(); 53 | 54 | // Test a single value 55 | const result = regex 56 | .hasMinLength(8) 57 | .hasUpperCase(1) 58 | .hasLowerCase(1) 59 | .hasNumber(1) 60 | .hasSpecialCharacter(1) 61 | .testOne("MyPassword123!"); 62 | 63 | console.log(result); 64 | // { value: "MyPassword123!", isValid: true, failedRequirements: [] } 65 | 66 | // Test multiple values (still supported) 67 | const results = regex.test(["MyPassword123!", "weak"]); 68 | ``` 69 | 70 | ## Usage Examples 💡 71 | 72 | ### Password Validation 73 | 74 | ```javascript 75 | const passwordValidator = new RegexCraft() 76 | .usePreset("password", "medium") // Use built-in presets 77 | .build(); 78 | 79 | // Or create custom rules 80 | const customPassword = new RegexCraft() 81 | .hasMinLength(10) 82 | .hasUpperCase(2) 83 | .hasNumber(2) 84 | .hasSpecialCharacter(1) 85 | .build(); 86 | ``` 87 | 88 | ### Username Validation 89 | 90 | ```javascript 91 | const usernameValidator = new RegexCraft() 92 | .usePreset("username", "standard") 93 | .build(); 94 | ``` 95 | 96 | ### Email Validation 97 | 98 | ```javascript 99 | const emailValidator = new RegexCraft().isEmail().build(); 100 | ``` 101 | 102 | ### Phone Number Validation 103 | 104 | ```javascript 105 | const phoneValidator = new RegexCraft() 106 | .isPhone("US") // Supports: US, UK, RW, DRC, KE, UG, TZ, NG, ZA, GH, international 107 | .build(); 108 | 109 | // Test a single phone number 110 | const result = phoneValidator.testOne("+1234567890"); 111 | 112 | // Test multiple numbers 113 | const results = phoneValidator.test(["+1234567890", "+254712345678"]); 114 | ``` 115 | 116 | ### Form Field Validation 117 | 118 | ```javascript 119 | const formValidator = new RegexCraft() 120 | .field("email", "required|email") 121 | .field("password", "password:high") 122 | .field("username", "username:strict") 123 | .build(); 124 | ``` 125 | 126 | ### React Example 127 | 128 | ```jsx 129 | import RegexCraft from "regexcraft"; 130 | import { useState } from "react"; 131 | 132 | function SignupForm() { 133 | const [formData, setFormData] = useState({ 134 | username: "", 135 | password: "", 136 | email: "", 137 | }); 138 | const [errors, setErrors] = useState({}); 139 | 140 | const validate = (field, value) => { 141 | const validator = new RegexCraft().usePreset(field, "high").build(); 142 | const result = validator.test([value]); 143 | if (!result) { 144 | setErrors((prevErrors) => ({ 145 | ...prevErrors, 146 | [field]: validator.getErrorMessage(), 147 | })); 148 | } 149 | }; 150 | 151 | const handleChange = (e) => { 152 | const { name, value } = e.target; 153 | setFormData((prevData) => ({ ...prevData, [name]: value })); 154 | validate(name, value); 155 | }; 156 | 157 | return ( 158 |
181 | ); 182 | } 183 | 184 | export default SignupForm; 185 | ``` 186 | 187 | ### Vanilla JavaScript Example 188 | 189 | ```javascript 190 | const form = document.querySelector("#signupForm"); 191 | const emailInput = form.querySelector('input[name="email"]'); 192 | const errorContainer = form.querySelector(".error-messages"); 193 | 194 | const validator = new RegexCraft(); 195 | 196 | emailInput.addEventListener("input", (e) => { 197 | const email = e.target.value; 198 | validator.field("email", "required|email"); 199 | 200 | const result = validator.test([email])[0]; 201 | errorContainer.innerHTML = ""; 202 | 203 | if (!result.isValid) { 204 | result.failedRequirements.forEach((error) => { 205 | const errorElement = document.createElement("p"); 206 | errorElement.textContent = error; 207 | errorContainer.appendChild(errorElement); 208 | }); 209 | } 210 | }); 211 | ``` 212 | 213 | ## API Reference 📚 214 | 215 | ### Basic Validators 216 | 217 | | Method | Description | Example | 218 | | -------------------------------------- | ------------------------ | -------------------------- | 219 | | `hasMinLength(length, message?)` | Validates minimum length | `.hasMinLength(8)` | 220 | | `hasMaxLength(length, message?)` | Validates maximum length | `.hasMaxLength(20)` | 221 | | `hasLengthBetween(min, max, message?)` | Validates length range | `.hasLengthBetween(8, 20)` | 222 | | `hasExactLength(length, message?)` | Validates exact length | `.hasExactLength(10)` | 223 | 224 | ### Character Type Validators 225 | 226 | | Method | Description | Example | 227 | | --------------------------------------- | --------------------------- | ------------------------ | 228 | | `hasLetter(count?, message?)` | Requires letters | `.hasLetter(2)` | 229 | | `hasLowerCase(count?, message?)` | Requires lowercase letters | `.hasLowerCase()` | 230 | | `hasUpperCase(count?, message?)` | Requires uppercase letters | `.hasUpperCase(1)` | 231 | | `hasNumber(count?, message?)` | Requires numbers | `.hasNumber(2)` | 232 | | `hasSpecialCharacter(count?, message?)` | Requires special characters | `.hasSpecialCharacter()` | 233 | 234 | ### Format Validators 235 | 236 | | Method | Description | Example | 237 | | ----------------------------- | ----------------------- | ---------------------------- | 238 | | `isEmail(message?)` | Validates email format | `.isEmail()` | 239 | | `isURL(options?, message?)` | Validates URL format | `.isURL({ protocol: true })` | 240 | | `isIPv4(message?)` | Validates IPv4 address | `.isIPv4()` | 241 | | `isDate(format?, message?)` | Validates date format | `.isDate('YYYY-MM-DD')` | 242 | | `isPhone(country?, message?)` | Validates phone numbers | `.isPhone('US')` | 243 | 244 | ### Presets 245 | 246 | ```javascript 247 | // Available preset types and levels 248 | const presets = { 249 | password: ["low", "medium", "high"], 250 | username: ["standard", "strict"], 251 | }; 252 | 253 | // Usage 254 | regex.usePreset("password", "high"); 255 | ``` 256 | 257 | ### Testing and Visualization 258 | 259 | ```javascript 260 | // Test multiple values 261 | const results = regex.test(["test1", "test2"]); 262 | 263 | // Visualize the pattern 264 | const visualization = regex.visualize(); 265 | ``` 266 | 267 | ## Contributing 🤝 268 | 269 | Contributions are welcome! Please feel free to submit a Pull Request. 270 | 271 | 1. Fork the repository 272 | 2. Create your feature branch (`git checkout -b feature/AmazingFeature`) 273 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) 274 | 4. Push to the branch (`git push origin feature/AmazingFeature`) 275 | 5. Open a Pull Request 276 | 277 | ## License 📄 278 | 279 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. 280 | 281 | ## Support 💖 282 | 283 | If you find this project helpful, please consider giving it a ⭐️ on GitHub! 284 | 285 | ## Author ✍️ 286 | 287 | Eliezer Nsengi 288 | 289 | - GitHub: [@iAmNsengi](https://github.com/iAmNsengi) 290 | 291 | ## Acknowledgments 🙏 292 | 293 | - Thanks to all contributors who have helped shape RegexCraft 294 | - Inspired by the need for simpler regex pattern creation 295 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * RegexCraft - A utility class for building regular expressions. 3 | * Provides a good interface for creating common regex patterns. 4 | */ 5 | 6 | class RegexCraft { 7 | /** 8 | * Creates a new RegexCraft instance 9 | * @constructor 10 | */ 11 | constructor() { 12 | this.patterns = []; 13 | this.flags = ""; 14 | this.description = []; 15 | this.errorMessages = new Map(); 16 | 17 | // Initializing presets 18 | this.presets = { 19 | password: { 20 | low: [ 21 | { pattern: "(?=.{6,})", message: "At least 6 characters" }, 22 | { 23 | pattern: "(?=.*\\d)", 24 | message: "At least one number", 25 | }, 26 | ], 27 | medium: [ 28 | { pattern: "(?=.{8,})", message: "At least 8 characters" }, 29 | { 30 | pattern: "(?=.*\\d)", 31 | message: "At least one number", 32 | }, 33 | { 34 | pattern: "(?=.*[a-z])", 35 | message: "At least one lowercase letter", 36 | }, 37 | { 38 | pattern: "(?=.*[A-Z])", 39 | message: "At least one uppercase letter", 40 | }, 41 | ], 42 | high: [ 43 | { pattern: "(?=.{10,})", message: "At least 10 characters" }, 44 | { 45 | pattern: "(?=(.*\\d){2,})", 46 | message: "At least two numbers", 47 | }, 48 | { 49 | pattern: "(?=.*[a-z])", 50 | message: "At least one lowercase letter", 51 | }, 52 | { 53 | pattern: "(?=.*[A-Z])", 54 | message: "At least one uppercase letter", 55 | }, 56 | { 57 | pattern: "(?=.*[!@#$%^&*])", 58 | message: "At least one special character", 59 | }, 60 | ], 61 | }, 62 | username: { 63 | low: [ 64 | { 65 | pattern: "^[a-zA-Z][a-zA-Z0-9_]{2,29}", 66 | message: 67 | "Letters, numbers and underscore only, at least 2 characters and max 29 characters.", 68 | }, 69 | ], 70 | medium: [ 71 | { 72 | pattern: "^[a-zA-Z][a-zA-Z0-9]{5,29}", 73 | message: 74 | "Letters and numbers only, at least 5 characters and max 29 characters.", 75 | }, 76 | ], 77 | high: [ 78 | { 79 | pattern: "^[a-zA-Z][a-zA-Z0-9]{8,29}", 80 | message: 81 | "Letters and numbers only, at least 8 characters and max 29 characters.", 82 | }, 83 | ], 84 | }, 85 | }; 86 | } 87 | 88 | /** 89 | * Validates minimum length of the input 90 | * @param {number} length - Minimum number of characters required 91 | * @param {string} [message] - Custom error message 92 | * @returns {RegexCraft} Returns this for method chaining 93 | */ 94 | hasMinLength(length, message = `Minimum length of ${length} characters`) { 95 | this.addPattern(`^.{${length},}$`, message); 96 | return this; 97 | } 98 | 99 | /** 100 | * Validates maximum length of the input 101 | * @param {number} length - Maximum number of characters allowed 102 | * @param {string} [message] - Custom error message 103 | * @returns {RegexCraft} Returns this for method chaining 104 | */ 105 | hasMaxLength(length, message = `Maximum length of ${length} characters`) { 106 | this.addPattern(`^.{0,${length}}$`, message); 107 | return this; 108 | } 109 | 110 | /** 111 | * Validates that input length falls within specified range 112 | * @param {number} min - Minimum number of characters required 113 | * @param {number} max - Maximum number of characters allowed 114 | * @param {string} [message] - Custom error message 115 | * @returns {RegexCraft} Returns this for method chaining 116 | */ 117 | hasLengthBetween( 118 | min, 119 | max, 120 | message = `Length should be between ${min} and ${max} characters` 121 | ) { 122 | this.addPattern(`^.{${min},${max}}$`, message); 123 | return this; 124 | } 125 | /** 126 | * Validates that input has exact length 127 | * @param {number} length - Required exact length 128 | * @param {string} [message] - Custom error message 129 | * @returns {RegexCraft} Returns this for method chaining 130 | */ 131 | hasExactLength( 132 | length, 133 | message = `Length should be exactly ${length} characters` 134 | ) { 135 | this.addPattern(`(?=^.{${length}}$)`, message); 136 | return this; 137 | } 138 | 139 | /** 140 | * Requires specified number of letters (a-z, A-Z) 141 | * @param {number} [count=1] - Minimum number of letters required 142 | * @param {string} [message] - Custom error message 143 | * @returns {RegexCraft} Returns this for method chaining 144 | */ 145 | hasLetter( 146 | count = 1, 147 | message = `At least ${count} letter${count > 1 ? "s" : ""}` 148 | ) { 149 | this.addPattern(`(?=(?:.*[a-zA-Z]){${count}})`, message); 150 | return this; 151 | } 152 | 153 | /** 154 | * Requires specified number of lowercase letters 155 | * @param {number} [count=1] - Minimum number of lowercase letters required 156 | * @param {string} [message] - Custom error message 157 | * @returns {RegexCraft} Returns this for method chaining 158 | */ 159 | hasLowerCase( 160 | count = 1, 161 | message = `At least ${count} lowercase letter${count > 1 ? "s" : ""}` 162 | ) { 163 | this.addPattern(`(?=(?:.*[a-z]){${count}})`, message); 164 | return this; 165 | } 166 | /** 167 | * Requires specified number of uppercase letters 168 | * @param {number} count - Minimum number of uppercase letters required 169 | * @param {string} message - Custom error message 170 | * @returns {RegexCraft} - Returns this for method chaining 171 | */ 172 | hasUpperCase( 173 | count = 1, 174 | message = `At least ${count} uppercase letter${count > 1 ? "s" : ""}` 175 | ) { 176 | this.addPattern(`(?=(?:.*[A-Z]){${count}})`, message); 177 | return this; 178 | } 179 | /** 180 | * Requires specified number of numbers 181 | * @param {number} [count=1] - Minimum number of numbers required 182 | * @param {string} [message] - Custom error message 183 | * @returns {RegexCraft} Returns this for method chaining 184 | */ 185 | hasNumber( 186 | count = 1, 187 | message = `At least ${count} number${count > 1 ? "s" : ""}` 188 | ) { 189 | this.addPattern(`(?=(?:.*\\d){${count}})`, message); 190 | return this; 191 | } 192 | /** 193 | * Requires specified number of special characters 194 | * @param {number} [count=1] - Minimum number of special characters required 195 | * @param {string} [message] - Custom error message 196 | * @returns {RegexCraft} Returns this for method chaining 197 | */ 198 | hasSpecialCharacter( 199 | count = 1, 200 | message = `At least ${count} special character${count > 1 ? "s" : ""}` 201 | ) { 202 | this.addPattern(`(?=(?=.*[!@#$%^&*?~]){${count}})`, message); 203 | return this; 204 | } 205 | 206 | /** 207 | * Requires that input contains no numbers 208 | * @param {string} [message] - Custom error message 209 | * @returns {RegexCraft} Returns this for method chaining 210 | */ 211 | hasNoNumber(message = "Must not contain any numbers") { 212 | this.addPattern("^[^0-9]*$", message); 213 | return this; 214 | } 215 | 216 | /** 217 | * Common format validators 218 | */ 219 | /** 220 | * Validates email format 221 | * @param {string} [message] - Custom error message 222 | * @returns {RegexCraft} Returns this for method chaining 223 | */ 224 | isEmail(message = "Must be a valid email address") { 225 | this.addPattern( 226 | "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$", 227 | message 228 | ); 229 | return this; 230 | } 231 | /** 232 | * Validates URL format 233 | * @param {Object} [options] - URL validation options 234 | * @param {boolean} [options.protocol=true] - Whether to require protocol (http/https) 235 | * @param {string} [message] - Custom error message 236 | * @returns {RegexCraft} Returns this for method chaining 237 | */ 238 | isURL(options = { protocol: true }, message = "Must be a valid URL") { 239 | const protocol = options.protocol ? "https?:\\/\\/" : ""; 240 | this.addPattern( 241 | `^${protocol}([\\w-]+\\.)+[\\w-]+(\\/[\\w- ./?%&=]*)?$`, 242 | message 243 | ); 244 | return this; 245 | } 246 | 247 | /** 248 | * Validates IPv4 address format 249 | * @param {string} [message] - Custom error message 250 | * @returns {RegexCraft} Returns this for method chaining 251 | */ 252 | isIPv4(message = "Must be a valid IPv4 address") { 253 | this.addPattern( 254 | "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$", 255 | message 256 | ); 257 | return this; 258 | } 259 | 260 | /** 261 | * Validates date format 262 | * @param {string} [format="YYYY-MM-DD"] - Date format to validate against 263 | * @param {string} [message] - Custom error message 264 | * @returns {RegexCraft} Returns this for method chaining 265 | */ 266 | isDate(format = "YYYY-MM-DD", message = `Valid date in ${format} format`) { 267 | const formats = { 268 | "YYYY-MM-DD": "^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])$", 269 | "MM/DD/YYYY": "^(?:0[1-9]|1[0-2])/(?:0[1-9]|[12]\\d|3[01])/\\d{4}$", 270 | "DD/MM/YYYY": "^(?:0[1-9]|[12]\\d|3[01])/(?:0[1-9]|1[0-2])/\\d{4}$", 271 | ISO: "^\\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|[12]\\d|3[01])T(?:[01]\\d|2[0-3]):[0-5]\\d:[0-5]\\d(?:\\.\\d+)?(?:Z|[+-](?:[01]\\d|2[0-3]):[0-5]\\d)$", 272 | }; 273 | this.addPattern(formats[format] || formats["YYYY-MM-DD"], message); 274 | return this; 275 | } 276 | 277 | /** 278 | * Phone number validators 279 | */ 280 | 281 | /** 282 | * Validates phone number format for different countries 283 | * @param {string} [country="international"] - Country code for phone format 284 | * @param {string} [message] - Custom error message 285 | * @returns {RegexCraft} Returns this for method chaining 286 | */ 287 | isPhone(country = "international", message) { 288 | const phonePatterns = { 289 | RW: "^(?:\\+?250|0)?7[2-9]\\d{7}$", 290 | DRC: "^(?:\\+?243|0)?8[1-9]\\d{7}$", 291 | US: "^\\+?1?[-.]?\\(?[0-9]{3}\\)?[-.]?[0-9]{3}[-.]?[0-9]{4}$", 292 | UK: "^\\+?44\\s?\\d{10}$", 293 | KE: "^(?:\\+?254|0)?[71]\\d{8}$", 294 | UG: "^(?:\\+?256|0)?[7]\\d{8}$", 295 | TZ: "^(?:\\+?255|0)?[67]\\d{8}$", 296 | NG: "^(?:\\+?234|0)?[789]\\d{9}$", 297 | ZA: "^(?:\\+?27|0)?[6-8]\\d{8}$", 298 | GH: "^(?:\\+?233|0)?[235]\\d{8}$", 299 | international: 300 | "\\+?\\d{1,4}?[-.]?\\(?\\d{1,3}?\\)?[-.]?\\d{1,4}[-.]?\\d{1,4}[-.]?\\d{1,9}", 301 | E164: "^\\+[1-9]\\d{1,14}$", 302 | }; 303 | 304 | this.addPattern( 305 | phonePatterns[country] || phonePatterns["international"], 306 | message || `Valid ${country} phone number` 307 | ); 308 | return this; 309 | } 310 | 311 | /** 312 | * Form field validators 313 | */ 314 | 315 | /** 316 | * Applies validation rules to a form field 317 | * @param {string} name - Field name 318 | * @param {string} rules - Pipe-separated list of validation rules 319 | * @returns {RegexCraft} Returns this for method chaining 320 | */ 321 | field(name, rules) { 322 | const ruleList = rules.split("|"); 323 | ruleList.forEach((rule) => { 324 | const [ruleName, params] = rule.split(":"); 325 | switch (ruleName) { 326 | case "required": 327 | this.addPattern(".+", `${name} is required`); 328 | break; 329 | case "email": 330 | this.isEmail(`${name} must be a valid email`); 331 | break; 332 | case "phone": 333 | this.isPhone(params, `${name} must be a valid phone number`); 334 | break; 335 | case "password": 336 | this.usePreset("password", params || "medium"); 337 | break; 338 | case "username": 339 | this.usePreset("username", params || "medium"); 340 | break; 341 | case "min": 342 | this.hasMinLength( 343 | parseInt(params), 344 | `${name} must be at least ${params} characters` 345 | ); 346 | break; 347 | case "max": 348 | this.hasMaxLength( 349 | parseInt(params), 350 | `${name} must be at most ${params} characters` 351 | ); 352 | break; 353 | case "exact": 354 | this.hasExactLength( 355 | parseInt(params), 356 | `${name} must be exactly ${params} characters` 357 | ); 358 | break; 359 | case "url": 360 | this.isURL({}, `${name} must be a valid URL`); 361 | break; 362 | case "date": 363 | this.isDate(params, `${name} must be a valid date`); 364 | break; 365 | default: 366 | throw new Error(`Unknown validation rule: ${ruleName}`); 367 | } 368 | }); 369 | return this; 370 | } 371 | /** 372 | * -------------------- Presets 373 | */ 374 | 375 | /** 376 | * Applies a preset validation pattern 377 | * @param {string} type - Preset type (e.g., 'password', 'username') 378 | * @param {string} [level="medium"] - Validation strictness level 379 | * @returns {RegexCraft} Returns this for method chaining 380 | * @throws {Error} If preset type or level is not found 381 | */ 382 | usePreset(type, level = "medium") { 383 | const preset = this.presets[type]?.[level]; 384 | if (!preset) { 385 | throw new Error(`Preset not found: ${type}:${level}`); 386 | } 387 | 388 | preset.forEach(({ pattern, message }) => { 389 | this.addPattern(pattern, message); 390 | }); 391 | 392 | return this; 393 | } 394 | 395 | /** 396 | * Visualisation and testing 397 | */ 398 | 399 | /** 400 | * Tests a single example against the built regex pattern 401 | * @param {string} example - String to test 402 | * @returns {{value: string, isValid: boolean, failedRequirements: string[]}} Test result 403 | */ 404 | testOne(example) { 405 | const regex = this.build(); 406 | return { 407 | value: example, 408 | isValid: regex.test(example), 409 | failedRequirements: this.getFailedRequirements(example), 410 | }; 411 | } 412 | 413 | /** 414 | * Tests examples against the built regex pattern 415 | * @param {string[]} examples - Array of strings to test 416 | * @returns {Array<{value: string, isValid: boolean, failedRequirements: string[]}>} Test results 417 | */ 418 | test(examples) { 419 | return examples.map((example) => this.testOne(example)); 420 | } 421 | 422 | /** 423 | * Returns the current regex pattern and requirements 424 | * @returns {{pattern: string, requirements: string[]}} Visualization object 425 | */ 426 | visualize() { 427 | return { 428 | pattern: this.build().toString(), 429 | requirements: this.description, 430 | }; 431 | } 432 | 433 | /** 434 | * -------------------------------------------------------------- 435 | */ 436 | 437 | // utility methods 438 | /** 439 | * Adds a pattern and its description to the RegexCraft instance 440 | * @private 441 | * @param {string} pattern - Regex pattern to add 442 | * @param {string} message - Description of the pattern 443 | * @returns {RegexCraft} Returns this for method chaining 444 | */ 445 | addPattern(pattern, message) { 446 | this.patterns.push(pattern); 447 | this.description.push(message); 448 | return this; 449 | } 450 | /** 451 | * Gets list of failed requirements for a value 452 | * @private 453 | * @param {string} value - Value to test 454 | * @returns {string[]} Array of failed requirement messages 455 | */ 456 | getFailedRequirements(value) { 457 | return this.patterns 458 | .map((pattern, index) => ({ 459 | requirement: this.description[index], 460 | passed: new RegExp(pattern).test(value), 461 | })) 462 | .filter((result) => !result.passed) 463 | .map((result) => result.requirement); 464 | } 465 | 466 | /** 467 | * Builds the final regex pattern 468 | * @returns {RegExp} Combine regular expression in one 469 | */ 470 | build() { 471 | if (this.patterns.length === 0) return new RegExp(".*", this.flags); 472 | 473 | // Combine patterns with positive lookaheads for AND logic 474 | const pattern = this.patterns.map((p) => `(?=${p})`).join("") + ".+"; 475 | return new RegExp(`^${pattern}$`, this.flags); 476 | } 477 | } 478 | 479 | export default RegexCraft; 480 | --------------------------------------------------------------------------------