diff --git a/components.d.ts b/components.d.ts index 89f41f80..57e1dad7 100644 --- a/components.d.ts +++ b/components.d.ts @@ -103,6 +103,7 @@ declare module '@vue/runtime-core' { IconMdiTriangleDown: typeof import('~icons/mdi/triangle-down')['default'] InputCopyable: typeof import('./src/components/InputCopyable.vue')['default'] IntegerBaseConverter: typeof import('./src/tools/integer-base-converter/integer-base-converter.vue')['default'] + IntegersToIp: typeof import('./src/tools/integers-to-ip/integers-to-ip.vue')['default'] Ipv4AddressConverter: typeof import('./src/tools/ipv4-address-converter/ipv4-address-converter.vue')['default'] Ipv4RangeExpander: typeof import('./src/tools/ipv4-range-expander/ipv4-range-expander.vue')['default'] Ipv4SubnetCalculator: typeof import('./src/tools/ipv4-subnet-calculator/ipv4-subnet-calculator.vue')['default'] @@ -129,11 +130,10 @@ declare module '@vue/runtime-core' { MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] - NCode: typeof import('naive-ui')['NCode'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] 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'] NH1: typeof import('naive-ui')['NH1'] NH3: typeof import('naive-ui')['NH3'] @@ -142,9 +142,6 @@ declare module '@vue/runtime-core' { NLayout: typeof import('naive-ui')['NLayout'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NMenu: typeof import('naive-ui')['NMenu'] - NScrollbar: typeof import('naive-ui')['NScrollbar'] - NSlider: typeof import('naive-ui')['NSlider'] - NSwitch: typeof import('naive-ui')['NSwitch'] 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'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] diff --git a/package.json b/package.json index 6191f702..c3553e8a 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,7 @@ "highlight.js": "^11.7.0", "iarna-toml-esm": "^3.0.5", "ibantools": "^4.3.3", + "ip-bigint": "^8.2.0", "js-base64": "^3.7.6", "json5": "^2.2.3", "jwt-decode": "^3.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3044541a..9a6b50c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -95,6 +95,9 @@ dependencies: ibantools: specifier: ^4.3.3 version: 4.3.3 + ip-bigint: + specifier: ^8.2.0 + version: 8.2.0 js-base64: specifier: ^3.7.6 version: 3.7.7 @@ -3360,7 +3363,7 @@ packages: dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 10.11.1(vue@3.3.4) + '@vueuse/shared': 11.0.3(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: @@ -3993,19 +3996,19 @@ packages: - vue dev: false - /@vueuse/shared@10.11.1(vue@3.3.4): - resolution: {integrity: sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==} + /@vueuse/shared@10.3.0(vue@3.3.4): + resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} dependencies: - vue-demi: 0.14.10(vue@3.3.4) + vue-demi: 0.14.5(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue dev: false - /@vueuse/shared@10.3.0(vue@3.3.4): - resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} + /@vueuse/shared@11.0.3(vue@3.3.4): + resolution: {integrity: sha512-0rY2m6HS5t27n/Vp5cTDsKTlNnimCqsbh/fmT2LgE+aaU42EMfXo8+bNX91W9I7DDmxfuACXMmrd7d79JxkqWA==} dependencies: - vue-demi: 0.14.5(vue@3.3.4) + vue-demi: 0.14.10(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -6192,6 +6195,11 @@ packages: sprintf-js: 1.1.2 dev: false + /ip-bigint@8.2.0: + resolution: {integrity: sha512-46EAEKzGNxojH5JaGEeCix49tL4h1W8ia5mhogZ68HroVAfyLj1E+SFFid4GuyK0mdIKjwcAITLqwg1wlkx2iQ==} + engines: {node: '>=18'} + dev: false + /ip-cidr@3.1.0: resolution: {integrity: sha512-HUCn4snshEX1P8cja/IyU3qk8FVDW8T5zZcegDFbu4w7NojmAhk5NcOgj3M8+0fmumo1afJTPDtJlzsxLdOjtg==} engines: {node: '>=10.0.0'} diff --git a/src/tools/index.ts b/src/tools/index.ts index b4c161ef..6dda674f 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -2,6 +2,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; import { tool as emailNormalizer } from './email-normalizer'; +import { tool as integersToIp } from './integers-to-ip'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -158,7 +159,15 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Network', - components: [ipv4SubnetCalculator, ipv4AddressConverter, ipv4RangeExpander, macAddressLookup, macAddressGenerator, ipv6UlaGenerator], + components: [ + ipv4SubnetCalculator, + ipv4AddressConverter, + ipv4RangeExpander, + macAddressLookup, + macAddressGenerator, + ipv6UlaGenerator, + integersToIp, + ], }, { name: 'Math', diff --git a/src/tools/integer-base-converter/integer-base-converter.model.test.ts b/src/tools/integer-base-converter/integer-base-converter.model.test.ts index c7d7db79..b7bcf95b 100644 --- a/src/tools/integer-base-converter/integer-base-converter.model.test.ts +++ b/src/tools/integer-base-converter/integer-base-converter.model.test.ts @@ -11,9 +11,21 @@ describe('integer-base-converter', () => { expect(convertBase({ value: '10100101', fromBase: 2, toBase: 16 })).toEqual('a5'); expect(convertBase({ value: '192654', fromBase: 10, toBase: 8 })).toEqual('570216'); expect(convertBase({ value: 'zz', fromBase: 64, toBase: 10 })).toEqual('2275'); - expect(convertBase({ value: '42540766411283223938465490632011909384', fromBase: 10, toBase: 10 })).toEqual('42540766411283223938465490632011909384'); - expect(convertBase({ value: '42540766411283223938465490632011909384', fromBase: 10, toBase: 16 })).toEqual('20010db8000085a300000000ac1f8908'); - expect(convertBase({ value: '20010db8000085a300000000ac1f8908', fromBase: 16, toBase: 10 })).toEqual('42540766411283223938465490632011909384'); + expect(convertBase({ value: 'AA', fromBase: 16, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: 'aa', fromBase: 16, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '0xAA', fromBase: -1, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '&HAA', fromBase: -1, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '0xAAUL', fromBase: -1, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '0XAAUL', fromBase: -1, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '10UL', fromBase: 10, toBase: 10 })).toEqual('10'); + expect(convertBase({ value: '10n', fromBase: 10, toBase: 10 })).toEqual('10'); + expect(convertBase({ value: '0o252', fromBase: -1, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '&O252', fromBase: -1, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '192 654', fromBase: 10, toBase: 8 })).toEqual('570216'); + expect(convertBase({ value: '192.654', fromBase: 10, toBase: 8 })).toEqual('570216'); + expect(convertBase({ value: '0b10101010', fromBase: -1, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '0b_1010_1010', fromBase: -1, toBase: 10 })).toEqual('170'); + expect(convertBase({ value: '192,654', fromBase: 10, toBase: 8 })).toEqual('570216'); }); }); }); diff --git a/src/tools/integer-base-converter/integer-base-converter.model.ts b/src/tools/integer-base-converter/integer-base-converter.model.ts index da0fe77f..431876f3 100644 --- a/src/tools/integer-base-converter/integer-base-converter.model.ts +++ b/src/tools/integer-base-converter/integer-base-converter.model.ts @@ -1,20 +1,61 @@ -export function convertBase({ value, fromBase, toBase }: { value: string; fromBase: number; toBase: number }) { +export function hasNumberPrefix(value: string) { + return (value ?? '').trim().match(/^(0[xob].|&[hob].)/i); +} + +export function convertBase( + { + value, fromBase, toBase, + ignorePunctuationsRegexChars = ' \u00A0_\.,-', + handlePrefixSuffix = true, + ignoreCase = true, + }: { + value: string + fromBase: number + toBase: number + ignorePunctuationsRegexChars?: string + handlePrefixSuffix?: boolean + ignoreCase?: boolean + }) { + let cleanedValue = (value ?? '0').trim(); + if (ignorePunctuationsRegexChars) { + cleanedValue = cleanedValue.replace(new RegExp(`[${ignorePunctuationsRegexChars}]`, 'g'), ''); + } + let finalFromBase = fromBase; + if (handlePrefixSuffix) { + for (const regBase of [ + { base: 2, regex: /^(&b|0b)?([01]+)([IULZn]*)$/i }, + { base: 8, regex: /^(&o|0o)?([0-7]+)([IULZn]*)$/i }, + { base: 16, regex: /^(&h|0x)?([a-f0-9]+)([IULZn]*)$/i }, + ]) { + const match = cleanedValue.match(regBase.regex); + if (match) { + if (match[1]) { + finalFromBase = regBase.base; + } + cleanedValue = match[2]; + break; + } + } + } + if (ignoreCase && finalFromBase <= 36) { + cleanedValue = cleanedValue.toLowerCase(); + } const range = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ+/'.split(''); - const fromRange = range.slice(0, fromBase); + const fromRange = range.slice(0, finalFromBase); const toRange = range.slice(0, toBase); - let decValue = value + let decValue = cleanedValue .split('') .reverse() - .reduce((carry: bigint, digit: string, index: number) => { + .reduce((carry: number, digit: string, index: number) => { if (!fromRange.includes(digit)) { - throw new Error(`Invalid digit "${digit}" for base ${fromBase}.`); + throw new Error(`Invalid digit "${digit}" for base ${finalFromBase}.`); } - return (carry += BigInt(fromRange.indexOf(digit)) * BigInt(fromBase) ** BigInt(index)); - }, 0n); + return (carry += fromRange.indexOf(digit) * finalFromBase ** index); + }, 0); let newValue = ''; while (decValue > 0) { - newValue = toRange[Number(decValue % BigInt(toBase))] + newValue; - decValue = (decValue - (decValue % BigInt(toBase))) / BigInt(toBase); + newValue = toRange[decValue % toBase] + newValue; + decValue = (decValue - (decValue % toBase)) / toBase; } return newValue || '0'; } diff --git a/src/tools/integers-to-ip/index.ts b/src/tools/integers-to-ip/index.ts new file mode 100644 index 00000000..2efce1a9 --- /dev/null +++ b/src/tools/integers-to-ip/index.ts @@ -0,0 +1,12 @@ +import { Binary } from '@vicons/tabler'; +import { defineTool } from '../tool'; + +export const tool = defineTool({ + name: 'Integers to IPv4/IPv6', + path: '/integers-to-ip', + description: 'Convert integers to IP', + keywords: ['integers', 'ip', 'ipv4', 'ipv6'], + component: () => import('./integers-to-ip.vue'), + icon: Binary, + createdAt: new Date('2024-07-14'), +}); diff --git a/src/tools/integers-to-ip/integers-to-ip.vue b/src/tools/integers-to-ip/integers-to-ip.vue new file mode 100644 index 00000000..5fc5c7d3 --- /dev/null +++ b/src/tools/integers-to-ip/integers-to-ip.vue @@ -0,0 +1,65 @@ + + + + +