Compare commits

..

20 Commits

Author SHA1 Message Date
201812e182 3.0.15 2019-11-24 11:43:32 +00:00
8dfa3f8965 fix(core): update 2019-11-24 11:43:32 +00:00
43d50b746b 3.0.14 2019-10-10 18:24:04 +02:00
a1ee8f37d6 fix(core): update 2019-10-10 18:24:03 +02:00
8d9c75e113 3.0.13 2019-10-10 18:14:08 +02:00
8ff2a9d64a fix(core): update 2019-10-10 18:14:07 +02:00
8665464416 3.0.12 2019-10-10 18:03:05 +02:00
e773644256 fix(core): update 2019-10-10 18:03:04 +02:00
71fd0e614c 3.0.11 2019-10-10 17:28:24 +02:00
55a5948a51 fix(core): update 2019-10-10 17:28:23 +02:00
979375f08d 3.0.10 2019-04-16 08:55:37 +02:00
95d5c6ceee fix(core): update 2019-04-16 08:55:37 +02:00
c92ece79ff 3.0.9 2019-04-16 08:54:27 +02:00
7c625c4390 fix(core): update 2019-04-16 08:54:27 +02:00
65fc64f6aa 3.0.8 2019-01-12 20:41:02 +01:00
8cda8c55c0 fix(core): update 2019-01-12 20:41:01 +01:00
ec0b82de00 3.0.7 2019-01-12 20:19:17 +01:00
5e69664f59 fix(core): update 2019-01-12 20:19:16 +01:00
f5838f6d6a 3.0.6 2019-01-12 20:12:59 +01:00
fb4a03ad37 fix(core): update 2019-01-12 20:12:58 +01:00
16 changed files with 1507 additions and 523 deletions

22
.gitignore vendored
View File

@ -1,4 +1,22 @@
.yarn/ .nogit/
pages/
# artifacts
coverage/
public/ public/
pages/
# installs
node_modules/ node_modules/
# caches
.yarn/
.cache/
.rpt2_cache
# builds
dist/
dist_web/
dist_serve/
dist_ts_web/
# custom

View File

@ -1,5 +1,5 @@
# gitzone standard # gitzone ci_default
image: hosttoday/ht-docker-node:npmci image: registry.gitlab.com/hosttoday/ht-docker-node:npmci
cache: cache:
paths: paths:
@ -38,19 +38,7 @@ snyk:
# test stage # test stage
# ==================== # ====================
testLTS: testStable:
stage: test
script:
- npmci npm prepare
- npmci node install lts
- npmci npm install
- npmci npm test
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- notpriv
testSTABLE:
stage: test stage: test
script: script:
- npmci npm prepare - npmci npm prepare
@ -60,6 +48,18 @@ testSTABLE:
coverage: /\d+.?\d+?\%\s*coverage/ coverage: /\d+.?\d+?\%\s*coverage/
tags: tags:
- docker - docker
- priv
testBuild:
stage: test
script:
- npmci npm prepare
- npmci node install stable
- npmci npm install
- npmci command npm run build
coverage: /\d+.?\d+?\%\s*coverage/
tags:
- docker
- notpriv - notpriv
release: release:
@ -78,19 +78,12 @@ release:
# ==================== # ====================
codequality: codequality:
stage: metadata stage: metadata
image: docker:stable
allow_failure: true allow_failure: true
services:
- docker:stable-dind
script: script:
- export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/') - npmci command npm install -g tslint typescript
- docker run - npmci npm prepare
--env SOURCE_CODE="$PWD" - npmci npm install
--volume "$PWD":/code - npmci command "tslint -c tslint.json ./ts/**/*.ts"
--volume /var/run/docker.sock:/var/run/docker.sock
"registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
artifacts:
paths: [codeclimate.json]
tags: tags:
- docker - docker
- priv - priv
@ -106,13 +99,15 @@ trigger:
- notpriv - notpriv
pages: pages:
image: hosttoday/ht-docker-node:npmci image: hosttoday/ht-docker-dbase:npmci
services:
- docker:stable-dind
stage: metadata stage: metadata
script: script:
- npmci command npm install -g typedoc typescript - npmci command npm install -g @gitzone/tsdoc
- npmci npm prepare - npmci npm prepare
- npmci npm install - npmci npm install
- npmci command typedoc --module "commonjs" --target "ES2016" --out public/ ts/ - npmci command tsdoc
tags: tags:
- docker - docker
- notpriv - notpriv

View File

@ -1,6 +1,7 @@
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2016 Push.Rocks Copyright (c) 2014 Maurice Butler
Copyright (c) 2016 Lossless GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@ -1,25 +1,20 @@
# smartstring # @pushrocks/smartstring
handle strings in smart ways. TypeScript ready. handle strings in smart ways. TypeScript ready.
## Availabililty ## Availabililty and Links
* [npmjs.org (npm package)](https://www.npmjs.com/package/@pushrocks/smartstring)
[![npm](https://pushrocks.gitlab.io/assets/repo-button-npm.svg)](https://www.npmjs.com/package/smartstring) * [gitlab.com (source)](https://gitlab.com/pushrocks/smartstring)
[![git](https://pushrocks.gitlab.io/assets/repo-button-git.svg)](https://GitLab.com/pushrocks/smartstring) * [github.com (source mirror)](https://github.com/pushrocks/smartstring)
[![git](https://pushrocks.gitlab.io/assets/repo-button-mirror.svg)](https://github.com/pushrocks/smartstring) * [docs (typedoc)](https://pushrocks.gitlab.io/smartstring/)
[![docs](https://pushrocks.gitlab.io/assets/repo-button-docs.svg)](https://pushrocks.gitlab.io/smartstring/)
## Status for master ## Status for master
[![build status](https://gitlab.com/pushrocks/smartstring/badges/master/build.svg)](https://gitlab.com/pushrocks/smartstring/commits/master)
[![build status](https://GitLab.com/pushrocks/smartstring/badges/master/build.svg)](https://GitLab.com/pushrocks/smartstring/commits/master) [![coverage report](https://gitlab.com/pushrocks/smartstring/badges/master/coverage.svg)](https://gitlab.com/pushrocks/smartstring/commits/master)
[![coverage report](https://GitLab.com/pushrocks/smartstring/badges/master/coverage.svg)](https://GitLab.com/pushrocks/smartstring/commits/master) [![npm downloads per month](https://img.shields.io/npm/dm/@pushrocks/smartstring.svg)](https://www.npmjs.com/package/@pushrocks/smartstring)
[![npm downloads per month](https://img.shields.io/npm/dm/smartstring.svg)](https://www.npmjs.com/package/smartstring) [![Known Vulnerabilities](https://snyk.io/test/npm/@pushrocks/smartstring/badge.svg)](https://snyk.io/test/npm/@pushrocks/smartstring)
[![Dependency Status](https://david-dm.org/pushrocks/smartstring.svg)](https://david-dm.org/pushrocks/smartstring) [![TypeScript](https://img.shields.io/badge/TypeScript->=%203.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![bitHound Dependencies](https://www.bithound.io/github/pushrocks/smartstring/badges/dependencies.svg)](https://www.bithound.io/github/pushrocks/smartstring/master/dependencies/npm) [![node](https://img.shields.io/badge/node->=%2010.x.x-blue.svg)](https://nodejs.org/dist/latest-v10.x/docs/api/)
[![bitHound Code](https://www.bithound.io/github/pushrocks/smartstring/badges/code.svg)](https://www.bithound.io/github/pushrocks/smartstring) [![JavaScript Style Guide](https://img.shields.io/badge/code%20style-prettier-ff69b4.svg)](https://prettier.io/)
[![TypeScript](https://img.shields.io/badge/TypeScript-2.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![node](https://img.shields.io/badge/node->=%206.x.x-blue.svg)](https://nodejs.org/dist/latest-v6.x/docs/api/)
[![JavaScript Style Guide](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)
## Usage ## Usage
@ -63,8 +58,9 @@ smartstring.indent.indent('somestringanotherstring', '>>>> '); // indents a stri
smartstring.indent.normalize(' somestring anotherstring', '>>>> '); // looks for the least amount of indention and removes superflouous space smartstring.indent.normalize(' somestring anotherstring', '>>>> '); // looks for the least amount of indention and removes superflouous space
``` ```
For further information read the linked docs at the top of this README. For further information read the linked docs at the top of this readme.
> MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh) > MIT licensed | **©** [Lossless GmbH](https://lossless.gmbh)
| By using this npm module you agree to our [privacy policy](https://lossless.gmbH/privacy)
[![repo-footer](https://pushrocks.gitlab.io/assets/repo-footer.svg)](https://push.rocks) [![repo-footer](https://lossless.gitlab.io/publicrelations/repofooter.svg)](https://maintainedby.lossless.com)

View File

@ -7,5 +7,15 @@
"baseImage": "hosttoday/ht-docker-node:npmci", "baseImage": "hosttoday/ht-docker-node:npmci",
"command": "npmci node install stable && npmci npm install && npmci npm test", "command": "npmci node install stable && npmci npm install && npmci npm test",
"dockerSock": false "dockerSock": false
},
"gitzone": {
"module": {
"githost": "gitlab.com",
"gitscope": "pushrocks",
"gitrepo": "smartstring",
"shortDescription": "handle strings in smart ways. TypeScript ready.",
"npmPackagename": "@pushrocks/smartstring",
"license": "MIT"
}
} }
} }

1582
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "@pushrocks/smartstring", "name": "@pushrocks/smartstring",
"version": "3.0.5", "version": "3.0.15",
"private": false, "private": false,
"description": "handle strings in smart ways. TypeScript ready.", "description": "handle strings in smart ways. TypeScript ready.",
"main": "dist/index.js", "main": "dist/index.js",
@ -24,17 +24,30 @@
}, },
"homepage": "https://gitlab.com/pushrocks/smartstring#readme", "homepage": "https://gitlab.com/pushrocks/smartstring#readme",
"devDependencies": { "devDependencies": {
"@gitzone/tsbuild": "^2.0.22", "@gitzone/tsbuild": "^2.1.17",
"@gitzone/tsrun": "^1.1.13", "@gitzone/tsrun": "^1.2.8",
"@gitzone/tstest": "^1.0.15", "@gitzone/tstest": "^1.0.28",
"@pushrocks/tapbundle": "^3.0.7", "@pushrocks/tapbundle": "^3.0.13",
"@types/node": "^10.12.10" "@types/node": "^12.7.12",
"tslint": "^5.20.0",
"tslint-config-prettier": "^1.18.0"
}, },
"dependencies": { "dependencies": {
"crypto-random-string": "^1.0.0", "crypto-random-string": "^3.0.1",
"js-base64": "^2.4.9", "js-base64": "^2.5.1",
"normalize-newline": "^3.0.0", "normalize-newline": "^3.0.0",
"randomatic": "^3.1.1", "randomatic": "^3.1.1",
"strip-indent": "^2.0.0" "strip-indent": "^3.0.0"
} },
"files": [
"ts/**/*",
"ts_web/**/*",
"dist/**/*",
"dist_web/**/*",
"dist_ts_web/**/*",
"assets/**/*",
"cli.js",
"npmextra.json",
"readme.md"
]
} }

19
test/test.normalize.ts Normal file
View File

@ -0,0 +1,19 @@
import { tap, expect } from '@pushrocks/tapbundle';
import * as smartstring from '../ts/index';
tap.test('should normalize a string', async () => {
const testString = `
myawesome string;
is indented with two spaces
`;
const normalizedString = smartstring.normalize.standard(testString);
expect(normalizedString).to.equal(
`
myawesome string;
is indented with two spaces
`
);
});
tap.start();

14
test/test.type.ts Normal file
View File

@ -0,0 +1,14 @@
import { tap, expect } from '@pushrocks/tapbundle';
import * as smartstring from '../ts';
tap.test('should state valuid utf8', async () => {
expect(smartstring.type.isUtf8('hithere')).to.be.true;
});
tap.test('should state wether base64 string is valid', async () => {
const base64String = smartstring.base64.encode('hi there');
expect(smartstring.type.isBase64(base64String)).to.be.true;
expect(smartstring.type.isBase64('hi there')).to.be.false;
});
tap.start();

View File

@ -2,9 +2,12 @@ import * as create from './smartstring.create';
import * as docker from './smartstring.docker'; import * as docker from './smartstring.docker';
import * as indent from './smartstring.indent'; import * as indent from './smartstring.indent';
import * as normalize from './smartstring.normalize'; import * as normalize from './smartstring.normalize';
import * as type from './smartstring.type';
export { create, docker, normalize, indent }; export { create, docker, normalize, indent, type };
export { Base64, base64 } from './smartstring.base64'; export { Base64, base64 } from './smartstring.base64';
export { Domain } from './smartstring.domain'; export { Domain } from './smartstring.domain';
export { GitRepo } from './smartstring.git'; export { GitRepo } from './smartstring.git';
export { Cryptr } from './smartstring.encryption';

View File

@ -1,13 +1,29 @@
import * as plugins from './smartstring.plugins'; import * as plugins from './smartstring.plugins';
/**
* creates a random string
*
* ```ts
* createRandomString('AAAA')
* //=> 'AGHR'
* ```
*
* @param patternArg the pattern argument to use, Aa0!* are viable pattern descritors
* @param lengthArg the length of the random string
* @param optionsArg options
*/
export const createRandomString = ( export const createRandomString = (
patternArg: string, patternArg: string,
lengthArg: number, lengthArg?: number,
optionsArg: any optionsArg?: any
): string => { ): string => {
return plugins.randomatic(patternArg, lengthArg, optionsArg); return plugins.randomatic(patternArg, lengthArg, optionsArg);
}; };
export const createCryptoRandomString = (lengthArg): string => { /**
* creates a crytic string in the speicifed length
* @param lengthArg the length of the crypto string
*/
export const createCryptoRandomString = (lengthArg: number): string => {
return plugins.cryptoRandomString(lengthArg); return plugins.cryptoRandomString(lengthArg);
}; };

View File

@ -1,24 +1,26 @@
import * as plugins from './smartstring.plugins'; import * as plugins from './smartstring.plugins';
export class Domain { export class Domain {
fullName: string; public fullName: string;
level1: string; public level1: string;
level2: string; public level2: string;
level3: string; public level3: string;
level4: string; public level4: string;
level5: string; public level5: string;
protocol: string; public protocol: string;
zoneName: string; public zoneName: string;
// aliases // aliases
topLevel: string; public topLevel: string;
domainName; public domainName;
subDomain; public subDomain;
public port;
public nodeParsedUrl: plugins.url.UrlWithStringQuery;
constructor(domainStringArg: string) { constructor(domainStringArg: string) {
let regexMatches = domainRegex(domainStringArg); const regexMatches = this._domainRegex(domainStringArg);
this.fullName = ''; this.fullName = '';
for (let i = 1; i <= 5; i++) { for (let i = 1; i <= 5; i++) {
if (regexMatches[i - 1]) { if (regexMatches[i - 1]) {
let localMatch = regexMatches[i - 1]; const localMatch = regexMatches[i - 1];
this['level' + i.toString()] = localMatch; this['level' + i.toString()] = localMatch;
if (this.fullName === '') { if (this.fullName === '') {
this.fullName = localMatch; this.fullName = localMatch;
@ -29,33 +31,49 @@ export class Domain {
this['level' + i.toString()] = undefined; this['level' + i.toString()] = undefined;
} }
} }
this.protocol = protocolRegex(domainStringArg); this.protocol = this._protocolRegex(domainStringArg);
this.zoneName = this.level2 + '.' + this.level1; this.zoneName = this.level2 + '.' + this.level1;
// aliases // aliases
this.topLevel = this.level1; this.topLevel = this.level1;
this.domainName = this.level2; this.domainName = this.level2;
this.subDomain = this.level3; this.subDomain = this.level3;
}
this.nodeParsedUrl = plugins.url.parse(domainStringArg);
this.port = this.nodeParsedUrl.port;
} }
let domainRegex = function(stringArg: string) { // helper functions
let regexString = /([a-zA-Z0-9\-\_]*)\.{0,1}([a-zA-Z0-9\-\_]*)\.{0,1}([a-zA-Z0-9\-\_]*)\.{0,1}([a-zA-Z0-9\-\_]*)\.{0,1}([a-zA-Z0-9\-\_]*)\.{0,1}$/;
let regexMatches = regexString.exec(stringArg); /** */
private _domainRegex(stringArg: string) {
const regexString = /([a-zA-Z0-9\-\_]*)\.{0,1}([a-zA-Z0-9\-\_]*)\.{0,1}([a-zA-Z0-9\-\_]*)\.{0,1}([a-zA-Z0-9\-\_]*)\.{0,1}([a-zA-Z0-9\-\_]*)\.{0,1}$/;
const regexMatches = regexString.exec(stringArg);
regexMatches.reverse(); //make sure we build the domain from toplevel to subdomain (reversed order) regexMatches.reverse(); //make sure we build the domain from toplevel to subdomain (reversed order)
regexMatches.pop(); // pop the last element, which is, since we reversed the Array, the full String of matched elements regexMatches.pop(); // pop the last element, which is, since we reversed the Array, the full String of matched elements
let regexMatchesFiltered = regexMatches.filter(function(stringArg: string) { const regexMatchesFiltered = regexMatches.filter(function(stringArg: string) {
return stringArg !== ''; return stringArg !== '';
}); });
return regexMatchesFiltered; return regexMatchesFiltered;
}; }
let protocolRegex = function(stringArg: string) { private _protocolRegex(stringArg: string) {
let regexString = /^([a-zA-Z0-9]*):\/\//; const regexString = /^([a-zA-Z0-9]*):\/\//;
let regexMatches = regexString.exec(stringArg); const regexMatches = regexString.exec(stringArg);
if (regexMatches) { if (regexMatches) {
return regexMatches[1]; return regexMatches[1];
} else { } else {
return undefined; return undefined;
} }
}; }
private _portRegex(stringArg: string) {
const regexString = /^([a-zA-Z0-9]*):\/\//;
const regexMatches = regexString.exec(stringArg);
if (regexMatches) {
return regexMatches[1];
} else {
return undefined;
}
}
}

View File

@ -0,0 +1,43 @@
import * as plugins from './smartstring.plugins';
const algorithm = 'aes-256-ctr';
export class Cryptr {
private key: Buffer;
constructor(secret) {
if (!secret || typeof secret !== 'string') {
throw new Error('Cryptr: secret must be a non-0-length string');
}
this.key = plugins.crypto
.createHash('sha256')
.update(String(secret))
.digest();
}
encrypt(value: string) {
if (value == null) {
throw new Error('value must not be null or undefined');
}
const iv = plugins.crypto.randomBytes(16);
const cipher = plugins.crypto.createCipheriv(algorithm, this.key, iv);
const encrypted = cipher.update(String(value), 'utf8', 'hex') + cipher.final('hex');
return iv.toString('hex') + encrypted;
}
decrypt(value: string) {
if (value == null) {
throw new Error('value must not be null or undefined');
}
const stringValue = String(value);
const iv = Buffer.from(stringValue.slice(0, 32), 'hex');
const encrypted = stringValue.slice(32);
const decipher = plugins.crypto.createDecipheriv(algorithm, this.key, iv);
return decipher.update(encrypted, 'hex', 'utf8') + decipher.final('utf8');
}
}

View File

@ -3,11 +3,11 @@ import * as plugins from './smartstring.plugins';
/** /**
* replaces all occurences of something in a string * replaces all occurences of something in a string
* @param stringArg * @param stringArg
* @param searchRegExp * @param searchPattern
* @param replacementString * @param replacementString
*/ */
export const replaceAll = (stringArg: string, searchRegExp: any, replacementString: string) => { export const replaceAll = (stringArg: string, searchPattern: string, replacementString: string) => {
return stringArg.replace(new RegExp(searchRegExp, 'g'), replacementString); return stringArg.replace(new RegExp(searchPattern, 'g'), replacementString);
}; };
/** /**
@ -17,6 +17,6 @@ export const replaceAll = (stringArg: string, searchRegExp: any, replacementStri
export const standard = (stringArg: string): string => { export const standard = (stringArg: string): string => {
let fix1 = plugins.stripIndent(stringArg); // fix indention let fix1 = plugins.stripIndent(stringArg); // fix indention
let fix2 = plugins.normalizeNewline(fix1); // fix newlines let fix2 = plugins.normalizeNewline(fix1); // fix newlines
let fix3 = replaceAll(fix2, /\t/, ' '); // fix tabs let fix3 = replaceAll(fix2, '\t/', ' '); // fix tabs
return fix3; return fix3;
}; };

View File

@ -1,3 +1,10 @@
// node native
import crypto from 'crypto';
import * as url from 'url';
export { crypto, url };
// third party
export let jsBase64 = require('js-base64').Base64; export let jsBase64 = require('js-base64').Base64;
export let stripIndent = require('strip-indent'); export let stripIndent = require('strip-indent');

81
ts/smartstring.type.ts Normal file
View File

@ -0,0 +1,81 @@
import * as plugins from './smartstring.plugins';
import * as base64 from './smartstring.base64';
export const isUtf8 = (stringArg: string) => {
const bytes = Buffer.from(stringArg);
let i = 0;
while (i < bytes.length) {
if (
// ASCII
bytes[i] === 0x09 ||
bytes[i] === 0x0a ||
bytes[i] === 0x0d ||
(0x20 <= bytes[i] && bytes[i] <= 0x7e)
) {
i += 1;
continue;
}
if (
// non-overlong 2-byte
0xc2 <= bytes[i] &&
bytes[i] <= 0xdf &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xbf)
) {
i += 2;
continue;
}
if (
// excluding overlongs
(bytes[i] === 0xe0 &&
(0xa0 <= bytes[i + 1] && bytes[i + 1] <= 0xbf) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xbf)) || // straight 3-byte
(((0xe1 <= bytes[i] && bytes[i] <= 0xec) || bytes[i] === 0xee || bytes[i] === 0xef) &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xbf) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xbf)) || // excluding surrogates
(bytes[i] === 0xed &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x9f) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xbf))
) {
i += 3;
continue;
}
if (
// planes 1-3
(bytes[i] === 0xf0 &&
(0x90 <= bytes[i + 1] && bytes[i + 1] <= 0xbf) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xbf) &&
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xbf)) || // planes 4-15
(0xf1 <= bytes[i] &&
bytes[i] <= 0xf3 &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0xbf) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xbf) &&
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xbf)) || // plane 16
(bytes[i] === 0xf4 &&
(0x80 <= bytes[i + 1] && bytes[i + 1] <= 0x8f) &&
(0x80 <= bytes[i + 2] && bytes[i + 2] <= 0xbf) &&
(0x80 <= bytes[i + 3] && bytes[i + 3] <= 0xbf))
) {
i += 4;
continue;
}
return false;
}
return true;
};
export const isBase64 = (stringArg: string) => {
const notBase64 = /[^A-Z0-9+\/=]/i;
const len = stringArg.length;
if (!len || len % 4 !== 0 || notBase64.test(stringArg)) {
return false;
}
const firstPaddingChar = stringArg.indexOf('=');
return firstPaddingChar === -1 ||
firstPaddingChar === len - 1 ||
(firstPaddingChar === len - 2 && stringArg[len - 1] === '=');
};