Skip to content

Commit c9f9bb1

Browse files
committed
fix: refactor to service + add regex diagram + ui enhancements
1 parent f61db56 commit c9f9bb1

File tree

6 files changed

+213
-57
lines changed

6 files changed

+213
-57
lines changed

components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,7 @@ declare module '@vue/runtime-core' {
134134
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
135135
NDivider: typeof import('naive-ui')['NDivider']
136136
NEllipsis: typeof import('naive-ui')['NEllipsis']
137+
NForm: typeof import('naive-ui')['NForm']
137138
NFormItem: typeof import('naive-ui')['NFormItem']
138139
NGi: typeof import('naive-ui')['NGi']
139140
NGrid: typeof import('naive-ui')['NGrid']
@@ -146,8 +147,10 @@ declare module '@vue/runtime-core' {
146147
NLayoutSider: typeof import('naive-ui')['NLayoutSider']
147148
NMenu: typeof import('naive-ui')['NMenu']
148149
NScrollbar: typeof import('naive-ui')['NScrollbar']
150+
NSlider: typeof import('naive-ui')['NSlider']
149151
NSpace: typeof import('naive-ui')['NSpace']
150152
NSpin: typeof import('naive-ui')['NSpin']
153+
NSwitch: typeof import('naive-ui')['NSwitch']
151154
NTable: typeof import('naive-ui')['NTable']
152155
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
153156
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"dependencies": {
3838
"@it-tools/bip39": "^0.0.4",
3939
"@it-tools/oggen": "^1.3.0",
40+
"@regexper/render": "^1.0.0",
4041
"@sindresorhus/slugify": "^2.2.1",
4142
"@tiptap/pm": "2.1.6",
4243
"@tiptap/starter-kit": "2.1.6",

pnpm-lock.yaml

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { matchRegex } from './regex-tester.service';
3+
4+
const regexesData = [
5+
{
6+
regex: '',
7+
text: '',
8+
flags: '',
9+
result: [],
10+
},
11+
{
12+
regex: '.*',
13+
text: '',
14+
flags: '',
15+
result: [],
16+
},
17+
{
18+
regex: '',
19+
text: 'aaa',
20+
flags: '',
21+
result: [],
22+
},
23+
{
24+
regex: 'a',
25+
text: 'baaa',
26+
flags: '',
27+
result: [
28+
{
29+
captures: [],
30+
groups: [],
31+
index: 1,
32+
value: 'a',
33+
},
34+
],
35+
},
36+
{
37+
regex: '(.)(?<g>r)',
38+
text: 'azertyr',
39+
flags: 'g',
40+
result: [
41+
{
42+
captures: [
43+
{
44+
end: 3,
45+
name: '1',
46+
start: 2,
47+
value: 'e',
48+
},
49+
{
50+
end: 4,
51+
name: '2',
52+
start: 3,
53+
value: 'r',
54+
},
55+
],
56+
groups: [
57+
{
58+
end: 4,
59+
name: 'g',
60+
start: 3,
61+
value: 'r',
62+
},
63+
],
64+
index: 2,
65+
value: 'er',
66+
},
67+
{
68+
captures: [
69+
{
70+
end: 6,
71+
name: '1',
72+
start: 5,
73+
value: 'y',
74+
},
75+
{
76+
end: 7,
77+
name: '2',
78+
start: 6,
79+
value: 'r',
80+
},
81+
],
82+
groups: [
83+
{
84+
end: 7,
85+
name: 'g',
86+
start: 6,
87+
value: 'r',
88+
},
89+
],
90+
index: 5,
91+
value: 'yr',
92+
},
93+
],
94+
},
95+
];
96+
97+
describe('regex-tester', () => {
98+
for (const reg of regexesData) {
99+
const { regex, text, flags, result: expected_result } = reg;
100+
it(`Should matchRegex("${regex}","${text}","${flags}") return correct result`, async () => {
101+
const result = matchRegex(regex, text, `${flags}d`);
102+
103+
expect(result).to.deep.equal(expected_result);
104+
});
105+
}
106+
});
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
interface RegExpGroupIndices {
2+
[name: string]: [number, number]
3+
}
4+
interface RegExpIndices extends Array<[number, number]> {
5+
groups: RegExpGroupIndices
6+
}
7+
interface RegExpExecArrayWithIndices extends RegExpExecArray {
8+
indices: RegExpIndices
9+
}
10+
interface GroupCapture {
11+
name: string
12+
value: string
13+
start: number
14+
end: number
15+
};
16+
17+
export function matchRegex(regex: string, text: string, flags: string) {
18+
// if (regex === '' || text === '') {
19+
// return [];
20+
// }
21+
22+
let lastIndex = -1;
23+
const re = new RegExp(regex, flags);
24+
const results = [];
25+
let match = re.exec(text) as RegExpExecArrayWithIndices;
26+
while (match !== null) {
27+
if (re.lastIndex === lastIndex || match[0] === '') {
28+
break;
29+
}
30+
const indices = match.indices;
31+
const captures: Array<GroupCapture> = [];
32+
Object.entries(match).forEach(([captureName, captureValue]) => {
33+
if (captureName !== '0' && captureName.match(/\d+/)) {
34+
captures.push({
35+
name: captureName,
36+
value: captureValue,
37+
start: indices[Number(captureName)][0],
38+
end: indices[Number(captureName)][1],
39+
});
40+
}
41+
});
42+
const groups: Array<GroupCapture> = [];
43+
Object.entries(match.groups || {}).forEach(([groupName, groupValue]) => {
44+
groups.push({
45+
name: groupName,
46+
value: groupValue,
47+
start: indices.groups[groupName][0],
48+
end: indices.groups[groupName][1],
49+
});
50+
});
51+
results.push({
52+
index: match.index,
53+
value: match[0],
54+
captures,
55+
groups,
56+
});
57+
lastIndex = re.lastIndex;
58+
match = re.exec(text) as RegExpExecArrayWithIndices;
59+
}
60+
return results;
61+
}

src/tools/regex-tester/regex-tester.vue

Lines changed: 24 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,10 @@
11
<script setup lang="ts">
22
import RandExp from 'randexp';
3+
import { render } from '@regexper/render';
4+
import { matchRegex } from './regex-tester.service';
35
import { useValidation } from '@/composable/validation';
46
import { useQueryParamOrStorage } from '@/composable/queryParams';
57
6-
interface RegExpGroupIndices {
7-
[name: string]: [number, number]
8-
}
9-
interface RegExpIndices extends Array<[number, number]> {
10-
groups: RegExpGroupIndices
11-
}
12-
interface RegExpExecArrayWithIndices extends RegExpExecArray {
13-
indices: RegExpIndices
14-
}
15-
interface GroupCapture {
16-
name: string
17-
value: string
18-
start: number
19-
end: number
20-
};
21-
228
const regex = useQueryParamOrStorage({ name: 'regex', storageName: 'regex-tester:regex', defaultValue: '' });
239
const text = ref('');
2410
const global = ref(true);
@@ -27,6 +13,7 @@ const multiline = ref(false);
2713
const dotAll = ref(true);
2814
const unicode = ref(true);
2915
const unicodeSets = ref(false);
16+
const visualizerSVG = ref() as Ref<SVGSVGElement>;
3017
3118
const regexValidation = useValidation({
3219
source: regex,
@@ -42,10 +29,6 @@ const regexValidation = useValidation({
4229
],
4330
});
4431
const results = computed(() => {
45-
if (regex.value === '' || text.value === '') {
46-
return [];
47-
}
48-
4932
let flags = 'd';
5033
if (global.value) {
5134
flags += 'g';
@@ -67,40 +50,7 @@ const results = computed(() => {
6750
}
6851
6952
try {
70-
const re = new RegExp(regex.value, flags);
71-
const results = [];
72-
let match = re.exec(text.value) as RegExpExecArrayWithIndices;
73-
while (match !== null) {
74-
const indices = match.indices;
75-
const captures: Array<GroupCapture> = [];
76-
Object.entries(match).forEach(([captureName, captureValue]) => {
77-
if (captureName !== '0' && captureName.match(/\d+/)) {
78-
captures.push({
79-
name: captureName,
80-
value: captureValue,
81-
start: indices[Number(captureName)][0],
82-
end: indices[Number(captureName)][1],
83-
});
84-
}
85-
});
86-
const groups: Array<GroupCapture> = [];
87-
Object.entries(match.groups || {}).forEach(([groupName, groupValue]) => {
88-
groups.push({
89-
name: groupName,
90-
value: groupValue,
91-
start: indices.groups[groupName][0],
92-
end: indices.groups[groupName][1],
93-
});
94-
});
95-
results.push({
96-
index: match.index,
97-
value: match[0],
98-
captures,
99-
groups,
100-
});
101-
match = re.exec(text.value) as RegExpExecArrayWithIndices;
102-
}
103-
return results;
53+
return matchRegex(regex.value, text.value, flags);
10454
}
10555
catch (_) {
10656
return [];
@@ -116,6 +66,19 @@ const sample = computed(() => {
11666
return '';
11767
}
11868
});
69+
70+
watchEffect(
71+
async () => {
72+
const regexValue = regex.value;
73+
const svg = visualizerSVG.value;
74+
svg.childNodes.forEach(n => n.remove());
75+
try {
76+
await render(regexValue, svg);
77+
}
78+
catch (_) {
79+
}
80+
},
81+
);
11982
</script>
12083

12184
<template>
@@ -164,7 +127,7 @@ const sample = computed(() => {
164127
/>
165128
</c-card>
166129

167-
<c-card title="Matches" mb-1>
130+
<c-card title="Matches" mb-1 mt-3>
168131
<n-table v-if="results?.length > 0">
169132
<thead>
170133
<tr>
@@ -208,8 +171,12 @@ const sample = computed(() => {
208171
</c-alert>
209172
</c-card>
210173

211-
<c-card title="Sample matching text">
212-
<pre style="white-space: pre-wrap">{{ sample }}</pre>
174+
<c-card title="Sample matching text" mt-3>
175+
<pre style="white-space: pre-wrap; word-break: break-all;">{{ sample }}</pre>
176+
</c-card>
177+
178+
<c-card title="Regex Diagram" style="overflow-x: scroll;" mt-3>
179+
<svg ref="visualizerSVG" />
213180
</c-card>
214181
</div>
215182
</template>

0 commit comments

Comments
 (0)