@naturalcycles/js-lib 14.107.1 → 14.109.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -49,6 +49,9 @@ import { pTimeout, pTimeoutFn, PTimeoutOptions } from './promise/pTimeout';
49
49
  export * from './string/case';
50
50
  export * from './string/json.util';
51
51
  export * from './string/string.util';
52
+ export * from './string/readingTime';
53
+ export * from './string/escape';
54
+ export * from './string/pupa';
52
55
  import { JsonStringifyFunction, StringifyAnyOptions, _stringifyAny } from './string/stringifyAny';
53
56
  export * from './time/time.util';
54
57
  export * from './is.util';
package/dist/index.js CHANGED
@@ -63,6 +63,9 @@ Object.defineProperty(exports, "pTimeoutFn", { enumerable: true, get: function (
63
63
  tslib_1.__exportStar(require("./string/case"), exports);
64
64
  tslib_1.__exportStar(require("./string/json.util"), exports);
65
65
  tslib_1.__exportStar(require("./string/string.util"), exports);
66
+ tslib_1.__exportStar(require("./string/readingTime"), exports);
67
+ tslib_1.__exportStar(require("./string/escape"), exports);
68
+ tslib_1.__exportStar(require("./string/pupa"), exports);
66
69
  const stringifyAny_1 = require("./string/stringifyAny");
67
70
  Object.defineProperty(exports, "_stringifyAny", { enumerable: true, get: function () { return stringifyAny_1._stringifyAny; } });
68
71
  tslib_1.__exportStar(require("./time/time.util"), exports);
@@ -0,0 +1,2 @@
1
+ export declare function htmlEscape(strings: string | TemplateStringsArray, ...values: any[]): string;
2
+ export declare function htmlUnescape(strings: string | TemplateStringsArray, ...values: any[]): string;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ /*
3
+
4
+ Vendored:
5
+ https://github.com/sindresorhus/escape-goat
6
+
7
+ (c) Sindre Sorhus
8
+
9
+ Reasons:
10
+ 1. Stable enough to be included in "core" js-lib
11
+ 2. ESM-only
12
+
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.htmlUnescape = exports.htmlEscape = void 0;
16
+ // Multiple `.replace()` calls are actually faster than using replacer functions
17
+ function _htmlEscape(s) {
18
+ return s
19
+ .replace(/&/g, '&') // Must happen first or else it will escape other just-escaped characters.
20
+ .replace(/"/g, '"')
21
+ .replace(/'/g, ''')
22
+ .replace(/</g, '&lt;')
23
+ .replace(/>/g, '&gt;');
24
+ }
25
+ function _htmlUnescape(html) {
26
+ return html
27
+ .replace(/&gt;/g, '>')
28
+ .replace(/&lt;/g, '<')
29
+ .replace(/&#0?39;/g, "'")
30
+ .replace(/&quot;/g, '"')
31
+ .replace(/&amp;/g, '&'); // Must happen last or else it will unescape other characters in the wrong order.
32
+ }
33
+ function htmlEscape(strings, ...values) {
34
+ if (typeof strings === 'string') {
35
+ return _htmlEscape(strings);
36
+ }
37
+ let output = strings[0];
38
+ for (const [index, value] of values.entries()) {
39
+ output = output + _htmlEscape(String(value)) + strings[index + 1];
40
+ }
41
+ return output;
42
+ }
43
+ exports.htmlEscape = htmlEscape;
44
+ function htmlUnescape(strings, ...values) {
45
+ if (typeof strings === 'string') {
46
+ return _htmlUnescape(strings);
47
+ }
48
+ let output = strings[0];
49
+ for (const [index, value] of values.entries()) {
50
+ output = output + _htmlUnescape(String(value)) + strings[index + 1];
51
+ }
52
+ return output;
53
+ }
54
+ exports.htmlUnescape = htmlUnescape;
@@ -0,0 +1,22 @@
1
+ import type { AnyObject } from '../types';
2
+ export declare class MissingValueError extends Error {
3
+ key: any;
4
+ constructor(key: any);
5
+ }
6
+ export interface PupaOptions {
7
+ /**
8
+ * By default, Pupa throws a `MissingValueError` when a placeholder resolves to `undefined`. With this option set to `true`, it simply ignores it and leaves the placeholder as is.
9
+ */
10
+ ignoreMissing?: boolean;
11
+ /**
12
+ * Performs arbitrary operation for each interpolation. If the returned value was `undefined`, it behaves differently depending on the `ignoreMissing` option. Otherwise, the returned value will be interpolated into a string (and escaped when double-braced) and embedded into the template.
13
+ */
14
+ transform?: (data: {
15
+ value: any;
16
+ key: string;
17
+ }) => unknown;
18
+ }
19
+ /**
20
+ * API: https://github.com/sindresorhus/pupa
21
+ */
22
+ export declare function pupa(template: string, data: any[] | AnyObject, opt?: PupaOptions): string;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ /*
3
+
4
+ Vendored:
5
+ https://github.com/sindresorhus/pupa
6
+
7
+ (c) Sindre Sorhus
8
+
9
+ Reasons:
10
+ 1. Stable enough to be included in "core" js-lib
11
+ 2. ESM-only
12
+
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.pupa = exports.MissingValueError = void 0;
16
+ const escape_1 = require("./escape");
17
+ class MissingValueError extends Error {
18
+ constructor(key) {
19
+ super(`Missing a value for ${key ? `the placeholder: ${key}` : 'a placeholder'}`);
20
+ this.key = key;
21
+ this.name = 'MissingValueError';
22
+ this.key = key;
23
+ }
24
+ }
25
+ exports.MissingValueError = MissingValueError;
26
+ /**
27
+ * API: https://github.com/sindresorhus/pupa
28
+ */
29
+ function pupa(template, data, opt = {}) {
30
+ if (typeof template !== 'string') {
31
+ throw new TypeError(`Expected a \`string\` in the first argument, got \`${typeof template}\``);
32
+ }
33
+ if (typeof data !== 'object') {
34
+ throw new TypeError(`Expected an \`object\` or \`Array\` in the second argument, got \`${typeof data}\``);
35
+ }
36
+ const { ignoreMissing = false, transform = ({ value }) => value } = opt;
37
+ const replace = (placeholder, key) => {
38
+ let value = data;
39
+ for (const property of key.split('.')) {
40
+ value = value ? value[property] : undefined;
41
+ }
42
+ const transformedValue = transform({ value, key });
43
+ if (transformedValue === undefined) {
44
+ if (ignoreMissing) {
45
+ return placeholder;
46
+ }
47
+ throw new MissingValueError(key);
48
+ }
49
+ return String(transformedValue);
50
+ };
51
+ const composeHtmlEscape = (replacer) => (...args) => (0, escape_1.htmlEscape)(replacer(...args));
52
+ // The regex tries to match either a number inside `{{ }}` or a valid JS identifier or key path.
53
+ const doubleBraceRegex = /{{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}}/gi;
54
+ if (doubleBraceRegex.test(template)) {
55
+ template = template.replace(doubleBraceRegex, composeHtmlEscape(replace));
56
+ }
57
+ const braceRegex = /{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}/gi;
58
+ return template.replace(braceRegex, replace);
59
+ }
60
+ exports.pupa = pupa;
@@ -0,0 +1,29 @@
1
+ import type { Integer } from '../types';
2
+ export interface ReadingTimeOptions {
3
+ /**
4
+ * A function that returns a boolean value depending on if a character is considered as a word bound.
5
+ * Default: spaces, new lines and tabs
6
+ */
7
+ wordBound?: (char: string) => boolean;
8
+ /**
9
+ * Default 200
10
+ */
11
+ wordsPerMinute?: number;
12
+ }
13
+ export interface ReadingTimeStats {
14
+ /**
15
+ * Number of milliseconds.
16
+ */
17
+ time: Integer;
18
+ minutes: Integer;
19
+ }
20
+ export interface WordCountStats {
21
+ total: number;
22
+ }
23
+ export interface ReadingTimeResult extends ReadingTimeStats {
24
+ words: WordCountStats;
25
+ }
26
+ /**
27
+ * API: https://github.com/ngryman/reading-time
28
+ */
29
+ export declare function readingTime(text: string, options?: ReadingTimeOptions): ReadingTimeResult;
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ /*
3
+
4
+ Vendored from https://github.com/ngryman/reading-time
5
+ (c) Nicolas Gryman <ngryman@gmail.com>
6
+
7
+ Reasons:
8
+ 1. latest (1.5.0) version had a typescript import bug.
9
+ 2. Streaming mode is not needed (but brings confusion when used in the Browser).
10
+
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.readingTime = void 0;
14
+ function codeIsInRanges(num, arrayOfRanges) {
15
+ return arrayOfRanges.some(([lowerBound, upperBound]) => lowerBound <= num && num <= upperBound);
16
+ }
17
+ const isCJK = c => {
18
+ const charCode = c.codePointAt(0);
19
+ // Help wanted!
20
+ // This should be good for most cases, but if you find it unsatisfactory
21
+ // (e.g. some other language where each character should be standalone words),
22
+ // contributions welcome!
23
+ return codeIsInRanges(charCode, [
24
+ // Hiragana (Katakana not included on purpose,
25
+ // context: https://github.com/ngryman/reading-time/pull/35#issuecomment-853364526)
26
+ // If you think Katakana should be included and have solid reasons, improvement is welcomed
27
+ [0x3040, 0x309f],
28
+ // CJK Unified ideographs
29
+ [0x4e00, 0x9fff],
30
+ // Hangul
31
+ [0xac00, 0xd7a3],
32
+ // CJK extensions
33
+ [0x20000, 0x2ebe0],
34
+ ]);
35
+ };
36
+ const isAnsiWordBound = c => {
37
+ return ' \n\r\t'.includes(c);
38
+ };
39
+ const isPunctuation = c => {
40
+ const charCode = c.codePointAt(0);
41
+ return codeIsInRanges(charCode, [
42
+ [0x21, 0x2f],
43
+ [0x3a, 0x40],
44
+ [0x5b, 0x60],
45
+ [0x7b, 0x7e],
46
+ // CJK Symbols and Punctuation
47
+ [0x3000, 0x303f],
48
+ // Full-width ASCII punctuation variants
49
+ [0xff00, 0xffef],
50
+ ]);
51
+ };
52
+ function countWords(text, options = {}) {
53
+ let words = 0;
54
+ let start = 0;
55
+ let end = text.length - 1;
56
+ const isWordBound = options.wordBound || isAnsiWordBound;
57
+ // fetch bounds
58
+ while (isWordBound(text[start]))
59
+ start++;
60
+ while (isWordBound(text[end]))
61
+ end--;
62
+ // Add a trailing word bound to make handling edges more convenient
63
+ const normalizedText = `${text}\n`;
64
+ // calculate the number of words
65
+ for (let i = start; i <= end; i++) {
66
+ // A CJK character is a always word;
67
+ // A non-word bound followed by a word bound / CJK is the end of a word.
68
+ if (isCJK(normalizedText[i]) ||
69
+ (!isWordBound(normalizedText[i]) &&
70
+ (isWordBound(normalizedText[i + 1]) || isCJK(normalizedText[i + 1])))) {
71
+ words++;
72
+ }
73
+ // In case of CJK followed by punctuations, those characters have to be eaten as well
74
+ if (isCJK(normalizedText[i])) {
75
+ while (i <= end &&
76
+ (isPunctuation(normalizedText[i + 1]) || isWordBound(normalizedText[i + 1]))) {
77
+ i++;
78
+ }
79
+ }
80
+ }
81
+ return { total: words };
82
+ }
83
+ function readingTimeWithCount(words, options = {}) {
84
+ const { wordsPerMinute = 200 } = options;
85
+ // reading time stats
86
+ const minutes = words.total / wordsPerMinute;
87
+ // Math.round used to resolve floating point funkiness
88
+ // http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
89
+ const time = Math.round(minutes * 60 * 1000);
90
+ const displayed = Math.ceil(parseFloat(minutes.toFixed(2)));
91
+ return {
92
+ minutes: displayed,
93
+ time,
94
+ };
95
+ }
96
+ /**
97
+ * API: https://github.com/ngryman/reading-time
98
+ */
99
+ function readingTime(text, options = {}) {
100
+ const words = countWords(text, options);
101
+ return {
102
+ ...readingTimeWithCount(words, options),
103
+ words,
104
+ };
105
+ }
106
+ exports.readingTime = readingTime;
package/dist/types.d.ts CHANGED
@@ -179,19 +179,17 @@ export declare type Reviver = (this: any, key: string, value: any) => any;
179
179
  * Needed due to https://github.com/microsoft/TypeScript/issues/13778
180
180
  * Only affects typings, no runtime effect.
181
181
  */
182
- export declare function _stringMapValues<T>(m: StringMap<T>): T[];
182
+ export declare const _stringMapValues: <T>(m: StringMap<T>) => T[];
183
183
  /**
184
184
  * Needed due to https://github.com/microsoft/TypeScript/issues/13778
185
185
  * Only affects typings, no runtime effect.
186
186
  */
187
- export declare function _stringMapEntries<T>(m: StringMap<T>): [k: string, v: T][];
187
+ export declare const _stringMapEntries: <T>(m: StringMap<T>) => [k: string, v: T][];
188
188
  /**
189
189
  * Like `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
190
190
  * This is how TypeScript should work, actually.
191
- *
192
- * @experimental
193
191
  */
194
- export declare function _objectKeys<T extends AnyObject>(obj: T): (keyof T)[];
192
+ export declare const _objectKeys: <T extends AnyObject>(obj: T) => (keyof T)[];
195
193
  export declare type NullishValue = null | undefined;
196
194
  export declare type FalsyValue = false | '' | 0 | null | undefined;
197
195
  /**
package/dist/types.js CHANGED
@@ -26,28 +26,17 @@ exports._passNothingPredicate = _passNothingPredicate;
26
26
  * Needed due to https://github.com/microsoft/TypeScript/issues/13778
27
27
  * Only affects typings, no runtime effect.
28
28
  */
29
- function _stringMapValues(m) {
30
- return Object.values(m);
31
- }
32
- exports._stringMapValues = _stringMapValues;
29
+ exports._stringMapValues = Object.values;
33
30
  /**
34
31
  * Needed due to https://github.com/microsoft/TypeScript/issues/13778
35
32
  * Only affects typings, no runtime effect.
36
33
  */
37
- function _stringMapEntries(m) {
38
- return Object.entries(m);
39
- }
40
- exports._stringMapEntries = _stringMapEntries;
34
+ exports._stringMapEntries = Object.entries;
41
35
  /**
42
36
  * Like `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
43
37
  * This is how TypeScript should work, actually.
44
- *
45
- * @experimental
46
38
  */
47
- function _objectKeys(obj) {
48
- return Object.keys(obj);
49
- }
50
- exports._objectKeys = _objectKeys;
39
+ exports._objectKeys = Object.keys;
51
40
  /**
52
41
  * Utility function that helps to cast *existing variable* to needed type T.
53
42
  *
package/dist-esm/index.js CHANGED
@@ -46,6 +46,9 @@ import { pTimeout, pTimeoutFn } from './promise/pTimeout';
46
46
  export * from './string/case';
47
47
  export * from './string/json.util';
48
48
  export * from './string/string.util';
49
+ export * from './string/readingTime';
50
+ export * from './string/escape';
51
+ export * from './string/pupa';
49
52
  import { _stringifyAny } from './string/stringifyAny';
50
53
  export * from './time/time.util';
51
54
  export * from './is.util';
@@ -0,0 +1,49 @@
1
+ /*
2
+
3
+ Vendored:
4
+ https://github.com/sindresorhus/escape-goat
5
+
6
+ (c) Sindre Sorhus
7
+
8
+ Reasons:
9
+ 1. Stable enough to be included in "core" js-lib
10
+ 2. ESM-only
11
+
12
+ */
13
+ // Multiple `.replace()` calls are actually faster than using replacer functions
14
+ function _htmlEscape(s) {
15
+ return s
16
+ .replace(/&/g, '&amp;') // Must happen first or else it will escape other just-escaped characters.
17
+ .replace(/"/g, '&quot;')
18
+ .replace(/'/g, '&#39;')
19
+ .replace(/</g, '&lt;')
20
+ .replace(/>/g, '&gt;');
21
+ }
22
+ function _htmlUnescape(html) {
23
+ return html
24
+ .replace(/&gt;/g, '>')
25
+ .replace(/&lt;/g, '<')
26
+ .replace(/&#0?39;/g, "'")
27
+ .replace(/&quot;/g, '"')
28
+ .replace(/&amp;/g, '&'); // Must happen last or else it will unescape other characters in the wrong order.
29
+ }
30
+ export function htmlEscape(strings, ...values) {
31
+ if (typeof strings === 'string') {
32
+ return _htmlEscape(strings);
33
+ }
34
+ let output = strings[0];
35
+ for (const [index, value] of values.entries()) {
36
+ output = output + _htmlEscape(String(value)) + strings[index + 1];
37
+ }
38
+ return output;
39
+ }
40
+ export function htmlUnescape(strings, ...values) {
41
+ if (typeof strings === 'string') {
42
+ return _htmlUnescape(strings);
43
+ }
44
+ let output = strings[0];
45
+ for (const [index, value] of values.entries()) {
46
+ output = output + _htmlUnescape(String(value)) + strings[index + 1];
47
+ }
48
+ return output;
49
+ }
@@ -0,0 +1,55 @@
1
+ /*
2
+
3
+ Vendored:
4
+ https://github.com/sindresorhus/pupa
5
+
6
+ (c) Sindre Sorhus
7
+
8
+ Reasons:
9
+ 1. Stable enough to be included in "core" js-lib
10
+ 2. ESM-only
11
+
12
+ */
13
+ import { htmlEscape } from './escape';
14
+ export class MissingValueError extends Error {
15
+ constructor(key) {
16
+ super(`Missing a value for ${key ? `the placeholder: ${key}` : 'a placeholder'}`);
17
+ this.key = key;
18
+ this.name = 'MissingValueError';
19
+ this.key = key;
20
+ }
21
+ }
22
+ /**
23
+ * API: https://github.com/sindresorhus/pupa
24
+ */
25
+ export function pupa(template, data, opt = {}) {
26
+ if (typeof template !== 'string') {
27
+ throw new TypeError(`Expected a \`string\` in the first argument, got \`${typeof template}\``);
28
+ }
29
+ if (typeof data !== 'object') {
30
+ throw new TypeError(`Expected an \`object\` or \`Array\` in the second argument, got \`${typeof data}\``);
31
+ }
32
+ const { ignoreMissing = false, transform = ({ value }) => value } = opt;
33
+ const replace = (placeholder, key) => {
34
+ let value = data;
35
+ for (const property of key.split('.')) {
36
+ value = value ? value[property] : undefined;
37
+ }
38
+ const transformedValue = transform({ value, key });
39
+ if (transformedValue === undefined) {
40
+ if (ignoreMissing) {
41
+ return placeholder;
42
+ }
43
+ throw new MissingValueError(key);
44
+ }
45
+ return String(transformedValue);
46
+ };
47
+ const composeHtmlEscape = (replacer) => (...args) => htmlEscape(replacer(...args));
48
+ // The regex tries to match either a number inside `{{ }}` or a valid JS identifier or key path.
49
+ const doubleBraceRegex = /{{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}}/gi;
50
+ if (doubleBraceRegex.test(template)) {
51
+ template = template.replace(doubleBraceRegex, composeHtmlEscape(replace));
52
+ }
53
+ const braceRegex = /{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}/gi;
54
+ return template.replace(braceRegex, replace);
55
+ }
@@ -0,0 +1,99 @@
1
+ /*
2
+
3
+ Vendored from https://github.com/ngryman/reading-time
4
+ (c) Nicolas Gryman <ngryman@gmail.com>
5
+
6
+ Reasons:
7
+ 1. latest (1.5.0) version had a typescript import bug.
8
+ 2. Streaming mode is not needed (but brings confusion when used in the Browser).
9
+
10
+ */
11
+ function codeIsInRanges(num, arrayOfRanges) {
12
+ return arrayOfRanges.some(([lowerBound, upperBound]) => lowerBound <= num && num <= upperBound);
13
+ }
14
+ const isCJK = c => {
15
+ const charCode = c.codePointAt(0);
16
+ // Help wanted!
17
+ // This should be good for most cases, but if you find it unsatisfactory
18
+ // (e.g. some other language where each character should be standalone words),
19
+ // contributions welcome!
20
+ return codeIsInRanges(charCode, [
21
+ // Hiragana (Katakana not included on purpose,
22
+ // context: https://github.com/ngryman/reading-time/pull/35#issuecomment-853364526)
23
+ // If you think Katakana should be included and have solid reasons, improvement is welcomed
24
+ [0x3040, 0x309f],
25
+ // CJK Unified ideographs
26
+ [0x4e00, 0x9fff],
27
+ // Hangul
28
+ [0xac00, 0xd7a3],
29
+ // CJK extensions
30
+ [0x20000, 0x2ebe0],
31
+ ]);
32
+ };
33
+ const isAnsiWordBound = c => {
34
+ return ' \n\r\t'.includes(c);
35
+ };
36
+ const isPunctuation = c => {
37
+ const charCode = c.codePointAt(0);
38
+ return codeIsInRanges(charCode, [
39
+ [0x21, 0x2f],
40
+ [0x3a, 0x40],
41
+ [0x5b, 0x60],
42
+ [0x7b, 0x7e],
43
+ // CJK Symbols and Punctuation
44
+ [0x3000, 0x303f],
45
+ // Full-width ASCII punctuation variants
46
+ [0xff00, 0xffef],
47
+ ]);
48
+ };
49
+ function countWords(text, options = {}) {
50
+ let words = 0;
51
+ let start = 0;
52
+ let end = text.length - 1;
53
+ const isWordBound = options.wordBound || isAnsiWordBound;
54
+ // fetch bounds
55
+ while (isWordBound(text[start]))
56
+ start++;
57
+ while (isWordBound(text[end]))
58
+ end--;
59
+ // Add a trailing word bound to make handling edges more convenient
60
+ const normalizedText = `${text}\n`;
61
+ // calculate the number of words
62
+ for (let i = start; i <= end; i++) {
63
+ // A CJK character is a always word;
64
+ // A non-word bound followed by a word bound / CJK is the end of a word.
65
+ if (isCJK(normalizedText[i]) ||
66
+ (!isWordBound(normalizedText[i]) &&
67
+ (isWordBound(normalizedText[i + 1]) || isCJK(normalizedText[i + 1])))) {
68
+ words++;
69
+ }
70
+ // In case of CJK followed by punctuations, those characters have to be eaten as well
71
+ if (isCJK(normalizedText[i])) {
72
+ while (i <= end &&
73
+ (isPunctuation(normalizedText[i + 1]) || isWordBound(normalizedText[i + 1]))) {
74
+ i++;
75
+ }
76
+ }
77
+ }
78
+ return { total: words };
79
+ }
80
+ function readingTimeWithCount(words, options = {}) {
81
+ const { wordsPerMinute = 200 } = options;
82
+ // reading time stats
83
+ const minutes = words.total / wordsPerMinute;
84
+ // Math.round used to resolve floating point funkiness
85
+ // http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
86
+ const time = Math.round(minutes * 60 * 1000);
87
+ const displayed = Math.ceil(parseFloat(minutes.toFixed(2)));
88
+ return {
89
+ minutes: displayed,
90
+ time,
91
+ };
92
+ }
93
+ /**
94
+ * API: https://github.com/ngryman/reading-time
95
+ */
96
+ export function readingTime(text, options = {}) {
97
+ const words = countWords(text, options);
98
+ return Object.assign(Object.assign({}, readingTimeWithCount(words, options)), { words });
99
+ }
package/dist-esm/types.js CHANGED
@@ -18,25 +18,17 @@ export const _passNothingPredicate = () => false;
18
18
  * Needed due to https://github.com/microsoft/TypeScript/issues/13778
19
19
  * Only affects typings, no runtime effect.
20
20
  */
21
- export function _stringMapValues(m) {
22
- return Object.values(m);
23
- }
21
+ export const _stringMapValues = Object.values;
24
22
  /**
25
23
  * Needed due to https://github.com/microsoft/TypeScript/issues/13778
26
24
  * Only affects typings, no runtime effect.
27
25
  */
28
- export function _stringMapEntries(m) {
29
- return Object.entries(m);
30
- }
26
+ export const _stringMapEntries = Object.entries;
31
27
  /**
32
28
  * Like `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
33
29
  * This is how TypeScript should work, actually.
34
- *
35
- * @experimental
36
30
  */
37
- export function _objectKeys(obj) {
38
- return Object.keys(obj);
39
- }
31
+ export const _objectKeys = Object.keys;
40
32
  /**
41
33
  * Utility function that helps to cast *existing variable* to needed type T.
42
34
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.107.1",
3
+ "version": "14.109.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -18,12 +18,10 @@
18
18
  "@types/node": "^18.0.0",
19
19
  "expect-type": "^0.13.0",
20
20
  "jest": "^28.0.3",
21
- "patch-package": "^6.2.1",
22
21
  "prettier": "^2.1.2",
23
22
  "rxjs": "^7.0.1",
24
23
  "vuepress": "^1.7.1",
25
- "vuepress-plugin-typescript": "^0.3.1",
26
- "weak-napi": "^2.0.2"
24
+ "vuepress-plugin-typescript": "^0.3.1"
27
25
  },
28
26
  "files": [
29
27
  "dist",
package/src/index.ts CHANGED
@@ -82,6 +82,9 @@ import { pTimeout, pTimeoutFn, PTimeoutOptions } from './promise/pTimeout'
82
82
  export * from './string/case'
83
83
  export * from './string/json.util'
84
84
  export * from './string/string.util'
85
+ export * from './string/readingTime'
86
+ export * from './string/escape'
87
+ export * from './string/pupa'
85
88
  import { JsonStringifyFunction, StringifyAnyOptions, _stringifyAny } from './string/stringifyAny'
86
89
  export * from './time/time.util'
87
90
  export * from './is.util'
@@ -0,0 +1,57 @@
1
+ /*
2
+
3
+ Vendored:
4
+ https://github.com/sindresorhus/escape-goat
5
+
6
+ (c) Sindre Sorhus
7
+
8
+ Reasons:
9
+ 1. Stable enough to be included in "core" js-lib
10
+ 2. ESM-only
11
+
12
+ */
13
+
14
+ // Multiple `.replace()` calls are actually faster than using replacer functions
15
+ function _htmlEscape(s: string): string {
16
+ return s
17
+ .replace(/&/g, '&amp;') // Must happen first or else it will escape other just-escaped characters.
18
+ .replace(/"/g, '&quot;')
19
+ .replace(/'/g, '&#39;')
20
+ .replace(/</g, '&lt;')
21
+ .replace(/>/g, '&gt;')
22
+ }
23
+
24
+ function _htmlUnescape(html: string): string {
25
+ return html
26
+ .replace(/&gt;/g, '>')
27
+ .replace(/&lt;/g, '<')
28
+ .replace(/&#0?39;/g, "'")
29
+ .replace(/&quot;/g, '"')
30
+ .replace(/&amp;/g, '&') // Must happen last or else it will unescape other characters in the wrong order.
31
+ }
32
+
33
+ export function htmlEscape(strings: string | TemplateStringsArray, ...values: any[]): string {
34
+ if (typeof strings === 'string') {
35
+ return _htmlEscape(strings)
36
+ }
37
+
38
+ let output = strings[0]!
39
+ for (const [index, value] of values.entries()) {
40
+ output = output + _htmlEscape(String(value)) + strings[index + 1]
41
+ }
42
+
43
+ return output
44
+ }
45
+
46
+ export function htmlUnescape(strings: string | TemplateStringsArray, ...values: any[]): string {
47
+ if (typeof strings === 'string') {
48
+ return _htmlUnescape(strings)
49
+ }
50
+
51
+ let output = strings[0]!
52
+ for (const [index, value] of values.entries()) {
53
+ output = output + _htmlUnescape(String(value)) + strings[index + 1]
54
+ }
55
+
56
+ return output
57
+ }
@@ -0,0 +1,86 @@
1
+ /*
2
+
3
+ Vendored:
4
+ https://github.com/sindresorhus/pupa
5
+
6
+ (c) Sindre Sorhus
7
+
8
+ Reasons:
9
+ 1. Stable enough to be included in "core" js-lib
10
+ 2. ESM-only
11
+
12
+ */
13
+
14
+ import type { AnyObject } from '../types'
15
+ import { htmlEscape } from './escape'
16
+
17
+ export class MissingValueError extends Error {
18
+ constructor(public key: any) {
19
+ super(`Missing a value for ${key ? `the placeholder: ${key}` : 'a placeholder'}`)
20
+ this.name = 'MissingValueError'
21
+ this.key = key
22
+ }
23
+ }
24
+
25
+ export interface PupaOptions {
26
+ /**
27
+ * By default, Pupa throws a `MissingValueError` when a placeholder resolves to `undefined`. With this option set to `true`, it simply ignores it and leaves the placeholder as is.
28
+ */
29
+ ignoreMissing?: boolean
30
+
31
+ /**
32
+ * Performs arbitrary operation for each interpolation. If the returned value was `undefined`, it behaves differently depending on the `ignoreMissing` option. Otherwise, the returned value will be interpolated into a string (and escaped when double-braced) and embedded into the template.
33
+ */
34
+ transform?: (data: { value: any; key: string }) => unknown
35
+ }
36
+
37
+ /**
38
+ * API: https://github.com/sindresorhus/pupa
39
+ */
40
+ export function pupa(template: string, data: any[] | AnyObject, opt: PupaOptions = {}): string {
41
+ if (typeof template !== 'string') {
42
+ throw new TypeError(`Expected a \`string\` in the first argument, got \`${typeof template}\``)
43
+ }
44
+
45
+ if (typeof data !== 'object') {
46
+ throw new TypeError(
47
+ `Expected an \`object\` or \`Array\` in the second argument, got \`${typeof data}\``,
48
+ )
49
+ }
50
+
51
+ const { ignoreMissing = false, transform = ({ value }) => value } = opt
52
+
53
+ const replace = (placeholder: string, key: string): string => {
54
+ let value = data
55
+ for (const property of key.split('.')) {
56
+ value = value ? (value as AnyObject)[property] : undefined
57
+ }
58
+
59
+ const transformedValue = transform({ value, key })
60
+ if (transformedValue === undefined) {
61
+ if (ignoreMissing) {
62
+ return placeholder
63
+ }
64
+
65
+ throw new MissingValueError(key)
66
+ }
67
+
68
+ return String(transformedValue)
69
+ }
70
+
71
+ const composeHtmlEscape =
72
+ (replacer: any) =>
73
+ (...args: any[]) =>
74
+ htmlEscape(replacer(...args))
75
+
76
+ // The regex tries to match either a number inside `{{ }}` or a valid JS identifier or key path.
77
+ const doubleBraceRegex = /{{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}}/gi
78
+
79
+ if (doubleBraceRegex.test(template)) {
80
+ template = template.replace(doubleBraceRegex, composeHtmlEscape(replace))
81
+ }
82
+
83
+ const braceRegex = /{(\d+|[a-z$_][\w\-$]*?(?:\.[\w\-$]*?)*?)}/gi
84
+
85
+ return template.replace(braceRegex, replace)
86
+ }
@@ -0,0 +1,150 @@
1
+ /*
2
+
3
+ Vendored from https://github.com/ngryman/reading-time
4
+ (c) Nicolas Gryman <ngryman@gmail.com>
5
+
6
+ Reasons:
7
+ 1. latest (1.5.0) version had a typescript import bug.
8
+ 2. Streaming mode is not needed (but brings confusion when used in the Browser).
9
+
10
+ */
11
+
12
+ import type { Integer } from '../types'
13
+
14
+ export interface ReadingTimeOptions {
15
+ /**
16
+ * A function that returns a boolean value depending on if a character is considered as a word bound.
17
+ * Default: spaces, new lines and tabs
18
+ */
19
+ wordBound?: (char: string) => boolean
20
+ /**
21
+ * Default 200
22
+ */
23
+ wordsPerMinute?: number
24
+ }
25
+
26
+ export interface ReadingTimeStats {
27
+ /**
28
+ * Number of milliseconds.
29
+ */
30
+ time: Integer
31
+ minutes: Integer
32
+ }
33
+
34
+ export interface WordCountStats {
35
+ total: number
36
+ }
37
+
38
+ export interface ReadingTimeResult extends ReadingTimeStats {
39
+ words: WordCountStats
40
+ }
41
+
42
+ type WordBoundFunction = (char: string) => boolean
43
+
44
+ function codeIsInRanges(num: number, arrayOfRanges: number[][]) {
45
+ return arrayOfRanges.some(([lowerBound, upperBound]) => lowerBound! <= num && num <= upperBound!)
46
+ }
47
+
48
+ const isCJK: WordBoundFunction = c => {
49
+ const charCode = c.codePointAt(0)!
50
+ // Help wanted!
51
+ // This should be good for most cases, but if you find it unsatisfactory
52
+ // (e.g. some other language where each character should be standalone words),
53
+ // contributions welcome!
54
+ return codeIsInRanges(charCode, [
55
+ // Hiragana (Katakana not included on purpose,
56
+ // context: https://github.com/ngryman/reading-time/pull/35#issuecomment-853364526)
57
+ // If you think Katakana should be included and have solid reasons, improvement is welcomed
58
+ [0x3040, 0x309f],
59
+ // CJK Unified ideographs
60
+ [0x4e00, 0x9fff],
61
+ // Hangul
62
+ [0xac00, 0xd7a3],
63
+ // CJK extensions
64
+ [0x20000, 0x2ebe0],
65
+ ])
66
+ }
67
+
68
+ const isAnsiWordBound: WordBoundFunction = c => {
69
+ return ' \n\r\t'.includes(c)
70
+ }
71
+
72
+ const isPunctuation: WordBoundFunction = c => {
73
+ const charCode = c.codePointAt(0)!
74
+ return codeIsInRanges(charCode, [
75
+ [0x21, 0x2f],
76
+ [0x3a, 0x40],
77
+ [0x5b, 0x60],
78
+ [0x7b, 0x7e],
79
+ // CJK Symbols and Punctuation
80
+ [0x3000, 0x303f],
81
+ // Full-width ASCII punctuation variants
82
+ [0xff00, 0xffef],
83
+ ])
84
+ }
85
+
86
+ function countWords(text: string, options: ReadingTimeOptions = {}): WordCountStats {
87
+ let words = 0
88
+ let start = 0
89
+ let end = text.length - 1
90
+ const isWordBound = options.wordBound || isAnsiWordBound
91
+
92
+ // fetch bounds
93
+ while (isWordBound(text[start]!)) start++
94
+ while (isWordBound(text[end]!)) end--
95
+
96
+ // Add a trailing word bound to make handling edges more convenient
97
+ const normalizedText = `${text}\n`
98
+
99
+ // calculate the number of words
100
+ for (let i = start; i <= end; i++) {
101
+ // A CJK character is a always word;
102
+ // A non-word bound followed by a word bound / CJK is the end of a word.
103
+ if (
104
+ isCJK(normalizedText[i]!) ||
105
+ (!isWordBound(normalizedText[i]!) &&
106
+ (isWordBound(normalizedText[i + 1]!) || isCJK(normalizedText[i + 1]!)))
107
+ ) {
108
+ words++
109
+ }
110
+ // In case of CJK followed by punctuations, those characters have to be eaten as well
111
+ if (isCJK(normalizedText[i]!)) {
112
+ while (
113
+ i <= end &&
114
+ (isPunctuation(normalizedText[i + 1]!) || isWordBound(normalizedText[i + 1]!))
115
+ ) {
116
+ i++
117
+ }
118
+ }
119
+ }
120
+ return { total: words }
121
+ }
122
+
123
+ function readingTimeWithCount(
124
+ words: WordCountStats,
125
+ options: ReadingTimeOptions = {},
126
+ ): ReadingTimeStats {
127
+ const { wordsPerMinute = 200 } = options
128
+ // reading time stats
129
+ const minutes = words.total / wordsPerMinute
130
+ // Math.round used to resolve floating point funkiness
131
+ // http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
132
+ const time = Math.round(minutes * 60 * 1000)
133
+ const displayed = Math.ceil(parseFloat(minutes.toFixed(2)))
134
+
135
+ return {
136
+ minutes: displayed,
137
+ time,
138
+ }
139
+ }
140
+
141
+ /**
142
+ * API: https://github.com/ngryman/reading-time
143
+ */
144
+ export function readingTime(text: string, options: ReadingTimeOptions = {}): ReadingTimeResult {
145
+ const words = countWords(text, options)
146
+ return {
147
+ ...readingTimeWithCount(words, options),
148
+ words,
149
+ }
150
+ }
package/src/types.ts CHANGED
@@ -243,27 +243,19 @@ export type Reviver = (this: any, key: string, value: any) => any
243
243
  * Needed due to https://github.com/microsoft/TypeScript/issues/13778
244
244
  * Only affects typings, no runtime effect.
245
245
  */
246
- export function _stringMapValues<T>(m: StringMap<T>): T[] {
247
- return Object.values(m) as T[]
248
- }
246
+ export const _stringMapValues = Object.values as <T>(m: StringMap<T>) => T[]
249
247
 
250
248
  /**
251
249
  * Needed due to https://github.com/microsoft/TypeScript/issues/13778
252
250
  * Only affects typings, no runtime effect.
253
251
  */
254
- export function _stringMapEntries<T>(m: StringMap<T>): [k: string, v: T][] {
255
- return Object.entries(m) as [string, T][]
256
- }
252
+ export const _stringMapEntries = Object.entries as <T>(m: StringMap<T>) => [k: string, v: T][]
257
253
 
258
254
  /**
259
255
  * Like `Object.keys`, but returns keys typed as `keyof T`, not as just `string`.
260
256
  * This is how TypeScript should work, actually.
261
- *
262
- * @experimental
263
257
  */
264
- export function _objectKeys<T extends AnyObject>(obj: T): (keyof T)[] {
265
- return Object.keys(obj)
266
- }
258
+ export const _objectKeys = Object.keys as <T extends AnyObject>(obj: T) => (keyof T)[]
267
259
 
268
260
  export type NullishValue = null | undefined
269
261
  export type FalsyValue = false | '' | 0 | null | undefined