Skip to content

Commit 3d63fde

Browse files
committed
feat(Chmod Calculator): implement symbol form input
Fix CorentinTh#418 and CorentinTh#522
1 parent ee04bfe commit 3d63fde

File tree

5 files changed

+195
-13
lines changed

5 files changed

+195
-13
lines changed

components.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ declare module '@vue/runtime-core' {
127127
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
128128
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
129129
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
130+
NCheckbox: typeof import('naive-ui')['NCheckbox']
130131
NCode: typeof import('naive-ui')['NCode']
131132
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
132133
NConfigProvider: typeof import('naive-ui')['NConfigProvider']
@@ -145,6 +146,7 @@ declare module '@vue/runtime-core' {
145146
NMenu: typeof import('naive-ui')['NMenu']
146147
NScrollbar: typeof import('naive-ui')['NScrollbar']
147148
NSpin: typeof import('naive-ui')['NSpin']
149+
NTable: typeof import('naive-ui')['NTable']
148150
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
149151
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
150152
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
@@ -159,6 +161,7 @@ declare module '@vue/runtime-core' {
159161
RouterLink: typeof import('vue-router')['RouterLink']
160162
RouterView: typeof import('vue-router')['RouterView']
161163
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
164+
SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default']
162165
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
163166
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
164167
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']

src/tools/chmod-calculator/chmod-calculator.service.test.ts

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, it } from 'vitest';
2-
import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation } from './chmod-calculator.service';
2+
import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation } from './chmod-calculator.service';
33

44
describe('chmod-calculator', () => {
55
describe('computeChmodOctalRepresentation', () => {
@@ -339,4 +339,119 @@ describe('chmod-calculator', () => {
339339
});
340340
});
341341
});
342+
describe('computePermissionsFromChmodSymbolicRepresentation', () => {
343+
it('throws on invalid symbolic values', () => {
344+
expect(() => computePermissionsFromChmodSymbolicRepresentation('rr---')).to.throw();
345+
expect(() => computePermissionsFromChmodSymbolicRepresentation('rwxrwx--w')).to.throw();
346+
});
347+
348+
it('get permissions from symbolic representation', () => {
349+
expect(
350+
computePermissionsFromChmodSymbolicRepresentation('dr-xr-xr-x'),
351+
).to.eql({
352+
owner: { read: true, write: false, execute: true },
353+
group: { read: true, write: false, execute: true },
354+
public: { read: true, write: false, execute: true },
355+
flags: { setuid: false, setgid: false, stickybit: false },
356+
});
357+
expect(
358+
computePermissionsFromChmodSymbolicRepresentation('-rw-r--r--'),
359+
).to.eql({
360+
owner: { read: true, write: true, execute: false },
361+
group: { read: true, write: false, execute: false },
362+
public: { read: true, write: false, execute: false },
363+
flags: { setuid: false, setgid: false, stickybit: false },
364+
});
365+
366+
expect(
367+
computePermissionsFromChmodSymbolicRepresentation('rwxrwxrwx'),
368+
).to.eql({
369+
owner: { read: true, write: true, execute: true },
370+
group: { read: true, write: true, execute: true },
371+
public: { read: true, write: true, execute: true },
372+
flags: { setuid: false, setgid: false, stickybit: false },
373+
});
374+
375+
expect(
376+
computePermissionsFromChmodSymbolicRepresentation('---------'),
377+
).to.eql({
378+
owner: { read: false, write: false, execute: false },
379+
group: { read: false, write: false, execute: false },
380+
public: { read: false, write: false, execute: false },
381+
flags: { setuid: false, setgid: false, stickybit: false },
382+
});
383+
384+
expect(
385+
computePermissionsFromChmodSymbolicRepresentation('r---wxr-x'),
386+
).to.eql({
387+
owner: { read: true, write: false, execute: false },
388+
group: { read: false, write: true, execute: true },
389+
public: { read: true, write: false, execute: true },
390+
flags: { setuid: false, setgid: false, stickybit: false },
391+
});
392+
393+
expect(
394+
computePermissionsFromChmodSymbolicRepresentation('r---w---x'),
395+
).to.eql({
396+
owner: { read: true, write: false, execute: false },
397+
group: { read: false, write: true, execute: false },
398+
public: { read: false, write: false, execute: true },
399+
flags: { setuid: false, setgid: false, stickybit: false },
400+
});
401+
402+
expect(
403+
computePermissionsFromChmodSymbolicRepresentation('--x-w-r--'),
404+
).to.eql({
405+
owner: { read: false, write: false, execute: true },
406+
group: { read: false, write: true, execute: false },
407+
public: { read: true, write: false, execute: false },
408+
flags: { setuid: false, setgid: false, stickybit: false },
409+
});
410+
411+
expect(
412+
computePermissionsFromChmodSymbolicRepresentation('-w--w--w-'),
413+
).to.eql({
414+
owner: { read: false, write: true, execute: false },
415+
group: { read: false, write: true, execute: false },
416+
public: { read: false, write: true, execute: false },
417+
flags: { setuid: false, setgid: false, stickybit: false },
418+
});
419+
420+
expect(
421+
computePermissionsFromChmodSymbolicRepresentation('-ws-ws-wt'),
422+
).to.eql({
423+
owner: { read: false, write: true, execute: true },
424+
group: { read: false, write: true, execute: true },
425+
public: { read: false, write: true, execute: true },
426+
flags: { setuid: true, setgid: true, stickybit: true },
427+
});
428+
429+
expect(
430+
computePermissionsFromChmodSymbolicRepresentation('-ws-w--w-'),
431+
).to.eql({
432+
owner: { read: false, write: true, execute: true },
433+
group: { read: false, write: true, execute: false },
434+
public: { read: false, write: true, execute: false },
435+
flags: { setuid: true, setgid: false, stickybit: false },
436+
});
437+
438+
expect(
439+
computePermissionsFromChmodSymbolicRepresentation('-w--ws-w-'),
440+
).to.eql({
441+
owner: { read: false, write: true, execute: false },
442+
group: { read: false, write: true, execute: true },
443+
public: { read: false, write: true, execute: false },
444+
flags: { setuid: false, setgid: true, stickybit: false },
445+
});
446+
447+
expect(
448+
computePermissionsFromChmodSymbolicRepresentation('-w--w--wt'),
449+
).to.eql({
450+
owner: { read: false, write: true, execute: false },
451+
group: { read: false, write: true, execute: false },
452+
public: { read: false, write: true, execute: true },
453+
flags: { setuid: false, setgid: false, stickybit: true },
454+
});
455+
});
456+
});
342457
});

src/tools/chmod-calculator/chmod-calculator.service.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import _ from 'lodash';
22
import type { GroupPermissions, Permissions, SpecialPermissions } from './chmod-calculator.types';
33

4-
export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation };
4+
export { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation };
55

66
function computeChmodOctalRepresentation({ permissions }: { permissions: Permissions }): string {
77
const permissionValue = { read: 4, write: 2, execute: 1 };
@@ -61,3 +61,28 @@ function computePermissionsFromChmodOctalRepresentation(octalPermissions: string
6161
flags: computePermissionObject(specialPermissionValue, flagsPosition),
6262
};
6363
}
64+
65+
function computePermissionsFromChmodSymbolicRepresentation(symbolicPermissions: string): Permissions {
66+
const formatRegex = /^[-dlbcsp]?([r-])([w-])([xs-])([r-])([w-])([xs-])([r-])([w-])([xt-])$/;
67+
if (!symbolicPermissions || !symbolicPermissions.match(formatRegex)) {
68+
throw new Error(`Invalid string permissions (must be in form 'rwxrwxrwx'): ${symbolicPermissions}`);
69+
}
70+
71+
const [_, rOwner, wOwner, xOwner, rGroup, wGroup, xGroup, rAll, wAll, xAll] = formatRegex.exec(symbolicPermissions) || [];
72+
const getOctal = (flag: string, flagLetter: string, flagValue: number) => flag === flagLetter ? flagValue : 0;
73+
const owner = getOctal(rOwner, 'r', 4)
74+
+ getOctal(wOwner, 'w', 2)
75+
+ getOctal(xOwner, 'x', 1) + getOctal(xOwner, 's', 1);
76+
const groups = getOctal(rGroup, 'r', 4)
77+
+ getOctal(wGroup, 'w', 2)
78+
+ getOctal(xGroup, 'x', 1) + getOctal(xGroup, 's', 1);
79+
const all = getOctal(rAll, 'r', 4)
80+
+ getOctal(wAll, 'w', 2)
81+
+ getOctal(xAll, 'x', 1) + getOctal(xAll, 't', 1);
82+
const flags = getOctal(xOwner, 's', 4)
83+
+ getOctal(xGroup, 's', 2)
84+
+ getOctal(xAll, 't', 1);
85+
const octalString = `${(flags > 0 ? flags : '')}${owner}${groups}${all}`;
86+
87+
return computePermissionsFromChmodOctalRepresentation(octalString);
88+
}

src/tools/chmod-calculator/chmod-calculator.vue

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { useThemeVars } from 'naive-ui';
33
44
import InputCopyable from '../../components/InputCopyable.vue';
5-
import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation } from './chmod-calculator.service';
5+
import { computeChmodOctalRepresentation, computeChmodSymbolicRepresentation, computePermissionsFromChmodOctalRepresentation, computePermissionsFromChmodSymbolicRepresentation } from './chmod-calculator.service';
66
77
import type { Group, Scope } from './chmod-calculator.types';
88
import { useValidation } from '@/composable/validation';
@@ -23,9 +23,9 @@ const permissions = ref({
2323
flags: { setuid: false, setgid: false, stickybit: false },
2424
});
2525
26-
const permissionsInput = ref('000');
27-
const permissionsInputValidation = useValidation({
28-
source: permissionsInput,
26+
const octalPermissionsInput = ref('000');
27+
const octalPermissionsInputValidation = useValidation({
28+
source: octalPermissionsInput,
2929
rules: [
3030
{
3131
message: 'Invalid octal permission string',
@@ -42,29 +42,69 @@ const permissionsInputValidation = useValidation({
4242
],
4343
});
4444
watch(
45-
permissionsInput,
45+
octalPermissionsInput,
4646
(newPermissions) => {
47-
if (!permissionsInputValidation.isValid) {
47+
if (!octalPermissionsInputValidation.isValid) {
4848
return;
4949
}
5050
permissions.value = computePermissionsFromChmodOctalRepresentation(newPermissions.trim());
5151
},
5252
);
5353
54+
const symbolicPermissionsInput = ref('---------');
55+
const symbolicPermissionsInputValidation = useValidation({
56+
source: symbolicPermissionsInput,
57+
rules: [
58+
{
59+
message: 'Invalid symbolic permission string',
60+
validator: (value) => {
61+
try {
62+
computePermissionsFromChmodSymbolicRepresentation(value.trim());
63+
return true;
64+
}
65+
catch {
66+
return false;
67+
}
68+
},
69+
},
70+
],
71+
});
72+
watch(
73+
symbolicPermissionsInput,
74+
(newPermissions) => {
75+
if (!symbolicPermissionsInputValidation.isValid) {
76+
return;
77+
}
78+
permissions.value = computePermissionsFromChmodSymbolicRepresentation(newPermissions.trim());
79+
},
80+
);
81+
5482
const octal = computed(() => computeChmodOctalRepresentation({ permissions: permissions.value }));
5583
const symbolic = computed(() => computeChmodSymbolicRepresentation({ permissions: permissions.value }));
5684
</script>
5785

5886
<template>
5987
<div>
6088
<c-input-text
61-
v-model:value="permissionsInput"
89+
v-model:value="octalPermissionsInput"
6290
placeholder="Put your octal permissions here..."
6391
label="Copy your octal permissions"
64-
:validation="permissionsInputValidation"
92+
:validation="octalPermissionsInputValidation"
93+
mb-2
94+
/>
95+
96+
<n-divider />
97+
98+
<c-input-text
99+
v-model:value="symbolicPermissionsInput"
100+
placeholder="Put your symbolic permissions here..."
101+
label="Copy your symbolic permissions"
102+
:validation="symbolicPermissionsInputValidation"
65103
mb-2
66104
/>
105+
67106
<n-divider />
107+
68108
<n-table :bordered="false" :bottom-bordered="false" single-column class="permission-table">
69109
<thead>
70110
<tr>

src/tools/chmod-calculator/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { FileInvoice } from '@vicons/tabler';
22
import { defineTool } from '../tool';
3-
import { translate } from '@/plugins/i18n.plugin';
43

54
export const tool = defineTool({
6-
name: translate('tools.chmod-calculator.title'),
5+
name: 'Chmod calculator',
76
path: '/chmod-calculator',
8-
description: translate('tools.chmod-calculator.description'),
7+
description: 'Compute your chmod permissions and commands with this online chmod calculator.',
98
keywords: [
109
'chmod',
1110
'calculator',

0 commit comments

Comments
 (0)