feat(core): Introduce native implementations for Base64, random generation and normalization; remove runtime plugin dependencies; update tests, docs and package metadata
This commit is contained in:
96
changelog.md
Normal file
96
changelog.md
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
## 2025-09-12 - 4.1.0 - feat(core)
|
||||||
|
Introduce native implementations for Base64, random generation and normalization; remove runtime plugin dependencies; update tests, docs and package metadata
|
||||||
|
|
||||||
|
- Implemented a cross-platform universal Base64 encoder/decoder and replaced usage of external js-base64 with the internal implementation
|
||||||
|
- Added a custom, cross-platform random string generator (uses crypto.getRandomValues when available) and removed dependency on randomatic / crypto-random-string
|
||||||
|
- Replaced strip-indent usage with an internal stripIndent implementation in normalize utilities
|
||||||
|
- Switched domain parsing to use the standard URL class instead of external url.parse
|
||||||
|
- Simplified ts/smartstring.plugins.ts to only export @push.rocks/isounique and removed several plugin re-exports (js-base64, strip-indent, randomatic, url, smartenv)
|
||||||
|
- Updated test imports to use @git.zone/tstest/tapbundle
|
||||||
|
- Expanded and updated README with full usage, examples and API reference
|
||||||
|
- Updated package.json: trimmed dependencies, bumped @git.zone/tstest version, added packageManager (pnpm) entry and adjusted files/browserslist
|
||||||
|
- Added .claude/settings.local.json
|
||||||
|
|
||||||
|
## 2024-05-29 - 4.0.15 - maintenance
|
||||||
|
Package metadata and TypeScript configuration updates.
|
||||||
|
|
||||||
|
- Updated package description.
|
||||||
|
- TypeScript configuration (tsconfig) adjustments.
|
||||||
|
- npmextra.json updated (githost entries).
|
||||||
|
|
||||||
|
## 2024-03-03 - 4.0.14 → 4.0.1 - maintenance & core fixes
|
||||||
|
Routine core fixes, small updates and version bumps across the 4.0.x series.
|
||||||
|
|
||||||
|
- Multiple "fix(core): update" commits addressing internal/core issues.
|
||||||
|
- Several version-only releases (patch bumps and releases with minimal change notes).
|
||||||
|
|
||||||
|
## 2022-03-18 - 4.0.2 → 3.0.26 - release consolidation
|
||||||
|
Consolidated releases and small fixes spanning late 2020–early 2022.
|
||||||
|
|
||||||
|
- Several patch releases with minor fixes and version bumps.
|
||||||
|
- Routine maintenance and stability improvements.
|
||||||
|
|
||||||
|
## 2018-11-28 - 3.0.4 → 3.0.0 - CI & dependency fixes
|
||||||
|
Improvements to build/CI and dependency cleanups for the 3.0.x line.
|
||||||
|
|
||||||
|
- fix(dependencies and structure): updated dependencies and project structure (3.0.4).
|
||||||
|
- fix(ci): reduced build dependencies and fixed build steps (3.0.3, 3.0.2).
|
||||||
|
- fix(dependencies): updated test framework and removed obsolete dependency on typings-global (3.0.1, 3.0.0).
|
||||||
|
- Numerous small fixes and version bump releases across 3.0.x.
|
||||||
|
|
||||||
|
## 2018-07-21 - 2.0.28 - BREAKING CHANGE: package scope
|
||||||
|
Breaking change: package scope changed.
|
||||||
|
|
||||||
|
- Changed package scope to @pushrocks (BREAKING CHANGE).
|
||||||
|
|
||||||
|
## 2017-10-26 - 2.0.27 → 2.0.25 - code quality & npm metadata
|
||||||
|
Small refactors and npm metadata additions.
|
||||||
|
|
||||||
|
- Refactor to use const (2.0.27).
|
||||||
|
- Added npmextra.json (2.0.26).
|
||||||
|
- Added create module (2.0.25).
|
||||||
|
|
||||||
|
## 2016-10-31 - 2.0.19 → 2.0.17 - Base64 & exports
|
||||||
|
Added Base64 handling and improved exports.
|
||||||
|
|
||||||
|
- Added Base64 handling and exposed base64 functions separate from class (2.0.17–2.0.19).
|
||||||
|
- Small export fixes.
|
||||||
|
|
||||||
|
## 2016-07-17 - 2.0.14 → 2.0.12 - ES6, indent/deindent, tests
|
||||||
|
Feature enhancements around string indentation and ES6 migration.
|
||||||
|
|
||||||
|
- Switched codebase to ES6 (2.0.14).
|
||||||
|
- Implemented deindent and working indent module; added tests (2.0.12).
|
||||||
|
- Prep work for indent functionality and recompiled builds (2.0.11, 2.0.10).
|
||||||
|
|
||||||
|
## 2016-06-21 - 2.0.9 → 2.0.6 - domain handling improvements
|
||||||
|
Domain parsing and regex improvements.
|
||||||
|
|
||||||
|
- Fixed Domain regex to include '-' and '_' and improved fullName handling (2.0.9, 2.0.8).
|
||||||
|
- Now evaluates Domains without protocol specified (2.0.6).
|
||||||
|
|
||||||
|
## 2016-05-25 - 2.0.4 → 2.0.0 - core features & CI/docs
|
||||||
|
Major 2.0.0 work and related improvements.
|
||||||
|
|
||||||
|
- Now computes zoneName and detects protocol (2.0.0).
|
||||||
|
- Added authors note, improved README and CI (AppVeyor, GitLab CI) across several commits.
|
||||||
|
- Multiple small fixes, dependency updates and YML/CI tweaks (2.0.4, 2.0.3, 2.0.2, 2.0.1).
|
||||||
|
|
||||||
|
## 2016-05-16 - 1.0.3 → 1.0.1 - parser & typings fixes
|
||||||
|
Parser improvements and TypeScript declaration fixes.
|
||||||
|
|
||||||
|
- Correctly parsing a Domain and structure updates (1.0.3).
|
||||||
|
- Fixes for typings and declaration issues (1.0.2, 1.0.1).
|
||||||
|
|
||||||
|
## 2016-04-15 - 1.0.0 → 0.0.3 - initial stable release
|
||||||
|
Initial stable release artifacts.
|
||||||
|
|
||||||
|
- Added TypeScript regex section and performed first 1.0.0 release (1.0.0).
|
||||||
|
- Prior 0.0.x preparatory releases.
|
||||||
|
|
||||||
|
## 2016-02-23 - 0.0.0 → initial - project initialization
|
||||||
|
Project initialization and first commits.
|
||||||
|
|
||||||
|
- Initial commit and early CI/travis updates.
|
15
package.json
15
package.json
@@ -38,19 +38,11 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@git.zone/tsbuild": "^2.1.72",
|
"@git.zone/tsbuild": "^2.1.72",
|
||||||
"@git.zone/tsrun": "^1.2.42",
|
"@git.zone/tsrun": "^1.2.42",
|
||||||
"@git.zone/tstest": "^1.0.86",
|
"@git.zone/tstest": "^2.3.7",
|
||||||
"@push.rocks/tapbundle": "^5.0.15",
|
|
||||||
"@types/node": "^20.11.24"
|
"@types/node": "^20.11.24"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@push.rocks/isounique": "^1.0.5",
|
"@push.rocks/isounique": "^1.0.5"
|
||||||
"@push.rocks/smartenv": "^5.0.12",
|
|
||||||
"@types/randomatic": "^3.1.5",
|
|
||||||
"crypto-random-string": "^5.0.0",
|
|
||||||
"js-base64": "^3.7.7",
|
|
||||||
"randomatic": "^3.1.1",
|
|
||||||
"strip-indent": "^4.0.0",
|
|
||||||
"url": "^0.11.3"
|
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"ts/**/*",
|
"ts/**/*",
|
||||||
@@ -66,5 +58,6 @@
|
|||||||
],
|
],
|
||||||
"browserslist": [
|
"browserslist": [
|
||||||
"last 1 chrome versions"
|
"last 1 chrome versions"
|
||||||
]
|
],
|
||||||
|
"packageManager": "pnpm@10.14.0+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748"
|
||||||
}
|
}
|
11656
pnpm-lock.yaml
generated
11656
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
358
readme.md
358
readme.md
@@ -1,135 +1,363 @@
|
|||||||
# @push.rocks/smartstring
|
# @push.rocks/smartstring
|
||||||
handle strings in smart ways. TypeScript ready.
|
|
||||||
|
🎯 **Smart string manipulation for TypeScript** - Your comprehensive toolkit for handling strings, domains, Git URLs, and encodings with elegance and precision.
|
||||||
|
|
||||||
|
## Why smartstring?
|
||||||
|
|
||||||
|
When working with strings in modern JavaScript/TypeScript applications, you often need more than just basic manipulation. You need to:
|
||||||
|
- Parse and validate domains and URLs
|
||||||
|
- Handle Git repository URLs across different formats
|
||||||
|
- Encode/decode Base64 for data transmission
|
||||||
|
- Normalize indentation in code generators
|
||||||
|
- Create cryptographically secure random strings
|
||||||
|
- Validate string encodings
|
||||||
|
- Parse Docker environment variables
|
||||||
|
|
||||||
|
**smartstring** unifies all these capabilities in a single, tree-shakeable, TypeScript-native package with zero setup overhead.
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
To install `@push.rocks/smartstring`, use the following npm command:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm install @push.rocks/smartstring --save
|
npm install @push.rocks/smartstring --save
|
||||||
```
|
```
|
||||||
|
|
||||||
This will add it to your project's dependencies.
|
Or using pnpm (recommended):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add @push.rocks/smartstring
|
||||||
|
```
|
||||||
|
|
||||||
|
## Features at a Glance
|
||||||
|
|
||||||
|
✅ **Domain parsing** - Extract protocols, subdomains, domains, and TLDs
|
||||||
|
✅ **Git URL handling** - Parse and convert between SSH and HTTPS formats
|
||||||
|
✅ **Base64 encoding** - Standard and URL-safe Base64 operations
|
||||||
|
✅ **Smart indentation** - Indent, outdent, and normalize multi-line strings
|
||||||
|
✅ **Random strings** - Pattern-based and cryptographically secure generation
|
||||||
|
✅ **String normalization** - Clean and standardize whitespace
|
||||||
|
✅ **Type checking** - Validate UTF-8 and Base64 encodings
|
||||||
|
✅ **Docker env parsing** - Transform Docker environment arrays to objects
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The `@push.rocks/smartstring` package provides a powerful set of utilities to handle and manipulate strings in various ways, ready for TypeScript usage. Here's an exhaustive guide to using this package.
|
### 🌐 Domain Parsing
|
||||||
|
|
||||||
### Working with Domain Strings
|
Extract detailed information from any URL or domain string:
|
||||||
|
|
||||||
The `Domain` class helps in parsing and extracting information from domain URLs.
|
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Domain } from '@push.rocks/smartstring';
|
import { Domain } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
// Parse a domain URL
|
const domain = new Domain('https://subdomain.example.com:3000/path');
|
||||||
const myDomain = new Domain('https://sub.example.com');
|
|
||||||
console.log(myDomain.level1); // Output: "com"
|
console.log(domain.protocol); // 'https'
|
||||||
console.log(myDomain.level2); // Output: "example"
|
console.log(domain.hostname); // 'subdomain.example.com'
|
||||||
console.log(myDomain.zoneName); // Output: "example.com"
|
console.log(domain.port); // '3000'
|
||||||
console.log(myDomain.protocol); // Output: "https"
|
console.log(domain.pathname); // '/path'
|
||||||
|
console.log(domain.fullUrl); // 'https://subdomain.example.com:3000/path'
|
||||||
|
|
||||||
|
// Domain level extraction
|
||||||
|
console.log(domain.level1); // 'com' - TLD
|
||||||
|
console.log(domain.level2); // 'example' - Domain
|
||||||
|
console.log(domain.level3); // 'subdomain' - Subdomain
|
||||||
|
console.log(domain.zoneName); // 'example.com' - Full domain without subdomain
|
||||||
|
console.log(domain.subdomain); // 'subdomain'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Handling Git Repositories
|
### 🔧 Git Repository URLs
|
||||||
|
|
||||||
The `GitRepo` class is designed for extracting information from Git repository URLs.
|
Parse and convert Git repository URLs between SSH and HTTPS formats:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { GitRepo } from '@push.rocks/smartstring';
|
import { GitRepo } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
// Parse a Git repository URL
|
// Parse SSH format
|
||||||
const myGitRepo = new GitRepo('git@github.com:user/repo.git');
|
const repo = new GitRepo('git@github.com:user/awesome-project.git');
|
||||||
console.log(myGitRepo.host); // Output: "github.com"
|
console.log(repo.host); // 'github.com'
|
||||||
console.log(myGitRepo.user); // Output: "user"
|
console.log(repo.user); // 'user'
|
||||||
console.log(myGitRepo.repo); // Output: "repo"
|
console.log(repo.repo); // 'awesome-project'
|
||||||
console.log(myGitRepo.sshUrl); // Output: "git@github.com:user/repo.git"
|
console.log(repo.httpsUrl); // 'https://github.com/user/awesome-project.git'
|
||||||
console.log(myGitRepo.httpsUrl); // Output: "https://github.com/user/repo.git"
|
|
||||||
|
// Parse HTTPS format
|
||||||
|
const httpsRepo = new GitRepo('https://gitlab.com/team/project.git');
|
||||||
|
console.log(httpsRepo.sshUrl); // 'git@gitlab.com:team/project.git'
|
||||||
|
console.log(httpsRepo.httpsUrl); // 'https://gitlab.com/team/project.git'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Encoding and Decoding Base64 Strings
|
### 🔐 Base64 Encoding
|
||||||
|
|
||||||
`@push.rocks/smartstring` offers base64 encoding and decoding through the `Base64` class and utility functions.
|
Handle Base64 encoding with both standard and URL-safe variants:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Base64, base64 } from '@push.rocks/smartstring';
|
import { Base64, base64 } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
// Using the Base64 class
|
// Using the Base64 class
|
||||||
const myBase64 = new Base64('hello world', 'string');
|
const encoded = new Base64('Hello World! 👋', 'string');
|
||||||
console.log(myBase64.base64String); // Encoded string
|
console.log(encoded.base64String); // Standard Base64
|
||||||
console.log(myBase64.base64UriString); // Encoded URI compatible string
|
console.log(encoded.base64UriString); // URL-safe Base64
|
||||||
|
console.log(encoded.simpleString); // Decoded string
|
||||||
|
|
||||||
// Using utility functions
|
// Using utility functions
|
||||||
const encoded = base64.encode('hello world');
|
const quickEncode = base64.encode('Secret message');
|
||||||
const decoded = base64.decode(encoded);
|
const quickDecode = base64.decode(quickEncode);
|
||||||
console.log(encoded); // Encoded string
|
|
||||||
console.log(decoded); // "hello world"
|
// Validate Base64 strings
|
||||||
|
console.log(base64.isBase64('SGVsbG8=')); // true
|
||||||
|
console.log(base64.isBase64('Not Base64!')); // false
|
||||||
|
|
||||||
|
// URL-safe Base64 operations
|
||||||
|
const urlSafeEncoded = base64.encodeUri('https://example.com/path?param=value');
|
||||||
|
const urlSafeDecoded = base64.decodeUri(urlSafeEncoded);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Applying Indentation
|
### 📐 Smart Indentation
|
||||||
|
|
||||||
SmartString allows you to easily indent strings or normalize indentation across a multi-line string.
|
Manage indentation in multi-line strings with precision:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { indent } from '@push.rocks/smartstring';
|
import { indent } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
// Indent a string by 4 spaces
|
// Indent with spaces
|
||||||
const indentedString = indent.indent('Some text\nAnother line', 4);
|
const indented = indent.indent('Line 1\nLine 2\nLine 3', 4);
|
||||||
console.log(indentedString);
|
// Result:
|
||||||
|
// Line 1
|
||||||
|
// Line 2
|
||||||
|
// Line 3
|
||||||
|
|
||||||
// Indent using a prefix
|
// Indent with custom prefix
|
||||||
const prefixedString = indent.indentWithPrefix('Line 1\nLine 2', '> ');
|
const prefixed = indent.indentWithPrefix('Item 1\nItem 2', '> ');
|
||||||
console.log(prefixedString);
|
// Result:
|
||||||
|
// > Item 1
|
||||||
|
// > Item 2
|
||||||
|
|
||||||
// Normalize indentation
|
// Add prefix to first line only
|
||||||
const normalizedString = indent.normalize(' Some indented text\n Another line');
|
const firstLinePrefixed = indent.addPrefix('Chapter\nContent here', '# ');
|
||||||
console.log(normalizedString);
|
// Result:
|
||||||
|
// # Chapter
|
||||||
|
// Content here
|
||||||
|
|
||||||
|
// Normalize irregular indentation
|
||||||
|
const messy = `
|
||||||
|
function hello() {
|
||||||
|
console.log('Hi');
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
const clean = indent.normalize(messy);
|
||||||
|
// Result: Properly aligned with minimum indentation preserved
|
||||||
```
|
```
|
||||||
|
|
||||||
### Creating Random or Encrypted Strings
|
### 🎲 Random String Generation
|
||||||
|
|
||||||
Create random strings based on patterns or generate cryptographically strong random strings.
|
Create random strings with specific patterns or cryptographic security:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { create } from '@push.rocks/smartstring';
|
import { create } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
// Create a random string
|
// Pattern-based random strings
|
||||||
const randomString = create.createRandomString('aA0', 10);
|
// A = uppercase, a = lowercase, 0 = number, ! = special, * = any
|
||||||
console.log(randomString); // Example output: "a9mB8v2Dq1"
|
const password = create.createRandomString('Aa0!', 16);
|
||||||
|
// Example: "Kg7$Lp2@Qm9#Xn4!"
|
||||||
|
|
||||||
// Create a crypto-random string
|
const alphanumeric = create.createRandomString('Aa0', 10);
|
||||||
const cryptoString = create.createCryptoRandomString(10);
|
// Example: "K7gLp2Qm9X"
|
||||||
console.log(cryptoString); // Example output: "f28Bb90aCc"
|
|
||||||
|
const numbers = create.createRandomString('0', 6);
|
||||||
|
// Example: "472819"
|
||||||
|
|
||||||
|
// Cryptographically secure random strings
|
||||||
|
const cryptoId = create.createCryptoRandomString();
|
||||||
|
// Example: "f7b2d8e0-3c4a-4b9c-8d2e-1f0a7b9c8d7e"
|
||||||
```
|
```
|
||||||
|
|
||||||
### Normalizing Strings
|
### 🧹 String Normalization
|
||||||
|
|
||||||
Normalize strings by removing leading/trailing whitespace, fixing indentation, and more.
|
Clean and standardize strings for consistent formatting:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { normalize } from '@push.rocks/smartstring';
|
import { normalize } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
// Normalize a multi-line string
|
const messyString = `
|
||||||
const exampleString = `
|
This text has
|
||||||
This is an example.
|
inconsistent indentation
|
||||||
The indentation will be fixed.
|
and too much whitespace
|
||||||
|
|
||||||
|
|
||||||
|
between lines...
|
||||||
`;
|
`;
|
||||||
const normalized = normalize.standard(exampleString);
|
|
||||||
console.log(normalized);
|
const cleaned = normalize.standard(messyString);
|
||||||
|
// Result: Properly formatted with consistent spacing
|
||||||
|
|
||||||
|
// Custom normalization
|
||||||
|
const customNormalized = normalize.normal(messyString);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Working with Docker Environment Variables
|
### 🔍 String Type Validation
|
||||||
|
|
||||||
Transform an array of Docker environment variables into an object for easy access.
|
Check string encodings and types:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { type } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
|
// Check if string is valid UTF-8
|
||||||
|
const isValidUtf8 = type.isUtf8('Hello 世界');
|
||||||
|
console.log(isValidUtf8); // true
|
||||||
|
|
||||||
|
// Check if string is Base64
|
||||||
|
const isBase64String = type.isBase64('SGVsbG8gV29ybGQ=');
|
||||||
|
console.log(isBase64String); // true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 🐳 Docker Environment Variables
|
||||||
|
|
||||||
|
Parse Docker-style environment variable arrays:
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { docker } from '@push.rocks/smartstring';
|
import { docker } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
const envVars = ['NODE_ENV=production', 'PORT=3000'];
|
const envArray = [
|
||||||
const envObject = docker.makeEnvObject(envVars);
|
'NODE_ENV=production',
|
||||||
console.log(envObject.NODE_ENV); // Output: "production"
|
'PORT=3000',
|
||||||
console.log(envObject.PORT); // Output: "3000"
|
'DATABASE_URL=postgresql://localhost:5432/mydb',
|
||||||
|
'API_KEY=secret123'
|
||||||
|
];
|
||||||
|
|
||||||
|
const envObject = docker.makeEnvObject(envArray);
|
||||||
|
|
||||||
|
console.log(envObject.NODE_ENV); // 'production'
|
||||||
|
console.log(envObject.PORT); // '3000'
|
||||||
|
console.log(envObject.DATABASE_URL); // 'postgresql://localhost:5432/mydb'
|
||||||
|
console.log(envObject.API_KEY); // 'secret123'
|
||||||
|
|
||||||
|
// Use in Docker-related configurations
|
||||||
|
// Perfect for parsing process.env or Docker Compose outputs
|
||||||
```
|
```
|
||||||
|
|
||||||
This guide covers the primary features of `@push.rocks/smartstring`, making string manipulation and information extraction simple and efficient in your TypeScript projects.
|
## API Reference
|
||||||
|
|
||||||
|
### Classes
|
||||||
|
|
||||||
|
- **`Domain`** - URL/domain parser with component extraction
|
||||||
|
- **`GitRepo`** - Git repository URL parser and converter
|
||||||
|
- **`Base64`** - Base64 encoder/decoder with multiple formats
|
||||||
|
|
||||||
|
### Modules
|
||||||
|
|
||||||
|
- **`create`** - Random string generation
|
||||||
|
- `createRandomString(pattern, length, options)` - Pattern-based generation
|
||||||
|
- `createCryptoRandomString()` - Cryptographically secure strings
|
||||||
|
|
||||||
|
- **`indent`** - Indentation management
|
||||||
|
- `indent(text, spaces)` - Add spaces to each line
|
||||||
|
- `indentWithPrefix(text, prefix)` - Add custom prefix to each line
|
||||||
|
- `normalize(text)` - Fix inconsistent indentation
|
||||||
|
- `addPrefix(text, prefix)` - Add prefix to first line only
|
||||||
|
|
||||||
|
- **`normalize`** - String normalization
|
||||||
|
- `standard(text)` - Apply standard normalization
|
||||||
|
- `normal(text)` - Basic normalization
|
||||||
|
|
||||||
|
- **`type`** - String type checking
|
||||||
|
- `isUtf8(text)` - Validate UTF-8 encoding
|
||||||
|
- `isBase64(text)` - Validate Base64 format
|
||||||
|
|
||||||
|
- **`base64`** - Base64 utilities
|
||||||
|
- `encode(text)` - Standard Base64 encoding
|
||||||
|
- `decode(text)` - Standard Base64 decoding
|
||||||
|
- `encodeUri(text)` - URL-safe Base64 encoding
|
||||||
|
- `decodeUri(text)` - URL-safe Base64 decoding
|
||||||
|
- `isBase64(text)` - Check if string is valid Base64
|
||||||
|
|
||||||
|
- **`docker`** - Docker utilities
|
||||||
|
- `makeEnvObject(envArray)` - Convert env array to object
|
||||||
|
|
||||||
|
## Real-World Examples
|
||||||
|
|
||||||
|
### Building a URL Shortener
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Domain, create, base64 } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
|
function createShortUrl(longUrl: string): string {
|
||||||
|
const domain = new Domain(longUrl);
|
||||||
|
const shortCode = create.createRandomString('Aa0', 6);
|
||||||
|
const encoded = base64.encodeUri(`${domain.hostname}${domain.pathname}`);
|
||||||
|
|
||||||
|
return `short.ly/${shortCode}`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Processing Code Templates
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { indent, normalize } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
|
function generateComponent(name: string, props: string[]): string {
|
||||||
|
const propsSection = props
|
||||||
|
.map(prop => `${prop}: string;`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const template = `
|
||||||
|
export interface ${name}Props {
|
||||||
|
${indent.indent(propsSection, 2)}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ${name}(props: ${name}Props) {
|
||||||
|
${indent.indent('// Component implementation', 2)}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
return normalize.standard(template);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Git Repository Manager
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { GitRepo } from '@push.rocks/smartstring';
|
||||||
|
|
||||||
|
class RepoManager {
|
||||||
|
repos: Map<string, GitRepo> = new Map();
|
||||||
|
|
||||||
|
addRepo(url: string): void {
|
||||||
|
const repo = new GitRepo(url);
|
||||||
|
this.repos.set(repo.repo, repo);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCloneCommand(name: string, useSSH = false): string {
|
||||||
|
const repo = this.repos.get(name);
|
||||||
|
if (!repo) throw new Error('Repository not found');
|
||||||
|
|
||||||
|
const url = useSSH ? repo.sshUrl : repo.httpsUrl;
|
||||||
|
return `git clone ${url}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Browser Support
|
||||||
|
|
||||||
|
This package is built for modern environments and includes:
|
||||||
|
- ✅ Full ES Module support
|
||||||
|
- ✅ Tree-shaking ready
|
||||||
|
- ✅ TypeScript definitions
|
||||||
|
- ✅ Browser-compatible (via bundlers)
|
||||||
|
- ✅ Node.js 14+ support
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Clone the repository
|
||||||
|
git clone https://code.foss.global/push.rocks/smartstring.git
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
# Run tests
|
||||||
|
pnpm test
|
||||||
|
|
||||||
|
# Build the project
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
## License and Legal Information
|
## License and Legal Information
|
||||||
|
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as smartstring from '../ts/index.js';
|
import * as smartstring from '../ts/index.js';
|
||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
// Base64
|
// Base64
|
||||||
let testBase64: smartstring.Base64;
|
let testBase64: smartstring.Base64;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as smartstring from '../ts/index.js';
|
import * as smartstring from '../ts/index.js';
|
||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
// Docker
|
// Docker
|
||||||
tap.test('expect create a Env Object', async () => {
|
tap.test('expect create a Env Object', async () => {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as smartstring from '../ts/index.js';
|
import * as smartstring from '../ts/index.js';
|
||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
// Domain
|
// Domain
|
||||||
let testDomain: smartstring.Domain;
|
let testDomain: smartstring.Domain;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as smartstring from '../ts/index.js';
|
import * as smartstring from '../ts/index.js';
|
||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
// git
|
// git
|
||||||
let testGit: smartstring.GitRepo;
|
let testGit: smartstring.GitRepo;
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
import * as smartstring from '../ts/index.js';
|
import * as smartstring from '../ts/index.js';
|
||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
// indent
|
// indent
|
||||||
let testString = `
|
let testString = `
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
|
|
||||||
import * as smartstring from '../ts/index.js';
|
import * as smartstring from '../ts/index.js';
|
||||||
|
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
import { tap, expect } from '@push.rocks/tapbundle';
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||||
import * as smartstring from '../ts/index.js';
|
import * as smartstring from '../ts/index.js';
|
||||||
|
|
||||||
tap.test('should state valuid utf8', async () => {
|
tap.test('should state valuid utf8', async () => {
|
||||||
|
@@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* autocreated commitinfo by @pushrocks/commitinfo
|
* autocreated commitinfo by @push.rocks/commitinfo
|
||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@push.rocks/smartstring',
|
name: '@push.rocks/smartstring',
|
||||||
version: '4.0.15',
|
version: '4.1.0',
|
||||||
description: 'handle strings in smart ways. TypeScript ready.'
|
description: 'A library for handling strings in smart ways, including manipulation and encoding, with TypeScript support.'
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,89 @@
|
|||||||
import * as plugins from './smartstring.plugins.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* the type for base 64
|
* the type for base 64
|
||||||
*/
|
*/
|
||||||
export type TStringInputType = 'string' | 'base64' | 'base64uri';
|
export type TStringInputType = 'string' | 'base64' | 'base64uri';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cross-platform base64 implementation
|
||||||
|
* Works in both Node.js and browser environments
|
||||||
|
*/
|
||||||
|
const universalBase64 = {
|
||||||
|
encode: (str: string): string => {
|
||||||
|
if (typeof Buffer !== 'undefined') {
|
||||||
|
// Node.js environment
|
||||||
|
return Buffer.from(str, 'utf8').toString('base64');
|
||||||
|
} else if (typeof btoa !== 'undefined') {
|
||||||
|
// Browser environment
|
||||||
|
// Handle Unicode properly
|
||||||
|
const utf8Bytes = new TextEncoder().encode(str);
|
||||||
|
const binaryString = Array.from(utf8Bytes, byte => String.fromCharCode(byte)).join('');
|
||||||
|
return btoa(binaryString);
|
||||||
|
} else {
|
||||||
|
// Fallback pure JS implementation
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
|
const bytes = new TextEncoder().encode(str);
|
||||||
|
let result = '';
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < bytes.length) {
|
||||||
|
const a = bytes[i++];
|
||||||
|
const b = i < bytes.length ? bytes[i++] : 0;
|
||||||
|
const c = i < bytes.length ? bytes[i++] : 0;
|
||||||
|
|
||||||
|
const bitmap = (a << 16) | (b << 8) | c;
|
||||||
|
|
||||||
|
result += chars.charAt((bitmap >> 18) & 63);
|
||||||
|
result += chars.charAt((bitmap >> 12) & 63);
|
||||||
|
result += i - 2 < bytes.length ? chars.charAt((bitmap >> 6) & 63) : '=';
|
||||||
|
result += i - 1 < bytes.length ? chars.charAt(bitmap & 63) : '=';
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
decode: (str: string): string => {
|
||||||
|
// Handle base64uri by converting back to standard base64
|
||||||
|
const base64String = str
|
||||||
|
.replace(/-/g, '+')
|
||||||
|
.replace(/_/g, '/')
|
||||||
|
.padEnd(str.length + ((4 - (str.length % 4)) % 4), '=');
|
||||||
|
|
||||||
|
if (typeof Buffer !== 'undefined') {
|
||||||
|
// Node.js environment
|
||||||
|
return Buffer.from(base64String, 'base64').toString('utf8');
|
||||||
|
} else if (typeof atob !== 'undefined') {
|
||||||
|
// Browser environment
|
||||||
|
const binaryString = atob(base64String);
|
||||||
|
const bytes = new Uint8Array(binaryString.length);
|
||||||
|
for (let i = 0; i < binaryString.length; i++) {
|
||||||
|
bytes[i] = binaryString.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return new TextDecoder().decode(bytes);
|
||||||
|
} else {
|
||||||
|
// Fallback pure JS implementation
|
||||||
|
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
|
||||||
|
let bytes: number[] = [];
|
||||||
|
let i = 0;
|
||||||
|
|
||||||
|
while (i < base64String.length) {
|
||||||
|
const encoded1 = chars.indexOf(base64String.charAt(i++));
|
||||||
|
const encoded2 = chars.indexOf(base64String.charAt(i++));
|
||||||
|
const encoded3 = chars.indexOf(base64String.charAt(i++));
|
||||||
|
const encoded4 = chars.indexOf(base64String.charAt(i++));
|
||||||
|
|
||||||
|
const bitmap = (encoded1 << 18) | (encoded2 << 12) | (encoded3 << 6) | encoded4;
|
||||||
|
|
||||||
|
bytes.push((bitmap >> 16) & 255);
|
||||||
|
if (encoded3 !== 64) bytes.push((bitmap >> 8) & 255);
|
||||||
|
if (encoded4 !== 64) bytes.push(bitmap & 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new TextDecoder().decode(new Uint8Array(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* handle base64 strings
|
* handle base64 strings
|
||||||
*/
|
*/
|
||||||
@@ -50,21 +129,24 @@ export let base64 = {
|
|||||||
* encodes the string
|
* encodes the string
|
||||||
*/
|
*/
|
||||||
encode: (stringArg: string) => {
|
encode: (stringArg: string) => {
|
||||||
return plugins.jsBase64.encode(stringArg);
|
return universalBase64.encode(stringArg);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* encodes a stringArg to base64 uri style
|
* encodes a stringArg to base64 uri style
|
||||||
*/
|
*/
|
||||||
encodeUri: (stringArg: string) => {
|
encodeUri: (stringArg: string) => {
|
||||||
return plugins.jsBase64.encodeURI(stringArg);
|
return universalBase64.encode(stringArg)
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_')
|
||||||
|
.replace(/=/g, '');
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* decodes a base64 encoded string
|
* decodes a base64 encoded string
|
||||||
*/
|
*/
|
||||||
decode: (stringArg: string) => {
|
decode: (stringArg: string) => {
|
||||||
return plugins.jsBase64.decode(stringArg);
|
return universalBase64.decode(stringArg);
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,5 +1,63 @@
|
|||||||
import * as plugins from './smartstring.plugins.js';
|
import * as plugins from './smartstring.plugins.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cross-platform random number generator
|
||||||
|
* Uses crypto.getRandomValues in browser and Math.random as fallback
|
||||||
|
*/
|
||||||
|
const getRandomInt = (min: number, max: number): number => {
|
||||||
|
if (typeof globalThis !== 'undefined' && globalThis.crypto && globalThis.crypto.getRandomValues) {
|
||||||
|
// Browser environment with crypto API
|
||||||
|
const range = max - min;
|
||||||
|
const array = new Uint32Array(1);
|
||||||
|
globalThis.crypto.getRandomValues(array);
|
||||||
|
return min + (array[0] % range);
|
||||||
|
} else {
|
||||||
|
// Fallback to Math.random for environments without crypto
|
||||||
|
return Math.floor(Math.random() * (max - min)) + min;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom implementation of randomatic pattern-based string generator
|
||||||
|
* Pattern characters:
|
||||||
|
* A - Uppercase letter
|
||||||
|
* a - Lowercase letter
|
||||||
|
* 0 - Number (0-9)
|
||||||
|
* ! - Special character
|
||||||
|
* * - Any character (A, a, 0, or !)
|
||||||
|
*/
|
||||||
|
const customRandomatic = (pattern: string, length?: number, options?: any): string => {
|
||||||
|
const charSets = {
|
||||||
|
'A': 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||||
|
'a': 'abcdefghijklmnopqrstuvwxyz',
|
||||||
|
'0': '0123456789',
|
||||||
|
'!': '!@#$%^&*()_+-=[]{}|;:,.<>?',
|
||||||
|
'*': 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+-=[]{}|;:,.<>?'
|
||||||
|
};
|
||||||
|
|
||||||
|
// If length is provided, repeat the pattern to match length
|
||||||
|
let actualPattern = pattern;
|
||||||
|
if (length && length > pattern.length) {
|
||||||
|
actualPattern = pattern.repeat(Math.ceil(length / pattern.length)).slice(0, length);
|
||||||
|
} else if (length) {
|
||||||
|
actualPattern = pattern.slice(0, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
let result = '';
|
||||||
|
for (const char of actualPattern) {
|
||||||
|
if (charSets[char]) {
|
||||||
|
const charSet = charSets[char];
|
||||||
|
const randomIndex = getRandomInt(0, charSet.length);
|
||||||
|
result += charSet[randomIndex];
|
||||||
|
} else {
|
||||||
|
// If not a pattern character, use it literally
|
||||||
|
result += char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* creates a random string
|
* creates a random string
|
||||||
*
|
*
|
||||||
@@ -17,7 +75,7 @@ export const createRandomString = (
|
|||||||
lengthArg?: number,
|
lengthArg?: number,
|
||||||
optionsArg?: any
|
optionsArg?: any
|
||||||
): string => {
|
): string => {
|
||||||
return plugins.randomatic(patternArg, lengthArg, optionsArg);
|
return customRandomatic(patternArg, lengthArg, optionsArg);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
import * as plugins from './smartstring.plugins.js';
|
|
||||||
|
|
||||||
export class Domain {
|
export class Domain {
|
||||||
public fullName: string;
|
public fullName: string;
|
||||||
public level1: string;
|
public level1: string;
|
||||||
@@ -14,14 +12,14 @@ export class Domain {
|
|||||||
public domainName;
|
public domainName;
|
||||||
public subDomain;
|
public subDomain;
|
||||||
public port;
|
public port;
|
||||||
public nodeParsedUrl: plugins.url.UrlWithStringQuery;
|
public nodeParsedUrl: URL;
|
||||||
constructor(domainStringArg: string) {
|
constructor(domainStringArg: string) {
|
||||||
// lets do the node standard stuff first
|
// lets do the node standard stuff first
|
||||||
this.protocol = this._protocolRegex(domainStringArg);
|
this.protocol = this._protocolRegex(domainStringArg);
|
||||||
if (!this.protocol) {
|
if (!this.protocol) {
|
||||||
domainStringArg = `https://${domainStringArg}`;
|
domainStringArg = `https://${domainStringArg}`;
|
||||||
}
|
}
|
||||||
this.nodeParsedUrl = plugins.url.parse(domainStringArg);
|
this.nodeParsedUrl = new URL(domainStringArg);
|
||||||
this.port = this.nodeParsedUrl.port;
|
this.port = this.nodeParsedUrl.port;
|
||||||
|
|
||||||
// lets do the rest after
|
// lets do the rest after
|
||||||
|
@@ -1,5 +1,3 @@
|
|||||||
import * as plugins from './smartstring.plugins.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* replaces all occurences of something in a string
|
* replaces all occurences of something in a string
|
||||||
* @param stringArg
|
* @param stringArg
|
||||||
@@ -10,6 +8,38 @@ export const replaceAll = (stringArg: string, searchPattern: string, replacement
|
|||||||
return stringArg.replace(new RegExp(searchPattern, 'g'), replacementString);
|
return stringArg.replace(new RegExp(searchPattern, 'g'), replacementString);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom implementation of strip-indent
|
||||||
|
* Removes the minimum indentation from all lines
|
||||||
|
*/
|
||||||
|
const stripIndent = (str: string): string => {
|
||||||
|
const lines = str.split('\n');
|
||||||
|
|
||||||
|
// Find the minimum indentation (ignoring empty lines)
|
||||||
|
let minIndent = Infinity;
|
||||||
|
for (const line of lines) {
|
||||||
|
if (line.trim().length > 0) {
|
||||||
|
const match = line.match(/^(\s*)/);
|
||||||
|
if (match) {
|
||||||
|
minIndent = Math.min(minIndent, match[1].length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no indentation found, return original string
|
||||||
|
if (minIndent === Infinity || minIndent === 0) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the minimum indentation from all lines
|
||||||
|
return lines.map(line => {
|
||||||
|
if (line.length >= minIndent) {
|
||||||
|
return line.slice(minIndent);
|
||||||
|
}
|
||||||
|
return line;
|
||||||
|
}).join('\n');
|
||||||
|
};
|
||||||
|
|
||||||
export interface INormalizeOptions {
|
export interface INormalizeOptions {
|
||||||
stripLeadingTrailingEmptyLines?: boolean;
|
stripLeadingTrailingEmptyLines?: boolean;
|
||||||
stripAllEmptyLines?: boolean;
|
stripAllEmptyLines?: boolean;
|
||||||
@@ -27,7 +57,7 @@ export const standard = (stringArg: string, options?: INormalizeOptions): string
|
|||||||
let result = stringArg;
|
let result = stringArg;
|
||||||
|
|
||||||
if (!options || options.stripIndent) {
|
if (!options || options.stripIndent) {
|
||||||
result = plugins.stripIndent(result); // fix indention
|
result = stripIndent(result); // fix indention
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options || options.normalizeNewline) {
|
if (!options || options.normalizeNewline) {
|
||||||
|
@@ -1,17 +1,4 @@
|
|||||||
// node native
|
// @push.rocks ecosystem
|
||||||
import * as smartenv from '@push.rocks/smartenv';
|
|
||||||
const smartenvInstance = new smartenv.Smartenv();
|
|
||||||
import * as isounique from '@push.rocks/isounique';
|
import * as isounique from '@push.rocks/isounique';
|
||||||
|
|
||||||
export { isounique };
|
export { isounique };
|
||||||
|
|
||||||
import * as url from 'url';
|
|
||||||
export { url };
|
|
||||||
|
|
||||||
// third party
|
|
||||||
import { Base64 as jsBase64 } from 'js-base64';
|
|
||||||
|
|
||||||
import stripIndent from 'strip-indent';
|
|
||||||
import randomatic from 'randomatic';
|
|
||||||
|
|
||||||
export { jsBase64, stripIndent, randomatic };
|
|
||||||
|
Reference in New Issue
Block a user