fix: refactor to service + add regex diagram + ui enhancements

This commit is contained in:
ShareVB 2024-05-15 22:03:23 +02:00
parent f61db56abb
commit c9f9bb1273
6 changed files with 213 additions and 57 deletions

3
components.d.ts vendored
View file

@ -134,6 +134,7 @@ declare module '@vue/runtime-core' {
NConfigProvider: typeof import('naive-ui')['NConfigProvider'] NConfigProvider: typeof import('naive-ui')['NConfigProvider']
NDivider: typeof import('naive-ui')['NDivider'] NDivider: typeof import('naive-ui')['NDivider']
NEllipsis: typeof import('naive-ui')['NEllipsis'] NEllipsis: typeof import('naive-ui')['NEllipsis']
NForm: typeof import('naive-ui')['NForm']
NFormItem: typeof import('naive-ui')['NFormItem'] NFormItem: typeof import('naive-ui')['NFormItem']
NGi: typeof import('naive-ui')['NGi'] NGi: typeof import('naive-ui')['NGi']
NGrid: typeof import('naive-ui')['NGrid'] NGrid: typeof import('naive-ui')['NGrid']
@ -146,8 +147,10 @@ declare module '@vue/runtime-core' {
NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NLayoutSider: typeof import('naive-ui')['NLayoutSider']
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSlider: typeof import('naive-ui')['NSlider']
NSpace: typeof import('naive-ui')['NSpace'] NSpace: typeof import('naive-ui')['NSpace']
NSpin: typeof import('naive-ui')['NSpin'] NSpin: typeof import('naive-ui')['NSpin']
NSwitch: typeof import('naive-ui')['NSwitch']
NTable: typeof import('naive-ui')['NTable'] NTable: typeof import('naive-ui')['NTable']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']

View file

@ -37,6 +37,7 @@
"dependencies": { "dependencies": {
"@it-tools/bip39": "^0.0.4", "@it-tools/bip39": "^0.0.4",
"@it-tools/oggen": "^1.3.0", "@it-tools/oggen": "^1.3.0",
"@regexper/render": "^1.0.0",
"@sindresorhus/slugify": "^2.2.1", "@sindresorhus/slugify": "^2.2.1",
"@tiptap/pm": "2.1.6", "@tiptap/pm": "2.1.6",
"@tiptap/starter-kit": "2.1.6", "@tiptap/starter-kit": "2.1.6",

18
pnpm-lock.yaml generated
View file

@ -11,6 +11,9 @@ dependencies:
'@it-tools/oggen': '@it-tools/oggen':
specifier: ^1.3.0 specifier: ^1.3.0
version: 1.3.0 version: 1.3.0
'@regexper/render':
specifier: ^1.0.0
version: 1.0.0
'@sindresorhus/slugify': '@sindresorhus/slugify':
specifier: ^2.2.1 specifier: ^2.2.1
version: 2.2.1 version: 2.2.1
@ -2468,6 +2471,17 @@ packages:
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
dev: false dev: false
/@regexper/parser@1.0.0:
resolution: {integrity: sha512-S8AWIGpCNdl9PNHdbhI6TpXZsPk6FDU/RTZI+6UFF4rVFqDQKjCIbZSgFu7NihoEZKq57wKFPbbT1EzrjVvPHA==}
dev: false
/@regexper/render@1.0.0:
resolution: {integrity: sha512-xYm9RUgnhhZotTtf8UZpK1PG2CcTRXQ3JPwfTlYUZsy2J+UcTVc7BaO/MJadpMoVuT8jrIyptH4Y0HLzqhI3hQ==}
dependencies:
'@regexper/parser': 1.0.0
'@svgdotjs/svg.js': 3.2.0
dev: false
/@remirror/core-constants@2.0.1: /@remirror/core-constants@2.0.1:
resolution: {integrity: sha512-ZR4aihtnnT9lMbhh5DEbsriJRlukRXmLZe7HmM+6ufJNNUDoazc75UX26xbgQlNUqgAqMcUdGFAnPc1JwgAdLQ==} resolution: {integrity: sha512-ZR4aihtnnT9lMbhh5DEbsriJRlukRXmLZe7HmM+6ufJNNUDoazc75UX26xbgQlNUqgAqMcUdGFAnPc1JwgAdLQ==}
dependencies: dependencies:
@ -2617,6 +2631,10 @@ packages:
string.prototype.matchall: 4.0.10 string.prototype.matchall: 4.0.10
dev: true dev: true
/@svgdotjs/svg.js@3.2.0:
resolution: {integrity: sha512-Tr8p+QVP7y+QT1GBlq1Tt57IvedVH8zCPoYxdHLX0Oof3a/PqnC/tXAkVufv1JQJfsDHlH/UrjcDfgxSofqSNA==}
dev: false
/@tiptap/core@2.1.12(@tiptap/pm@2.1.6): /@tiptap/core@2.1.12(@tiptap/pm@2.1.6):
resolution: {integrity: sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==} resolution: {integrity: sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==}
peerDependencies: peerDependencies:

View file

@ -0,0 +1,106 @@
import { describe, expect, it } from 'vitest';
import { matchRegex } from './regex-tester.service';
const regexesData = [
{
regex: '',
text: '',
flags: '',
result: [],
},
{
regex: '.*',
text: '',
flags: '',
result: [],
},
{
regex: '',
text: 'aaa',
flags: '',
result: [],
},
{
regex: 'a',
text: 'baaa',
flags: '',
result: [
{
captures: [],
groups: [],
index: 1,
value: 'a',
},
],
},
{
regex: '(.)(?<g>r)',
text: 'azertyr',
flags: 'g',
result: [
{
captures: [
{
end: 3,
name: '1',
start: 2,
value: 'e',
},
{
end: 4,
name: '2',
start: 3,
value: 'r',
},
],
groups: [
{
end: 4,
name: 'g',
start: 3,
value: 'r',
},
],
index: 2,
value: 'er',
},
{
captures: [
{
end: 6,
name: '1',
start: 5,
value: 'y',
},
{
end: 7,
name: '2',
start: 6,
value: 'r',
},
],
groups: [
{
end: 7,
name: 'g',
start: 6,
value: 'r',
},
],
index: 5,
value: 'yr',
},
],
},
];
describe('regex-tester', () => {
for (const reg of regexesData) {
const { regex, text, flags, result: expected_result } = reg;
it(`Should matchRegex("${regex}","${text}","${flags}") return correct result`, async () => {
const result = matchRegex(regex, text, `${flags}d`);
expect(result).to.deep.equal(expected_result);
});
}
});

View file

@ -0,0 +1,61 @@
interface RegExpGroupIndices {
[name: string]: [number, number]
}
interface RegExpIndices extends Array<[number, number]> {
groups: RegExpGroupIndices
}
interface RegExpExecArrayWithIndices extends RegExpExecArray {
indices: RegExpIndices
}
interface GroupCapture {
name: string
value: string
start: number
end: number
};
export function matchRegex(regex: string, text: string, flags: string) {
// if (regex === '' || text === '') {
// return [];
// }
let lastIndex = -1;
const re = new RegExp(regex, flags);
const results = [];
let match = re.exec(text) as RegExpExecArrayWithIndices;
while (match !== null) {
if (re.lastIndex === lastIndex || match[0] === '') {
break;
}
const indices = match.indices;
const captures: Array<GroupCapture> = [];
Object.entries(match).forEach(([captureName, captureValue]) => {
if (captureName !== '0' && captureName.match(/\d+/)) {
captures.push({
name: captureName,
value: captureValue,
start: indices[Number(captureName)][0],
end: indices[Number(captureName)][1],
});
}
});
const groups: Array<GroupCapture> = [];
Object.entries(match.groups || {}).forEach(([groupName, groupValue]) => {
groups.push({
name: groupName,
value: groupValue,
start: indices.groups[groupName][0],
end: indices.groups[groupName][1],
});
});
results.push({
index: match.index,
value: match[0],
captures,
groups,
});
lastIndex = re.lastIndex;
match = re.exec(text) as RegExpExecArrayWithIndices;
}
return results;
}

View file

@ -1,24 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import RandExp from 'randexp'; import RandExp from 'randexp';
import { render } from '@regexper/render';
import { matchRegex } from './regex-tester.service';
import { useValidation } from '@/composable/validation'; import { useValidation } from '@/composable/validation';
import { useQueryParamOrStorage } from '@/composable/queryParams'; import { useQueryParamOrStorage } from '@/composable/queryParams';
interface RegExpGroupIndices {
[name: string]: [number, number]
}
interface RegExpIndices extends Array<[number, number]> {
groups: RegExpGroupIndices
}
interface RegExpExecArrayWithIndices extends RegExpExecArray {
indices: RegExpIndices
}
interface GroupCapture {
name: string
value: string
start: number
end: number
};
const regex = useQueryParamOrStorage({ name: 'regex', storageName: 'regex-tester:regex', defaultValue: '' }); const regex = useQueryParamOrStorage({ name: 'regex', storageName: 'regex-tester:regex', defaultValue: '' });
const text = ref(''); const text = ref('');
const global = ref(true); const global = ref(true);
@ -27,6 +13,7 @@ const multiline = ref(false);
const dotAll = ref(true); const dotAll = ref(true);
const unicode = ref(true); const unicode = ref(true);
const unicodeSets = ref(false); const unicodeSets = ref(false);
const visualizerSVG = ref() as Ref<SVGSVGElement>;
const regexValidation = useValidation({ const regexValidation = useValidation({
source: regex, source: regex,
@ -42,10 +29,6 @@ const regexValidation = useValidation({
], ],
}); });
const results = computed(() => { const results = computed(() => {
if (regex.value === '' || text.value === '') {
return [];
}
let flags = 'd'; let flags = 'd';
if (global.value) { if (global.value) {
flags += 'g'; flags += 'g';
@ -67,40 +50,7 @@ const results = computed(() => {
} }
try { try {
const re = new RegExp(regex.value, flags); return matchRegex(regex.value, text.value, flags);
const results = [];
let match = re.exec(text.value) as RegExpExecArrayWithIndices;
while (match !== null) {
const indices = match.indices;
const captures: Array<GroupCapture> = [];
Object.entries(match).forEach(([captureName, captureValue]) => {
if (captureName !== '0' && captureName.match(/\d+/)) {
captures.push({
name: captureName,
value: captureValue,
start: indices[Number(captureName)][0],
end: indices[Number(captureName)][1],
});
}
});
const groups: Array<GroupCapture> = [];
Object.entries(match.groups || {}).forEach(([groupName, groupValue]) => {
groups.push({
name: groupName,
value: groupValue,
start: indices.groups[groupName][0],
end: indices.groups[groupName][1],
});
});
results.push({
index: match.index,
value: match[0],
captures,
groups,
});
match = re.exec(text.value) as RegExpExecArrayWithIndices;
}
return results;
} }
catch (_) { catch (_) {
return []; return [];
@ -116,6 +66,19 @@ const sample = computed(() => {
return ''; return '';
} }
}); });
watchEffect(
async () => {
const regexValue = regex.value;
const svg = visualizerSVG.value;
svg.childNodes.forEach(n => n.remove());
try {
await render(regexValue, svg);
}
catch (_) {
}
},
);
</script> </script>
<template> <template>
@ -164,7 +127,7 @@ const sample = computed(() => {
/> />
</c-card> </c-card>
<c-card title="Matches" mb-1> <c-card title="Matches" mb-1 mt-3>
<n-table v-if="results?.length > 0"> <n-table v-if="results?.length > 0">
<thead> <thead>
<tr> <tr>
@ -208,8 +171,12 @@ const sample = computed(() => {
</c-alert> </c-alert>
</c-card> </c-card>
<c-card title="Sample matching text"> <c-card title="Sample matching text" mt-3>
<pre style="white-space: pre-wrap">{{ sample }}</pre> <pre style="white-space: pre-wrap; word-break: break-all;">{{ sample }}</pre>
</c-card>
<c-card title="Regex Diagram" style="overflow-x: scroll;" mt-3>
<svg ref="visualizerSVG" />
</c-card> </c-card>
</div> </div>
</template> </template>