9 Commits

Author SHA1 Message Date
Spencer Brower
667937459f feat: Added obfuscate. 2024-07-19 17:33:51 -04:00
Spencer Brower
fa87bbc895 style: Reformatted flake.nix. 2024-04-04 12:06:52 -04:00
Spencer Brower
83d2ed54be chore: Ignored yarn-error.log. 2023-11-21 12:49:04 -05:00
Spencer Brower
72691b6f65 chore(release): 0.1.5 2023-06-20 17:04:59 -04:00
Spencer Brower
1485791367 feat: Added words and sentences. 2023-06-20 17:04:49 -04:00
Spencer Brower
fd15af85cb chore(release): 0.1.4 2023-06-15 13:43:48 -04:00
Spencer Brower
f46b68c709 fix: re-built code. 2023-06-15 13:43:40 -04:00
Spencer Brower
559217ac60 chore(release): 0.1.3 2023-06-15 13:40:40 -04:00
Spencer Brower
ba9656059b fix: beforeFirst now returns the input when needle is not present. 2023-06-15 13:40:26 -04:00
24 changed files with 898 additions and 500 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
.direnv
node_modules
yarn-error.log

View File

@@ -2,6 +2,27 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [0.1.5](https://github.com/sbrow/strings/compare/v0.1.4...v0.1.5) (2023-06-20)
### Features
* Added words and sentences. ([1485791](https://github.com/sbrow/strings/commit/1485791367e68286b48a4fb3d34d42dcaac1e6f5))
### [0.1.4](https://github.com/sbrow/strings/compare/v0.1.3...v0.1.4) (2023-06-15)
### Bug Fixes
* re-built code. ([f46b68c](https://github.com/sbrow/strings/commit/f46b68c709b16fb67ea6df56271276f31b05a7fd))
### [0.1.3](https://github.com/sbrow/strings/compare/v0.1.2...v0.1.3) (2023-06-15)
### Bug Fixes
* beforeFirst now returns the input when needle is not present. ([ba96560](https://github.com/sbrow/strings/commit/ba9656059ba14887202463d02a6bca9edb3203a4))
### 0.1.2 (2023-05-30)

View File

@@ -81,7 +81,7 @@ ___
#### Defined in
[src/index.ts:34](https://github.com/sbrow/strings/blob/7b676b7/src/index.ts#L34)
[src/index.ts:38](https://github.com/sbrow/strings/blob/559217a/src/index.ts#L38)
___
@@ -299,7 +299,7 @@ ___
#### Defined in
[src/index.ts:35](https://github.com/sbrow/strings/blob/7b676b7/src/index.ts#L35)
[src/index.ts:39](https://github.com/sbrow/strings/blob/559217a/src/index.ts#L39)
___

View File

@@ -9,18 +9,22 @@
};
outputs = { bp, self, nixpkgs }:
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in {
devShells.x86_64-linux.default = pkgs.mkShell {
buildInputs = with pkgs; [
yarn
];
};
let
pkgs = nixpkgs.legacyPackages.x86_64-linux;
in
{
devShells.x86_64-linux.default = pkgs.mkShell {
buildInputs = with pkgs; [
nodejs
yarn
checks.x86_64-linux.default = bp.outputs.legacyPackages.x86_64-linux.buildYarnPackage {
nodePackages.typescript-language-server
];
};
checks.x86_64-linux.default = bp.outputs.legacyPackages.x86_64-linux.buildYarnPackage {
src = ./.;
yarnBuildMore = "yarn build; yarn test";
};
};
};
}

5
lib/index.d.ts vendored
View File

@@ -1,7 +1,12 @@
export * from "./sentences";
export * from "./shorten";
export * from "./trim";
export * from "./words";
export declare const startsWith: import("ts-toolbelt/out/Function/Curry").Curry<(needle: string, haystack: string) => boolean>;
export declare const endsWith: import("ts-toolbelt/out/Function/Curry").Curry<(needle: string, haystack: string) => boolean>;
export declare const afterFirst: import("ts-toolbelt/out/Function/Curry").Curry<(separator: string, str: string) => string>;
export declare const afterLast: import("ts-toolbelt/out/Function/Curry").Curry<(separator: string, str: string) => string>;
export declare const beforeFirst: import("ts-toolbelt/out/Function/Curry").Curry<(separator: string, str: string) => string>;
export declare const beforeFirstWord: import("ts-toolbelt/out/Function/Curry").Curry<(str: string) => string>;
export declare const afterFirstWord: (str: string) => string;
export declare const removeFirstWord: (str: string) => string;

View File

@@ -14,9 +14,12 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.beforeFirstWord = exports.beforeFirst = exports.afterLast = exports.afterFirst = exports.endsWith = exports.startsWith = void 0;
exports.removeFirstWord = exports.afterFirstWord = exports.beforeFirstWord = exports.beforeFirst = exports.afterLast = exports.afterFirst = exports.endsWith = exports.startsWith = void 0;
const utils_1 = require("./utils");
__exportStar(require("./sentences"), exports);
__exportStar(require("./shorten"), exports);
__exportStar(require("./trim"), exports);
__exportStar(require("./words"), exports);
function escapeRegExp(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
}
@@ -24,35 +27,14 @@ exports.startsWith = (0, utils_1.curry)((needle, haystack) => haystack.indexOf(n
exports.endsWith = (0, utils_1.curry)((needle, haystack) => new RegExp(`${escapeRegExp(needle)}$`).test(haystack));
exports.afterFirst = (0, utils_1.curry)((separator, str) => str.substring(str.indexOf(separator) + 1, str.length));
exports.afterLast = (0, utils_1.curry)((separator, str) => str.substring(str.lastIndexOf(separator) + 1, str.length));
exports.beforeFirst = (0, utils_1.curry)((separator, str) => str.substring(0, str.indexOf(separator)));
exports.beforeFirst = (0, utils_1.curry)((separator, str) => {
const index = str.indexOf(separator);
return index === -1
? str
: str.substring(0, index);
});
// @todo Test
exports.beforeFirstWord = (0, exports.beforeFirst)(" ");
// /**
// * @param {String} str
// * @return {String}
// */
// export function afterFirstWord(str) {
// return afterFirst(' ', str);
// }
// export const removeFirstWord = afterFirstWord;
// /**
// * @param {Number} maxChars
// * @param {String} str
// * @return {Boolean} False if str is longer than maxChars characters.
// */
// const shorterThan = curry((maxChars, str) => str.length <= maxChars);
// /**
// * @param {Number} maxChars The maximum length of the desired output string.
// * @param strategy a function that accepts a string and returns a shorter string.
// * @return {String} The input string, shortened to maxChars by strategy.
// */
// const shortenString = (maxChars, strategy) => until(shorterThan(maxChars), strategy);
// /**
// * @param {Number} maxChars
// * @param {String} str The string to remove words from.
// * @return {String} the shortened string.
// */
// export const removeWordsFromStartOfString = uncurryN(
// 2,
// (maxChars) => shortenString(maxChars, removeFirstWord),
// );
// @todo Test
exports.afterFirstWord = (0, exports.afterFirst)(" ");
exports.removeFirstWord = exports.afterFirstWord;

3
lib/sentences.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export declare function sentencesLazy(str: string, pattern?: RegExp): Generator<string>;
export declare function sentences(str: string, pattern?: RegExp): string[];
export declare function countSentences(str: string, pattern?: RegExp): number;

26
lib/sentences.js Normal file
View File

@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.countSentences = exports.sentences = exports.sentencesLazy = void 0;
const matchSentenceEnd = /[!?]|(?<!\be|\be\.g|\betc)\./;
function* sentencesLazy(str, pattern = matchSentenceEnd) {
if (str.trim() !== '') {
const match = str.match(pattern);
const index = (match === null || match === void 0 ? void 0 : match.index) ? match.index + 1 : undefined;
if (index) {
yield str.substring(0, index);
yield* sentencesLazy(str.substring(index));
}
else {
yield str;
}
}
}
exports.sentencesLazy = sentencesLazy;
function sentences(str, pattern = matchSentenceEnd) {
return Array.from(sentencesLazy(str, pattern));
}
exports.sentences = sentences;
function countSentences(str, pattern = matchSentenceEnd) {
return sentences(str, pattern).length;
}
exports.countSentences = countSentences;

3
lib/shorten.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export declare const shorterThan: import("ts-toolbelt/out/Function/Curry").Curry<(maxChars: number, str: string) => boolean>;
export declare const shorten: import("ts-toolbelt/out/Function/Curry").Curry<(maxChars: number, strategy: any) => (init: unknown) => unknown>;
export declare const removeWordsFromBeginning: import("ts-toolbelt/out/Function/Curry").Curry<(maxChars: number, str: string) => unknown>;

14
lib/shorten.js Normal file
View File

@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.removeWordsFromBeginning = exports.shorten = exports.shorterThan = void 0;
const index_1 = require("./index");
const utils_1 = require("./utils");
exports.shorterThan = (0, utils_1.curry)((maxChars, str) => str.length <= maxChars);
exports.shorten = (0, utils_1.curry)((maxChars, strategy) => (0, utils_1.until)((0, exports.shorterThan)(maxChars), strategy));
exports.removeWordsFromBeginning = (0, utils_1.curry)((maxChars, str) => (0, exports.shorten)(maxChars, index_1.removeFirstWord)(str));
/*
export const removeWordsFromBeginning = uncurryN(
2,
(maxChars: number) => shorten(maxChars, removeFirstWord),
);
*/

3
lib/utils.d.ts vendored
View File

@@ -1,5 +1,6 @@
import type { curry as _curry, when as _when } from "ramda";
import type { curry as _curry, until as _until, when as _when } from "ramda";
export declare const curry: typeof _curry;
export declare const isEmpty: (str: string) => boolean;
export declare const tail: (str: string) => string;
export declare const when: typeof _when;
export declare const until: typeof _until;

View File

@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.when = exports.tail = exports.isEmpty = exports.curry = void 0;
exports.until = exports.when = exports.tail = exports.isEmpty = exports.curry = void 0;
exports.curry = function (fn) {
return (...args) => {
if (args.length >= fn.length) {
@@ -17,3 +17,7 @@ exports.tail = tail;
exports.when = (0, exports.curry)((predicate, whenTrueFn, arg) => {
return predicate(arg) ? whenTrueFn(arg) : arg;
});
exports.until = (0, exports.curry)((predicate, fn, arg) => {
const loop = (a) => predicate(a) ? a : loop(fn(a));
return loop(arg);
});

3
lib/words.d.ts vendored Normal file
View File

@@ -0,0 +1,3 @@
export declare function wordsLazy(str: string, pattern?: RegExp): Generator<string, void, unknown>;
export declare function words(str: string, pattern?: RegExp): string[];
export declare function wordCount(str: string, pattern?: RegExp): number;

21
lib/words.js Normal file
View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.wordCount = exports.words = exports.wordsLazy = void 0;
const matchWord = /\S+/g;
function* wordsLazy(str, pattern = matchWord) {
for (const match of str.matchAll(pattern)) {
yield match[0];
}
}
exports.wordsLazy = wordsLazy;
function words(str, pattern = matchWord) {
return Array.from(wordsLazy(str, pattern));
}
exports.words = words;
function wordCount(str, pattern = matchWord) {
let count = 0;
for (const {} of str.matchAll(pattern))
count++;
return count;
}
exports.wordCount = wordCount;

View File

@@ -1,6 +1,6 @@
{
"name": "@sbrow/strings",
"version": "0.1.2",
"version": "0.1.5",
"description": "Library for string manipulation",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
@@ -22,6 +22,6 @@
"typedoc": "^0.24.7",
"typedoc-plugin-markdown": "^3.15.3",
"typescript": "^5.0.4",
"vitest": "^0.31.1"
"vitest": "^2.0.3"
}
}

View File

@@ -71,8 +71,8 @@ describe("strings", () => {
expect(beforeFirst(" ", "foo bar bat")).toBe("foo");
});
it("removes everythin when needle is not present", () => {
expect(beforeFirst("&", "foo bar bat")).toBe("");
it("returns the input when needle is not present", () => {
expect(beforeFirst("&", "foo bar bat")).toBe("foo bar bat");
});
});
// describe('afterFirstWord', () => {

View File

@@ -1,7 +1,9 @@
import { curry } from "./utils";
export * from "./sentences";
export * from "./shorten";
export * from "./trim";
export * from "./words";
function escapeRegExp(str: string) {
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
@@ -23,9 +25,13 @@ export const afterLast = curry((separator: string, str: string) =>
str.substring(str.lastIndexOf(separator) + 1, str.length)
);
export const beforeFirst = curry((separator: string, str: string) =>
str.substring(0, str.indexOf(separator))
);
export const beforeFirst = curry((separator: string, str: string) => {
const index = str.indexOf(separator);
return index === -1
? str
: str.substring(0, index)
});
// @todo Test
export const beforeFirstWord = beforeFirst(" ");

16
src/obfuscate.spec.ts Normal file
View File

@@ -0,0 +1,16 @@
import { describe, expect, test } from "vitest";
import { obfuscate } from "./obfuscate";
describe('obfuscate', function() {
test.each([
['brower.spencer@gmail.com', [/^.{3}/, '@', '.com'], ['co'], 'bro•••••••••••@•••••.••m'],
['brower.spencer@gmail.com', [/^.{3}/, '@', '.com'], [], 'bro•••••••••••@•••••.com']
])('%# works', function (str, allow, deny, want) {
const got = obfuscate(str, allow, deny);
expect(want.length).toEqual(str.length)
expect(got).toBe(want);
});
});

56
src/obfuscate.ts Normal file
View File

@@ -0,0 +1,56 @@
export type Patterns = Array<string | RegExp>;
/**
* Replaces all but the first `length` characters of a string with `•`.
*
* @param {string} string The string to obfuscate.
* @param {string[]} whitelistPatterns
* @return {string} The obfuscated string.
*/
export function obfuscate(
string: string,
whitelistPatterns: Patterns = [],
blacklistPatterns: Patterns = []
): string {
return [
whitelist(whitelistPatterns),
blacklist(blacklistPatterns)
].reduce(
(str, fn) => fn(str),
string
);
}
function mergePatterns(patterns: Patterns): string {
return patterns
.map((pattern) =>
typeof pattern === "string" ? escapeRegExp(pattern) : pattern.source
)
.join("|");
}
function whitelist(patterns: Patterns) {
const whitelistPattern = RegExp(mergePatterns(patterns), "dgm");
return (string: string) =>
Array.from(string.matchAll(whitelistPattern))
.map((result) => [result[0], result.indices[0][0]])
.reduce(
(carry, [word, index]) => carry.padEnd(index, "\u2022") + word,
""
);
}
function blacklist(patterns: Patterns) {
const blacklistPattern = new RegExp(mergePatterns(patterns), "dgm");
//return string => Array.from(string.matchAll(blacklistPattern)).filter(x => x[0].length)
return (string: string) =>
string.replaceAll(blacklistPattern, (match: string) => {
return "".padEnd(match.length, "\u2022");
});
}
function escapeRegExp(string: string): string {
return string.replace(/[.*+?^${}()|[/\]\\]/g, "\\$&");
}

48
src/sentences.spec.ts Normal file
View File

@@ -0,0 +1,48 @@
import fc from "fast-check";
import { describe, expect, it } from "vitest";
import { countSentences, sentences } from "./sentences";
describe("sentences", () => {
describe("sentences", () => {
it("returns '[]' for empty strings", () => {
expect(sentences("")).toEqual([]);
});
it.each([
[" "],
[" "],
[" \r "],
["\n "],
])("returns '[]' for strings with only whitespace characters", (str) => {
expect(sentences(str)).toEqual([]);
});
it.each([
["Hello, World!"],
["Hello, World."],
["Hello, World?"],
])("wraps the input in an array if it has only one sentence", (str) => {
expect(sentences(str)).toEqual([str]);
});
it('passes', () => {
fc.assert(
fc.property(fc.string(), (str) => {
if (str === '' || str.trim() === '') {
expect(sentences(str).length).toEqual(0)
} else if (!str.match(/[!?.]/)){
expect(sentences(str).length).toEqual(1)
// expect(se??tences(str).length).toEqual(str.split(/\s+/).filter(complement(isEmpty)).length)
}
})
);
})
});
describe("countSentences", () => {
it("matches the length of the output from sentences", () => {
fc.assert(
fc.property(fc.string(), (str) => {
expect(countSentences(str)).toBe(sentences(str).length);
})
);
});
});
});

23
src/sentences.ts Normal file
View File

@@ -0,0 +1,23 @@
const matchSentenceEnd = /[!?]|(?<!\be|\be\.g|\betc)\./
export function* sentencesLazy(str: string, pattern = matchSentenceEnd): Generator<string> {
if (str.trim() !== '') {
const match = str.match(pattern)
const index = match?.index ? match.index + 1 : undefined
if (index) {
yield str.substring(0, index)
yield* sentencesLazy(str.substring(index))
} else {
yield str
}
}
}
export function sentences(str: string, pattern = matchSentenceEnd) {
return Array.from(sentencesLazy(str, pattern))
}
export function countSentences(str: string, pattern = matchSentenceEnd) {
return sentences(str, pattern).length
}

39
src/words.spec.ts Normal file
View File

@@ -0,0 +1,39 @@
import fc from "fast-check";
import { describe, expect, it } from "vitest";
import { wordCount, words } from "./words";
import { complement, isEmpty } from "ramda";
describe("words", () => {
describe("words", () => {
it("returns '[]' for empty strings", () => {
expect(words("")).toEqual([]);
});
it("returns '[]' for strings with only whitespace characters", () => {
expect(words(" ")).toEqual([]);
expect(words(" ")).toEqual([]);
expect(words(" \r ")).toEqual([]);
expect(words("\n ")).toEqual([]);
});
it('passes', () => {
fc.assert(
fc.property(fc.string(), (str) => {
if (str === '' || str.trim() === '') {
expect(words(str).length).toEqual(0)
} else {
expect(words(str).length).toEqual(str.split(/\s+/).filter(complement(isEmpty)).length)
}
})
);
})
});
describe("wordCount", () => {
it("matches the length of the output from words", () => {
fc.assert(
fc.property(fc.string(), (str) => {
expect(wordCount(str)).toBe(words(str).length);
})
);
});
});
});

20
src/words.ts Normal file
View File

@@ -0,0 +1,20 @@
const matchWord = /\S+/g
export function* wordsLazy(str: string, pattern: RegExp = matchWord) {
for (const match of str.matchAll(pattern)) {
yield match[0]
}
}
export function words(str: string, pattern: RegExp = matchWord) {
return Array.from(wordsLazy(str, pattern))
}
export function wordCount(str: string, pattern: RegExp = matchWord) {
let count = 0
for (const {} of str.matchAll(pattern))
count++
return count;
}

994
yarn.lock

File diff suppressed because it is too large Load Diff