Skip to content

Commit 1616ee1

Browse files
authored
Merge pull request #28 from benjGam/develop
Release v2.3.0
2 parents c85f5ca + 7195a03 commit 1616ee1

17 files changed

+821
-333
lines changed

.github/workflows/cd.yml

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
name: Node.js CD
22

33
on:
4-
push:
5-
branches: ['main']
4+
pull_request:
5+
branches:
6+
- main
7+
types: [closed]
68

79
jobs:
10+
if: ${{ github.event.pull_request.merged }}
811
publish-npm:
912
runs-on: ubuntu-latest
1013
steps:

CHANGELOG.md

+28
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,31 @@
1+
# Version 2.3.0
2+
3+
- `Enhancement`:
4+
- `Case convertion`: Previously algorithms responsible of converting a string to another case was obviously to light, so, the range of managed uses was too poor, i reworked those algorithms and they're now better from far that was they were. The new ones got tested and passes tests, it's more than sure that i didn't test all of cases, but an enhancement of this feature is truely brang to package.
5+
- `Added`:
6+
- `Case` abstract class is now used to reliably create case objects.
7+
- `CamelCase` class implement logic of previous correspondant object.
8+
- `PascalCase` class implement logic of previous correspondant object.
9+
- `SnakeCase` class implement logic of previous correspondant object.
10+
- `LowerCase` class implement logic of previous correspondant object.
11+
- `UpperCase` class implement logic of previous correspondant object.
12+
- `StringUtils (main.ts)`
13+
- `isConsiderableCharSequence(str: string): boolean` method has been implemented and used to check if a given string contains atleast 2 chars (excepted blanks ones).
14+
- `containsConsiderableCharSequence(stringTable: string[]): boolean` method has been implemented and used to check if a given table of string contains atleast one considerable (determined by `isConsiderableCharSequence` criterias) element.
15+
- `containsOnlyConsiderableCharSequences(stringTable: string[]): boolean` method has been implemented and used to check if a given table of string contains only considerable (determined by `isConsiderableCharSequence` criterias) elements.
16+
- `removeBlankChars(str): string` method has been implemented and could be use to remove blank chars from a given string.
17+
- `blendIrrelevantStringsInRelevantOnes(str: string): string[]` method has been implemented and should be used to blend orphan chars (a char adjacent to blank chars) to the last considerable subsequence of char (determined by `isConsiderableCharSequence` criterias) in a given string.
18+
- `Removed`:
19+
- `[BREAKING CHANGES]`:
20+
- `Case` type has been renamed `CaseName` to avoid collision between new `Case` object type and previous `Case` type.
21+
- `ICase` interface has been removed, it was useless to keep working with it.
22+
- `Refactor`:
23+
- `[BREAKING CHANGES]`:
24+
- `determineCase(str: string): ICase` method signature changed to `determineCase(str: string): Case`.
25+
- `convertToCase(str: string, caseToConvert: Case): string` method signature moved to `convertToCase(str: string, caseToConvert: CaseName): string`.
26+
- `knownCases` table is now a `:Case[]` instead of `:ICase[]`.
27+
- Tests has been refactored to avoid rewriting loops again and again, they now use `JestRunner` utils class.
28+
129
# Version 2.2.0
230

331
- `Added`:

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "string-utils-ts",
3-
"version": "2.2.0",
3+
"version": "2.3.0",
44
"description": "Provide some useful functions for strings",
55
"main": "./lib",
66
"scripts": {

src/case.ts

+111-74
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,32 @@
1-
import StringUtilsWord from './word-ending-utils';
2-
3-
/**
4-
* This interface provide a structure for literal objects
5-
* It brings reliable way to add unmanaged cases without any other code writing
6-
*
7-
* @interface ICase
8-
* @field {string} name is used to represent a matcher & splitter for case
9-
* @field {RegExp} matcher is used with Regex operations to check if a given string match
10-
* @field {RegExp | string} splitter is used to split given string who match `matcher`
11-
*
12-
*/
13-
interface ICase {
14-
name: Case;
15-
matcher: RegExp;
16-
splitter: RegExp | string;
17-
}
1+
import CamelCase from './case/camel-case';
2+
import Case from './case/Case';
3+
import LowerCase from './case/lower-case';
4+
import PascalCase from './case/pascal-case';
5+
import SnakeCase from './case/snake-case';
6+
import UpperCase from './case/upper-case';
7+
import { StringUtils } from './main';
188

199
/**
2010
* This type is used to ensure case selection is reliable
2111
*/
22-
23-
export type Case =
24-
| 'snakeCase'
25-
| 'pascalCase'
26-
| 'lowerCase'
27-
| 'upperCase'
28-
| 'camelCase';
12+
export type CaseName =
13+
| 'SnakeCase'
14+
| 'PascalCase'
15+
| 'LowerCase'
16+
| 'UpperCase'
17+
| 'CamelCase';
2918

3019
/**
3120
* This table store few basics cases.
3221
*
3322
* @method StringUtilsCase.determineCase <- Use it
3423
*/
35-
const knownCases: ICase[] = [
36-
{
37-
name: 'snakeCase',
38-
matcher: /(\w+)_(\w+)/,
39-
splitter: '_',
40-
},
41-
{
42-
name: 'pascalCase',
43-
matcher: /^[A-Z][a-z]+(?:[A-Z][a-z]+)*$/,
44-
splitter: /([A-Z]+[a-z]*)/,
45-
},
46-
{
47-
name: 'lowerCase',
48-
matcher: /^[a-z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\s]*$/,
49-
splitter: '',
50-
},
51-
{
52-
name: 'upperCase',
53-
matcher: /^[A-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\s]*$/,
54-
splitter: '',
55-
},
56-
{
57-
name: 'camelCase',
58-
matcher: /^[a-z]+(?:[A-Z][a-z]+)*$/,
59-
splitter: /([A-Z]+[a-z]*)/,
60-
},
24+
const knownCases: Case[] = [
25+
new SnakeCase(),
26+
new PascalCase(),
27+
new LowerCase(),
28+
new UpperCase(),
29+
new CamelCase(),
6130
];
6231

6332
export default class StringUtilsCase {
@@ -68,7 +37,7 @@ export default class StringUtilsCase {
6837
*
6938
* @returns {ICase} - The case of given string
7039
*/
71-
public static determineCase(str: string): ICase | undefined {
40+
public static determineCase(str: string): Case | undefined {
7241
return knownCases.find((caseObject) => caseObject.matcher.test(str));
7342
}
7443

@@ -105,31 +74,99 @@ export default class StringUtilsCase {
10574
* case: snakeCase
10675
* returns: this_is_a_test
10776
*/
108-
public static convertToCase(str: string, caseToConvert: Case): string {
109-
if (str.trim().replaceAll(' ', '').length < 2) return str;
77+
public static convertToCase(str: string, caseToConvert: CaseName): string {
78+
switch (caseToConvert) {
79+
case 'LowerCase':
80+
return this.convertToCaseLogic('LowerCase', str);
81+
case 'UpperCase':
82+
return this.convertToCaseLogic('UpperCase', str);
83+
case 'CamelCase':
84+
return this.convertToCaseLogic('CamelCase', str);
85+
case 'PascalCase':
86+
return this.convertToCaseLogic('PascalCase', str);
87+
case 'SnakeCase':
88+
return this.convertToCaseLogic('SnakeCase', str);
89+
}
90+
}
11091

111-
const splittedByCaseString = this.splitByCase(str);
112-
if (splittedByCaseString.length == 1) return str; // Case was unsucessfully determinated
92+
/**
93+
* Returns a given string converted to camelCase
94+
*
95+
* @param {string} str - The string to convert
96+
*
97+
* @example
98+
* str: ThisIsMyExample
99+
* returns: thisIsMyExample
100+
* @example
101+
* str: thisIsMyExample
102+
* returns: thisIsMyExample
103+
*/
104+
public static toCamelCase(str: string): string {
105+
return this.convertToCaseLogic('CamelCase', str);
106+
}
113107

114-
switch (caseToConvert) {
115-
case 'lowerCase':
116-
return splittedByCaseString.join('').toLowerCase();
117-
case 'upperCase':
118-
return splittedByCaseString.join('').toUpperCase();
119-
case 'camelCase':
120-
return splittedByCaseString
121-
.map((subSequence, index) =>
122-
index == 0
123-
? subSequence.toLowerCase()
124-
: StringUtilsWord.formatWord(subSequence),
125-
)
126-
.join('');
127-
case 'pascalCase':
128-
return splittedByCaseString
129-
.map((subSequence) => StringUtilsWord.formatWord(subSequence))
130-
.join('');
131-
case 'snakeCase':
132-
return splittedByCaseString.join('_').toLowerCase();
108+
/**
109+
* Returns a given string converted to PamelCase
110+
*
111+
* @param {string} str - The string to convert
112+
*
113+
* @example
114+
* str: ThisIsMyExample
115+
* returns: ThisIsMyExample
116+
* @example
117+
* str: thisIsMyExample
118+
* returns: ThisIsMyExample
119+
*/
120+
public static toPascalCase(str: string): string {
121+
return this.convertToCaseLogic('PascalCase', str);
122+
}
123+
124+
/**
125+
* Returns a given string converted to snakeCase
126+
*
127+
* @param {string} str - The string to convert
128+
*
129+
* @example
130+
* str: ThisIsMyExample
131+
* returns: this_is_my_example
132+
* @example
133+
* str: thisIsMyExample
134+
* returns: this_is_my_example
135+
*/
136+
public static toSnakeCase(str: string): string {
137+
return this.convertToCaseLogic('SnakeCase', str);
138+
}
139+
140+
private static convertToCaseLogic(toCase: CaseName, str: string): string {
141+
const correspondantKnownCase = knownCases.find(
142+
(caseInstance: Case) => caseInstance.name == toCase,
143+
);
144+
145+
// do not apply any process overload for nothing
146+
if (!StringUtils.isConsiderableCharSequence(str)) return str;
147+
148+
if (!str.includes(' ')) {
149+
// str do not need blending operation
150+
if (!this.determineCase(str)) return str;
151+
152+
return correspondantKnownCase.basicConversionReturnFn(
153+
this.splitByCase(str),
154+
str,
155+
); //Apply stored behavior of correspondantKnownCase and return the processed value
133156
}
157+
158+
// str need blending operation
159+
160+
const removedBlankChars = StringUtils.removeBlankChars(str);
161+
if (this.determineCase(removedBlankChars).name == toCase) {
162+
//str is per any chance alraedy cased as wanted but needed to be cleanedFrom any blank chars
163+
return removedBlankChars;
164+
}
165+
166+
return correspondantKnownCase.blendedConversionReturnFn(
167+
this.splitByCase(str),
168+
StringUtils.blendIrrelevantStringsInRelevantOnes(str),
169+
str,
170+
); //Apply stored behavior of correspondantKnownCase and return the processed value
134171
}
135172
}

src/case/Case.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export default abstract class Case {
2+
protected abstract _matcher: RegExp;
3+
protected abstract _splitter: RegExp | string;
4+
5+
/**
6+
* This method is a wrapper for conversion to targeted case (when `str` is already cased in managed one)
7+
*/
8+
public abstract basicConversionReturnFn(
9+
splittedByCase: string[],
10+
str: string,
11+
): string;
12+
13+
/**
14+
* This method is a wrapper for conversion to targeted case (when `str` contains spaces)
15+
*/
16+
public abstract blendedConversionReturnFn(
17+
splittedByCase: string[],
18+
blended: string[],
19+
str: string,
20+
): string;
21+
22+
/**
23+
* Returns the name of class
24+
*/
25+
public get name(): string {
26+
return this.constructor.name;
27+
}
28+
29+
/**
30+
* Returns the matcher for Regex linked to targeted case class
31+
*/
32+
public get matcher(): RegExp {
33+
return this._matcher;
34+
}
35+
36+
/**
37+
* Returns the splitter for Regex or string linked to targeted case class
38+
*/
39+
public get splitter(): RegExp | string {
40+
return this._splitter;
41+
}
42+
}

src/case/camel-case.ts

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import Case from './Case';
2+
import StringUtilsCase from '../case';
3+
import StringUtilsWord from '../word';
4+
import { StringUtils } from '../main';
5+
6+
export default class CamelCase extends Case {
7+
protected _matcher = /^[a-z]+(?:[A-Z][a-z]+)*$/;
8+
protected _splitter = /([A-Z]+[a-z]*)/;
9+
10+
/*
11+
* `str` do not need blending operation (there's no spaces into it)
12+
* from `splittedByCase`, for each subsequence
13+
* (at this step, it should be atleast a subSequence of length >= 2: because of check if it's a considerable str)
14+
* if current subSequence index is index 0, then lower it
15+
* otherwise format the current subSequence as a word (c.f: with first char uppered and the rest lowered)
16+
*/
17+
public basicConversionReturnFn(
18+
splittedByCase: string[],
19+
str: string,
20+
): string {
21+
return splittedByCase
22+
.map((subSequence, index) =>
23+
index == 0
24+
? subSequence.toLowerCase()
25+
: StringUtilsWord.formatWord(subSequence),
26+
)
27+
.join('');
28+
}
29+
30+
/*
31+
* `str` was blent (see StringUtils.blendIrrelevantStringsInRelevantOnes) and stored in `blended`
32+
* if blended length < 2 (c.f: blend operation didn't produce more than 1 relevant string) then
33+
* the table to work with in previous statement will be the result of splitted removed from blank chars
34+
* version of `str` (which is never modified).
35+
* Otherwise use the result of blend operation as table to work with (blended)
36+
*/
37+
public blendedConversionReturnFn(
38+
splittedByCase: string[],
39+
blended: string[],
40+
str: string,
41+
): string {
42+
return (
43+
blended.length < 2
44+
? StringUtilsCase.splitByCase(StringUtils.removeBlankChars(str))
45+
: blended
46+
)
47+
.map((subSequence, index) =>
48+
index == 0
49+
? subSequence.toLowerCase()
50+
: StringUtilsWord.formatWord(subSequence),
51+
)
52+
.join('');
53+
}
54+
}

src/case/lower-case.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Case from './Case';
2+
3+
export default class LowerCase extends Case {
4+
protected _matcher = /^[a-z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?\s]*$/;
5+
protected _splitter = '';
6+
7+
public basicConversionReturnFn(
8+
splittedByCase: string[],
9+
str: string,
10+
): string {
11+
return str.toLowerCase();
12+
}
13+
14+
public blendedConversionReturnFn(
15+
splittedByCase: string[],
16+
blended: string[],
17+
str: string,
18+
): string {
19+
return str.toLowerCase();
20+
}
21+
}

0 commit comments

Comments
 (0)