fix: refactor to service + add regex diagram + ui enhancements
This commit is contained in:
		
							parent
							
								
									f61db56abb
								
							
						
					
					
						commit
						c9f9bb1273
					
				
							
								
								
									
										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'] | ||||
|     NDivider: typeof import('naive-ui')['NDivider'] | ||||
|     NEllipsis: typeof import('naive-ui')['NEllipsis'] | ||||
|     NForm: typeof import('naive-ui')['NForm'] | ||||
|     NFormItem: typeof import('naive-ui')['NFormItem'] | ||||
|     NGi: typeof import('naive-ui')['NGi'] | ||||
|     NGrid: typeof import('naive-ui')['NGrid'] | ||||
| @ -146,8 +147,10 @@ declare module '@vue/runtime-core' { | ||||
|     NLayoutSider: typeof import('naive-ui')['NLayoutSider'] | ||||
|     NMenu: typeof import('naive-ui')['NMenu'] | ||||
|     NScrollbar: typeof import('naive-ui')['NScrollbar'] | ||||
|     NSlider: typeof import('naive-ui')['NSlider'] | ||||
|     NSpace: typeof import('naive-ui')['NSpace'] | ||||
|     NSpin: typeof import('naive-ui')['NSpin'] | ||||
|     NSwitch: typeof import('naive-ui')['NSwitch'] | ||||
|     NTable: typeof import('naive-ui')['NTable'] | ||||
|     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'] | ||||
|  | ||||
| @ -37,6 +37,7 @@ | ||||
|   "dependencies": { | ||||
|     "@it-tools/bip39": "^0.0.4", | ||||
|     "@it-tools/oggen": "^1.3.0", | ||||
|     "@regexper/render": "^1.0.0", | ||||
|     "@sindresorhus/slugify": "^2.2.1", | ||||
|     "@tiptap/pm": "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': | ||||
|     specifier: ^1.3.0 | ||||
|     version: 1.3.0 | ||||
|   '@regexper/render': | ||||
|     specifier: ^1.0.0 | ||||
|     version: 1.0.0 | ||||
|   '@sindresorhus/slugify': | ||||
|     specifier: ^2.2.1 | ||||
|     version: 2.2.1 | ||||
| @ -2468,6 +2471,17 @@ packages: | ||||
|     resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} | ||||
|     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: | ||||
|     resolution: {integrity: sha512-ZR4aihtnnT9lMbhh5DEbsriJRlukRXmLZe7HmM+6ufJNNUDoazc75UX26xbgQlNUqgAqMcUdGFAnPc1JwgAdLQ==} | ||||
|     dependencies: | ||||
| @ -2617,6 +2631,10 @@ packages: | ||||
|       string.prototype.matchall: 4.0.10 | ||||
|     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): | ||||
|     resolution: {integrity: sha512-ZGc3xrBJA9KY8kln5AYTj8y+GDrKxi7u95xIl2eccrqTY5CQeRu6HRNM1yT4mAjuSaG9jmazyjGRlQuhyxCKxQ==} | ||||
|     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"> | ||||
| import RandExp from 'randexp'; | ||||
| import { render } from '@regexper/render'; | ||||
| import { matchRegex } from './regex-tester.service'; | ||||
| import { useValidation } from '@/composable/validation'; | ||||
| 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 text = ref(''); | ||||
| const global = ref(true); | ||||
| @ -27,6 +13,7 @@ const multiline = ref(false); | ||||
| const dotAll = ref(true); | ||||
| const unicode = ref(true); | ||||
| const unicodeSets = ref(false); | ||||
| const visualizerSVG = ref() as Ref<SVGSVGElement>; | ||||
| 
 | ||||
| const regexValidation = useValidation({ | ||||
|   source: regex, | ||||
| @ -42,10 +29,6 @@ const regexValidation = useValidation({ | ||||
|   ], | ||||
| }); | ||||
| const results = computed(() => { | ||||
|   if (regex.value === '' || text.value === '') { | ||||
|     return []; | ||||
|   } | ||||
| 
 | ||||
|   let flags = 'd'; | ||||
|   if (global.value) { | ||||
|     flags += 'g'; | ||||
| @ -67,40 +50,7 @@ const results = computed(() => { | ||||
|   } | ||||
| 
 | ||||
|   try { | ||||
|     const re = new RegExp(regex.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; | ||||
|     return matchRegex(regex.value, text.value, flags); | ||||
|   } | ||||
|   catch (_) { | ||||
|     return []; | ||||
| @ -116,6 +66,19 @@ const sample = computed(() => { | ||||
|     return ''; | ||||
|   } | ||||
| }); | ||||
| 
 | ||||
| watchEffect( | ||||
|   async () => { | ||||
|     const regexValue = regex.value; | ||||
|     const svg = visualizerSVG.value; | ||||
|     svg.childNodes.forEach(n => n.remove()); | ||||
|     try { | ||||
|       await render(regexValue, svg); | ||||
|     } | ||||
|     catch (_) { | ||||
|     } | ||||
|   }, | ||||
| ); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
| @ -164,7 +127,7 @@ const sample = computed(() => { | ||||
|       /> | ||||
|     </c-card> | ||||
| 
 | ||||
|     <c-card title="Matches" mb-1> | ||||
|     <c-card title="Matches" mb-1 mt-3> | ||||
|       <n-table v-if="results?.length > 0"> | ||||
|         <thead> | ||||
|           <tr> | ||||
| @ -208,8 +171,12 @@ const sample = computed(() => { | ||||
|       </c-alert> | ||||
|     </c-card> | ||||
| 
 | ||||
|     <c-card title="Sample matching text"> | ||||
|       <pre style="white-space: pre-wrap">{{ sample }}</pre> | ||||
|     <c-card title="Sample matching text" mt-3> | ||||
|       <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> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user