Merge e66702ec9f into 0de73e8971
				
					
				
			This commit is contained in:
		
						commit
						e0d0893fb4
					
				
							
								
								
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								components.d.ts
									
									
									
									
										vendored
									
									
								
							| @ -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'] | ||||
|  | ||||
| @ -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 | ||||
| 
 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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í | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -70,3 +70,8 @@ tools: | ||||
|     measurement: Вимірювання | ||||
|     text: Текст | ||||
|     data: Дані | ||||
|   cli-command-editor: | ||||
|     title: Редактор команд CLI | ||||
|     description: Перетворіть команди CLI з опціями у форму, яку легко редагувати, та згенеруйте результуючу команду з вхідними значеннями. | ||||
|     command: Команда | ||||
|     placeholder: Вставте команду сюди | ||||
|  | ||||
| @ -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 | ||||
|  | ||||
| @ -388,3 +388,9 @@ tools: | ||||
|   text-to-binary: | ||||
|     title: 文本到 ASCII 二进制 | ||||
|     description: 将文本转换为其 ASCII 二进制表示形式,反之亦然。 | ||||
| 
 | ||||
|   cli-command-editor: | ||||
|     title: CLI 命令编辑器 | ||||
|     description: 将带有选项的 CLI 命令转换为易于编辑的形式,并生成带有输入值的结果命令。 | ||||
|     command: 命令 | ||||
|     placeholder: 将命令粘贴到此处 | ||||
|  | ||||
							
								
								
									
										11
									
								
								src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/tools/cli-command-editor/cli-command-editor.e2e.spec.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								src/tools/cli-command-editor/cli-command-editor.service.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/tools/cli-command-editor/cli-command-editor.service.test.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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
									
								
								src/tools/cli-command-editor/cli-command-editor.service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/tools/cli-command-editor/cli-command-editor.service.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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; | ||||
| } | ||||
							
								
								
									
										51
									
								
								src/tools/cli-command-editor/cli-command-editor.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/tools/cli-command-editor/cli-command-editor.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,51 @@ | ||||
| <script setup lang="ts"> | ||||
| import * as service from './cli-command-editor.service'; | ||||
| 
 | ||||
| const inputCommand = ref(''); | ||||
| const options = computed(() => service.extractOptions(inputCommand.value)); | ||||
| const optionsObject = computed(() => service.buildOptionsObject(options.value)); | ||||
| const optionsInput = ref<{ [k: string]: string }>(optionsObject.value); | ||||
| const command = ref(''); | ||||
| </script> | ||||
| 
 | ||||
| <template> | ||||
|   <c-card> | ||||
|     <n-grid x-gap="12" y-gap="12" cols="1 600:3"> | ||||
|       <n-gi span="2"> | ||||
|         <c-input-text | ||||
|           v-model:value="inputCommand" | ||||
|           label-position="left" | ||||
|           label-width="130px" | ||||
|           :label="$t('tools.cli-command-editor.command')" | ||||
|           :aria-label="$t('tools.cli-command-editor.command')" | ||||
|           :placeholder="$t('tools.cli-command-editor.placeholder')" | ||||
|           :aria-placeholder="$t('tools.cli-command-editor.placeholder')" | ||||
|           raw-text | ||||
|           @update:value="() => { command = inputCommand }" | ||||
|         /> | ||||
|         <div v-for="option in options" :key="option" flex justify-center> | ||||
|           <c-input-text | ||||
|             v-model:value="optionsInput[option]" | ||||
|             label-position="left" | ||||
|             label-width="130px" | ||||
|             label-align="left" | ||||
|             :label="service.sanitizeOption(option)" | ||||
|             :aria-label="service.sanitizeOption(option)" | ||||
|             :placeholder="service.sanitizeOption(option)" | ||||
|             :aria-placeholder="service.sanitizeOption(option)" | ||||
|             mt-6 | ||||
|             @update:value="() => { command = service.buildEditedCommand(optionsInput, optionsObject, inputCommand) }" | ||||
|           /> | ||||
|         </div> | ||||
| 
 | ||||
|         <c-text-copyable | ||||
|           v-if="command" | ||||
|           :value="command" | ||||
|           font-mono | ||||
|           :show-icon="false" | ||||
|           mt-6 | ||||
|         /> | ||||
|       </n-gi> | ||||
|     </n-grid> | ||||
|   </c-card> | ||||
| </template> | ||||
							
								
								
									
										12
									
								
								src/tools/cli-command-editor/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/tools/cli-command-editor/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| import { Terminal2 } from '@vicons/tabler'; | ||||
| import { defineTool } from '../tool'; | ||||
| 
 | ||||
| export const tool = defineTool({ | ||||
|   name: 'CLI command editor', | ||||
|   path: '/cli-command-editor', | ||||
|   description: '', | ||||
|   keywords: ['cli', 'command', 'editor'], | ||||
|   component: () => import('./cli-command-editor.vue'), | ||||
|   icon: Terminal2, | ||||
|   createdAt: new Date('2025-06-21'), | ||||
| }); | ||||
| @ -1,6 +1,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 cliCommandEditor } from './cli-command-editor'; | ||||
| import { tool as emailNormalizer } from './email-normalizer'; | ||||
| 
 | ||||
| import { tool as asciiTextDrawer } from './ascii-text-drawer'; | ||||
| @ -160,6 +161,7 @@ export const toolsByCategory: ToolCategory[] = [ | ||||
|       emailNormalizer, | ||||
|       regexTester, | ||||
|       regexMemo, | ||||
|       cliCommandEditor, | ||||
|     ], | ||||
|   }, | ||||
|   { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user