Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions components.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ declare module '@vue/runtime-core' {
CKeyValueList: typeof import('./src/ui/c-key-value-list/c-key-value-list.vue')['default']
CKeyValueListItem: typeof import('./src/ui/c-key-value-list/c-key-value-list-item.vue')['default']
CLabel: typeof import('./src/ui/c-label/c-label.vue')['default']
CliCommandEditor: typeof import('./src/tools/cli-command-editor/cli-command-editor.vue')['default']
CLink: typeof import('./src/ui/c-link/c-link.vue')['default']
'CLink.demo': typeof import('./src/ui/c-link/c-link.demo.vue')['default']
CMarkdown: typeof import('./src/ui/c-markdown/c-markdown.vue')['default']
Expand Down
7 changes: 7 additions & 0 deletions locales/de.yml
Original file line number Diff line number Diff line change
Expand Up @@ -454,3 +454,10 @@ tools:
text-to-binary:
title: Text zu ASCII-Binär
description: Konvertiere Text in seine ASCII-Binärrepräsentation und umgekehrt.

cli-command-editor:
title: CLI-Befehlseditor
description: Wandeln Sie CLI-Befehle mit Optionen in eine leicht bearbeitbare Form um und generieren Sie den Befehl mit Eingabewerten.
command: Befehl
placeholder: Befehl hier einfügen

6 changes: 6 additions & 0 deletions locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,9 @@ tools:
text-to-binary:
title: Text to ASCII binary
description: Convert text to its ASCII binary representation and vice-versa.

cli-command-editor:
title: CLI command editor
description: Convert CLI commands with options into an easily editable form and generate the resulting command with input values.
command: Command
placeholder: Paste command here
5 changes: 5 additions & 0 deletions locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,8 @@ tools:
measurement: Measurement
text: Text
data: Data
cli-command-editor:
title: editor de comandos CLI
description: Convierta comandos CLI con opciones en un formato fácilmente editable y genere el comando resultante con valores de entrada.
command: Dominio
placeholder: Pegar comando aquí
5 changes: 5 additions & 0 deletions locales/fr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,3 +80,8 @@ tools:
copied: Le token a été copié
length: Longueur
tokenPlaceholder: Le token...
cli-command-editor:
title: Éditeur de commandes CLI
description: Convertissez les commandes CLI avec des options dans un format facilement modifiable et générez la commande résultante avec des valeurs d'entrée.
command: Commande
placeholder: Coller la commande ici
6 changes: 6 additions & 0 deletions locales/no.yml
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,9 @@ tools:
text-to-binary:
title: Tekst til ASCII binært
description: Konverter tekst til sin ASCII binære representasjon og visa-versa.

cli-command-editor:
title: CLI kommando editor
description: Konverter CLI-kommandoer med alternativer til et enkelt redigerbart format og generer den resulterende kommandoen med inndataverdier.
command: Kommando
placeholder: Lim inn kommando her
5 changes: 5 additions & 0 deletions locales/pt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,8 @@ tools:
measurement: 'Medidas'
text: 'Texto'
data: 'Dados'
cli-command-editor:
title: Editor de comando CLI
description: Converta comandos CLI com opções em um formato facilmente editável e gere o comando resultante com valores de entrada.
command: Comando
placeholder: Cole o comando aqui
5 changes: 5 additions & 0 deletions locales/uk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,8 @@ tools:
measurement: Вимірювання
text: Текст
data: Дані
cli-command-editor:
title: Редактор команд CLI
description: Перетворіть команди CLI з опціями у форму, яку легко редагувати, та згенеруйте результуючу команду з вхідними значеннями.
command: Команда
placeholder: Вставте команду сюди
6 changes: 6 additions & 0 deletions locales/vi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -381,3 +381,9 @@ tools:
text-to-binary:
title: Chuyển đổi văn bản thành nhị phân ASCII
description: Chuyển đổi văn bản thành biểu diễn nhị phân ASCII của nó và ngược lại.

cli-command-editor:
title: Trình soạn thảo lệnh CLI
description: Chuyển đổi các lệnh CLI có tùy chọn thành dạng dễ chỉnh sửa và tạo lệnh kết quả với các giá trị đầu vào.
command: Yêu cầu
placeholder: Dán lệnh vào đây
6 changes: 6 additions & 0 deletions locales/zh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -388,3 +388,9 @@ tools:
text-to-binary:
title: 文本到 ASCII 二进制
description: 将文本转换为其 ASCII 二进制表示形式,反之亦然。

cli-command-editor:
title: CLI 命令编辑器
description: 将带有选项的 CLI 命令转换为易于编辑的形式,并生成带有输入值的结果命令。
command: 命令
placeholder: 将命令粘贴到此处
11 changes: 11 additions & 0 deletions src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { expect, test } from '@playwright/test';

test.describe('Tool - Cli command editor', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/cli-command-editor');
});

test('Has correct title', async ({ page }) => {
await expect(page).toHaveTitle('Cli command editor - IT Tools');
});
});
140 changes: 140 additions & 0 deletions src/tools/cli-command-editor/cli-command-editor.service.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import { describe, expect, it } from 'vitest';
import { buildEditedCommand, buildOptionsObject, extractOptions, isOption, sanitizeOption } from './cli-command-editor.service';

describe('cli-command-editor', () => {
describe('extractOptions', () => {
it ('extracts all the options from a command', () => {
expect(
extractOptions('aws elb describe-load-balancers --load-balancer-name my-load-balancer')[0],
).toContain('--load-balancer-name');

expect(
extractOptions('aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy')[0],
).toContain('--load-balancer-name');

expect(
extractOptions('aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy')[1],
).toContain('--debug');

expect(
extractOptions('aws elb describe-load-balancers --load-balancer-name my-load-balancer --debug --query my-queryyy')[2],
).toContain('--query');
});

it('extracts all the option from a command with a mix of hyphen and double hyphens', () => {
expect(
extractOptions('npm i lodash -g --legacy-peer-deps')[0],
).toContain('-g');

expect(
extractOptions('npm i lodash -g --legacy-peer-deps')[1],
).toContain('--legacy-peer-deps');
});

it('shouldn\'t extract any options from a command without options', () => {
expect(
extractOptions('npm i lodash'),
).toEqual([]);
});

it('shouldn\'t return any options if command is not passed', () => {
expect(extractOptions()).toEqual([]);
});
});

describe('buildOptionsObject', () => {
it('returns a valid options object with the given options', () => {
expect(
buildOptionsObject(['--debug', '--load-balancer-names']),
).toEqual({
'--debug': '',
'--load-balancer-names': '',
});
});

it('returns an empty obnject with blank options array', () => {
expect(
buildOptionsObject([]),
).toEqual({});
});
});

describe('sanitizeOption', () => {
it('returns the sanitized option without `id` suffix', () => {
expect(sanitizeOption('--debug-id-1dfsj'))
.toEqual('--debug');
});

it('returns the blank string', () => {
expect(sanitizeOption('')).toEqual('');
});
});

describe('isOption', () => {
it('returns true for a valid double hyphen option token', () => {
expect(isOption('--debug')).toBe(true);
});

it('returns true for a valid single hyphen option token', () => {
expect(isOption('-i')).toBe(true);
});

it('returns false for an non-option token', () => {
expect(isOption('hello-world')).toBe(false);
});
});

describe('buildEditedCommand', () => {
it('returns the edited command', () => {
expect(
buildEditedCommand({
'--debug-id-1dfsj': 'stdin',
'-p': '',
'-m': 'nahhhh',
}, {
'--debug-id-1dfsj': 'stdin',
'-p': '',
'-m': 'nahhhh',
}, 'aws node --debug stdio -p -m okayyy'),
).toEqual('aws node --debug stdin -p -m nahhhh');

expect(
buildEditedCommand({
'-d-id-1dfsj': '',
'-p-id-fdsd': '4444:3333',
'-p-id-fddd': '3333:4444',
'--name-id-nnnn': 'clickhouse-server',
'--ulimit-id-uuuu': 'nofile=3333:4444',
}, {
'-d-id-1dfsj': '',
'-p-id-fdsd': '4444:3333',
'-p-id-fddd': '3333:4444',
'--name-id-nnnn': 'clickhouse-server',
'--ulimit-id-uuuu': 'nofile=3333:4444',
}, 'docker run -d -p 18123:8123 -p 19000:9000 --name some-clickhouse-server --ulimit nofile=262144:262144 clickhouse/clickhouse-server'),
).toEqual('docker run -d -p 4444:3333 -p 3333:4444 --name clickhouse-server --ulimit nofile=3333:4444 clickhouse/clickhouse-server');
});

it('returns the edited command when options object and CLI options order doesn\'t match', () => {
expect(
buildEditedCommand({
'-d-id-t1dd3': 'true',
'--install-id-only123': 'nodemon',
}, {
'--install-id-only123': 'nodem',
'-d-id-t1dd3': 'false',
}, 'npm --install nodem -d false'),
).toBe('npm --install nodemon -d true');
});

it('returns the original command', () => {
expect(
buildEditedCommand({}, {}, 'npm install nodemon'),
).toBe('npm install nodemon');

expect(
buildEditedCommand({}, {}, 'aws load-balancer describe-load-balancers all'),
).toBe('aws load-balancer describe-load-balancers all');
});
});
});
104 changes: 104 additions & 0 deletions src/tools/cli-command-editor/cli-command-editor.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { generateRandomId } from '@/utils/random';

export function isOption(token: string): boolean {
return token?.startsWith('--') || token?.startsWith('-');
}

export function extractOptions(command: string = ''): string[] {
/*
in a CLI, the options are either written with a hyphen or double hyphens, however,
script names or package/library sometimes include a hyphen, too, for example 'describe-load-balancers'
*/

// split into tokens first
const tokens = command.split(' ');

// map each token of the command to an option
const options = tokens.map((token: string) => {
// every option in a starts with either a hyphen or double hyphens
if (isOption(token)) {
const randomId = generateRandomId();
return `${token}-${randomId}`;
}

return '';
}).filter((option: string): boolean => !!option);
return options;
}

export function buildOptionsObject(options: string[]): Record<string, string> {
const optionsObject: Record<string, string> = {};

for (const option of options) {
optionsObject[option] = '';
}

return optionsObject;
}

export function sanitizeOption(option: string): string {
return option.split('-id')?.[0];
}

export function buildEditedCommand(options: Record<string, string>, originalOptions: Record<string, string>, command: string): string {
if (!Object.keys(options).length) {
return command;
}

const tokens = command.split(' ');
const editedTokens = [];

// user may input the option value in any order, from the form
// preserve the original object with options in the correct
// order as they appear in the original command, this is done
// to handle the interpolation of edited option values into the
// command
originalOptions = Object.entries(options)
.reduce((previousValue: Record<string, string>, currentValue: string[]) => {
previousValue[currentValue[0]] = currentValue[1];

return previousValue;
}, originalOptions);

const defaultValues: Record<string, string> = {};
// replacing the options and their values (if any) with formatter ($i) to
// help in interpolation of the command
for (let i = 0, j = 0, n = tokens.length; i < n; ++i) {
const token = tokens[i];
const nextToken = tokens[i + 1];

if (isOption(token)) {
editedTokens.push(`$${j}`);

if (!isOption(nextToken)) {
++i;
defaultValues[`$${j}`] = nextToken;
}

++j;
continue;
}

editedTokens.push(token);
}

let editedCommand = editedTokens.join(' ');

const originalOptionKeys = Object.keys(originalOptions);

for (let i = 0, n = originalOptionKeys.length; i < n; ++i) {
const key = originalOptionKeys[i];
const keyWithoutIdSuffix = key.split('-id-')[0] || key;

if (originalOptions[key]) {
editedCommand = editedCommand.replace(`$${i}`, `${keyWithoutIdSuffix} ${originalOptions[key]}`);
}
else {
const value = defaultValues[`$${i}`];
const replaceValue = value ? `${keyWithoutIdSuffix} ${value}` : keyWithoutIdSuffix;
editedCommand = editedCommand.replace(`$${i}`, replaceValue);
}
}

return editedCommand;
}
Loading
Loading