mirror of
https://github.com/CorentinTh/it-tools.git
synced 2025-05-04 21:37:11 -04:00
fix: refactor to service + add regex diagram + ui enhancements
This commit is contained in:
parent
f61db56abb
commit
c9f9bb1273
6 changed files with 213 additions and 57 deletions
3
components.d.ts
vendored
3
components.d.ts
vendored
|
@ -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']
|
||||||
|
|
|
@ -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
18
pnpm-lock.yaml
generated
|
@ -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:
|
||||||
|
|
106
src/tools/regex-tester/regex-tester.service.test.ts
Normal file
106
src/tools/regex-tester/regex-tester.service.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
61
src/tools/regex-tester/regex-tester.service.ts
Normal file
61
src/tools/regex-tester/regex-tester.service.ts
Normal 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;
|
||||||
|
}
|
|
@ -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>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue