refactor: tools folder inside pages

This commit is contained in:
Ibrahima G. Coulibaly
2025-02-23 01:38:42 +01:00
parent 62f084eb45
commit 64936ab11f
117 changed files with 447 additions and 194 deletions

View File

@@ -0,0 +1,66 @@
import { describe, expect, it } from 'vitest';
import { duplicateList } from './service';
describe('duplicateList function', () => {
it('should duplicate elements correctly with symbol split', () => {
const input = 'Hello World';
const result = duplicateList('symbol', ' ', ' ', input, true, false, 2);
expect(result).toBe('Hello World Hello World');
});
it('should duplicate elements correctly with regex split', () => {
const input = 'Hello||World';
const result = duplicateList('regex', '\\|\\|', ' ', input, true, false, 2);
expect(result).toBe('Hello World Hello World');
});
it('should handle fractional duplication', () => {
const input = 'Hello World';
const result = duplicateList('symbol', ' ', ' ', input, true, false, 1.5);
expect(result).toBe('Hello World Hello');
});
it('should handle reverse option correctly', () => {
const input = 'Hello World';
const result = duplicateList('symbol', ' ', ' ', input, true, true, 2);
expect(result).toBe('Hello World World Hello');
});
it('should handle concatenate option correctly', () => {
const input = 'Hello World';
const result = duplicateList('symbol', ' ', ' ', input, false, false, 2);
expect(result).toBe('Hello Hello World World');
});
it('should handle interweaving option correctly', () => {
const input = 'Hello World';
const result = duplicateList('symbol', ' ', ' ', input, false, false, 2);
expect(result).toBe('Hello Hello World World');
});
it('should throw an error for negative copies', () => {
expect(() =>
duplicateList('symbol', ' ', ' ', 'Hello World', true, false, -1)
).toThrow('Number of copies cannot be negative');
});
it('should handle interweaving option correctly 2', () => {
const input = "je m'appelle king";
const result = duplicateList('symbol', ' ', ', ', input, false, true, 2.1);
expect(result).toBe("je, king, m'appelle, m'appelle, king, je");
});
it('should handle interweaving option correctly 3', () => {
const input = "je m'appelle king";
const result = duplicateList('symbol', ' ', ', ', input, false, true, 1);
expect(result).toBe("je, m'appelle, king");
});
it('should handle interweaving option correctly 3', () => {
const input = "je m'appelle king";
const result = duplicateList('symbol', ' ', ', ', input, true, true, 2.7);
expect(result).toBe(
"je, m'appelle, king, king, m'appelle, je, king, m'appelle"
);
});
});

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function Duplicate() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Duplicate',
path: 'duplicate',
// image,
description: '',
shortDescription: '',
keywords: ['duplicate'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,81 @@
export type SplitOperatorType = 'symbol' | 'regex';
function interweave(array1: string[], array2: string[]) {
const result: string[] = [];
const maxLength = Math.max(array1.length, array2.length);
for (let i = 0; i < maxLength; i++) {
if (i < array1.length) result.push(array1[i]);
if (i < array2.length) result.push(array2[i]);
}
return result;
}
function duplicate(
input: string[],
concatenate: boolean,
reverse: boolean,
copy?: number
) {
if (copy) {
if (copy > 0) {
let result: string[] = [];
let toAdd: string[] = [];
let WholePart: string[] = [];
let fractionalPart: string[] = [];
const whole = Math.floor(copy);
const fractional = copy - whole;
if (!reverse) {
WholePart = concatenate
? Array(whole).fill(input).flat()
: Array(whole - 1)
.fill(input)
.flat();
fractionalPart = input.slice(0, Math.floor(input.length * fractional));
toAdd = WholePart.concat(fractionalPart);
result = concatenate
? WholePart.concat(fractionalPart)
: interweave(input, toAdd);
} else {
WholePart = Array(whole - 1)
.fill(input)
.flat()
.reverse();
fractionalPart = input
.slice()
.reverse()
.slice(0, Math.floor(input.length * fractional));
toAdd = WholePart.concat(fractionalPart);
result = concatenate ? input.concat(toAdd) : interweave(input, toAdd);
}
return result;
}
throw new Error('Number of copies cannot be negative');
}
throw new Error('Number of copies must be a valid number');
}
export function duplicateList(
splitOperatorType: SplitOperatorType,
splitSeparator: string,
joinSeparator: string,
input: string,
concatenate: boolean,
reverse: boolean,
copy?: number
): string {
let array: string[];
let result: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input
.split(new RegExp(splitSeparator))
.filter((item) => item !== '');
break;
}
result = duplicate(array, concatenate, reverse, copy);
return result.join(joinSeparator);
}

View File

@@ -0,0 +1,100 @@
import { describe, expect, it } from 'vitest';
import { TopItemsList } from './service';
describe('TopItemsList function', () => {
it('should handle sorting alphabetically ignoring case', () => {
const input = 'Apple,banana,apple,Orange,Banana,apple';
const result = TopItemsList(
'symbol',
'alphabetic',
'count',
',',
input,
false,
true,
false
);
expect(result).toEqual('apple: 3\n' + 'banana: 2\n' + 'orange: 1');
});
it('should handle sorting by count and not ignoring case', () => {
const input = 'apple,banana,apple,orange,banana,apple,Banana';
const result = TopItemsList(
'symbol',
'count',
'count',
',',
input,
false,
false,
false
);
expect(result).toEqual(
'apple: 3\n' + 'banana: 2\n' + 'orange: 1\n' + 'Banana: 1'
);
});
it('should handle regex split operator', () => {
const input = 'apple123banana456apple789orange012banana345apple678';
const result = TopItemsList(
'regex',
'count',
'count',
'\\d+',
input,
false,
false,
false
);
expect(result).toEqual('apple: 3\n' + 'banana: 2\n' + 'orange: 1');
});
it('should handle percentage display format', () => {
const input = 'apple,banana,apple,orange,banana,apple';
const result = TopItemsList(
'symbol',
'count',
'percentage',
',',
input,
false,
false,
false
);
expect(result).toEqual(
'apple: 3 (50.00%)\n' + 'banana: 2 (33.33%)\n' + 'orange: 1 (16.67%)'
);
});
it('should handle total display format', () => {
const input = 'apple,banana,apple,orange,banana,apple';
const result = TopItemsList(
'symbol',
'count',
'total',
',',
input,
false,
false,
false
);
expect(result).toEqual(
'apple: 3 (3 / 6)\n' + 'banana: 2 (2 / 6)\n' + 'orange: 1 (1 / 6)'
);
});
it('should handle trimming and ignoring empty items', () => {
const input = ' apple , banana , apple , orange , banana , apple ';
const result = TopItemsList(
'symbol',
'count',
'count',
',',
input,
true,
false,
true
);
expect(result).toEqual('apple: 3\n' + 'banana: 2\n' + 'orange: 1');
});
});

View File

@@ -0,0 +1,167 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import {
DisplayFormat,
SortingMethod,
SplitOperatorType,
TopItemsList
} from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import SelectWithDesc from '@components/options/SelectWithDesc';
const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType,
sortingMethod: 'alphabetic' as SortingMethod,
displayFormat: 'count' as DisplayFormat,
splitSeparator: ',',
deleteEmptyItems: false,
ignoreItemCase: false,
trimItems: false
};
const splitOperators: {
title: string;
description: string;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description: 'Delimit input list items with a character.',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
type: 'regex',
description: 'Delimit input list items with a regular expression.'
}
];
export default function FindMostPopular() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const {
splitSeparatorType,
splitSeparator,
displayFormat,
sortingMethod,
deleteEmptyItems,
ignoreItemCase,
trimItems
} = optionsValues;
setResult(
TopItemsList(
splitSeparatorType,
sortingMethod,
displayFormat,
splitSeparator,
input,
deleteEmptyItems,
ignoreItemCase,
trimItems
)
);
};
return (
<Box>
<ToolInputAndResult
input={
<ToolTextInput
title={'Input list'}
value={input}
onChange={setInput}
/>
}
result={<ToolTextResult title={'Most popular items'} value={result} />}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'How to Extract List Items?',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitSeparatorType', type)}
title={title}
description={description}
checked={values.splitSeparatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Item comparison',
component: (
<Box>
<CheckboxWithDesc
title={'Remove empty items'}
description={'Ignore empty items from comparison.'}
checked={values.deleteEmptyItems}
onChange={(value) => updateField('deleteEmptyItems', value)}
/>
<CheckboxWithDesc
title={'Trim top list items'}
description={
'Remove leading and trailing spaces before comparing items'
}
checked={values.trimItems}
onChange={(value) => updateField('trimItems', value)}
/>
<CheckboxWithDesc
title={'Ignore Item Case'}
description={'Compare all list items in lowercase.'}
checked={values.ignoreItemCase}
onChange={(value) => updateField('ignoreItemCase', value)}
/>
</Box>
)
},
{
title: 'Top item output format',
component: (
<Box>
<SelectWithDesc
selected={values.displayFormat}
options={[
{ label: 'Show item percentage', value: 'percentage' },
{ label: 'Show item count', value: 'count' },
{ label: 'Show item total', value: 'total' }
]}
onChange={(value) => updateField('displayFormat', value)}
description={'How to display the most popular list items?'}
/>
<SelectWithDesc
selected={values.sortingMethod}
options={[
{ label: 'Sort Alphabetically', value: 'alphabetic' },
{ label: 'Sort by count', value: 'count' }
]}
onChange={(value) => updateField('sortingMethod', value)}
description={'Select a sorting method.'}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Find most popular',
path: 'find-most-popular',
// image,
description: '',
shortDescription: '',
keywords: ['find', 'most', 'popular'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,114 @@
export type SplitOperatorType = 'symbol' | 'regex';
export type DisplayFormat = 'count' | 'percentage' | 'total';
export type SortingMethod = 'count' | 'alphabetic';
// Function that takes the array as arg and returns a dict of element occurrences and handle the ignoreItemCase
function dictMaker(
array: string[],
ignoreItemCase: boolean
): { [key: string]: number } {
const dict: { [key: string]: number } = {};
for (const item of array) {
const key = ignoreItemCase ? item.toLowerCase() : item;
dict[key] = (dict[key] || 0) + 1;
}
return dict;
}
// Function that sorts the dict created with dictMaker based on the chosen sorting method
function dictSorter(
dict: { [key: string]: number },
sortingMethod: SortingMethod
): { [key: string]: number } {
let sortedArray: [string, number][];
switch (sortingMethod) {
case 'count':
sortedArray = Object.entries(dict).sort(
([, countA], [, countB]) => countB - countA
);
break;
case 'alphabetic':
sortedArray = Object.entries(dict).sort(([keyA], [keyB]) => {
return keyA.localeCompare(keyB);
});
break;
default:
sortedArray = Object.entries(dict);
break;
}
return Object.fromEntries(sortedArray);
}
// Function that prepares the output of dictSorter based on the chosen display format
function displayFormater(
dict: { [key: string]: number },
displayFormat: DisplayFormat
): string[] {
const formattedOutput: string[] = [];
const total = Object.values(dict).reduce((acc, val) => acc + val, 0);
switch (displayFormat) {
case 'percentage':
Object.entries(dict).forEach(([key, value]) => {
formattedOutput.push(
`${key}: ${value} (${((value / total) * 100).toFixed(2)}%)`
);
});
break;
case 'total':
Object.entries(dict).forEach(([key, value]) => {
formattedOutput.push(`${key}: ${value} (${value} / ${total})`);
});
break;
case 'count':
Object.entries(dict).forEach(([key, value]) => {
formattedOutput.push(`${key}: ${value}`);
});
break;
}
return formattedOutput;
}
export function TopItemsList(
splitOperatorType: SplitOperatorType,
sortingMethod: SortingMethod,
displayFormat: DisplayFormat,
splitSeparator: string,
input: string,
deleteEmptyItems: boolean,
ignoreItemCase: boolean,
trimItems: boolean
): string {
let array: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input
.split(new RegExp(splitSeparator))
.filter((item) => item !== '');
break;
}
// Trim items if required
if (trimItems) {
array = array.map((item) => item.trim());
}
// Delete empty items after initial split
if (deleteEmptyItems) {
array = array.filter((item) => item !== '');
}
// Transform the array into dict
const unsortedDict = dictMaker(array, ignoreItemCase);
// Sort the list if required
const sortedDict = dictSorter(unsortedDict, sortingMethod);
// Format the output with desired format
const formattedOutput = displayFormater(sortedDict, displayFormat);
return formattedOutput.join('\n');
}

View File

@@ -0,0 +1,110 @@
import { describe, expect } from 'vitest';
import { findUniqueCompute } from './service';
describe('TopItemsList Function', () => {
test('should return unique items ignoring case sensitivity', () => {
const input = 'apple,banana,Apple,orange,Banana,apple';
const result = findUniqueCompute(
'symbol',
',',
'\n',
input,
true,
true,
false,
true
);
expect(result).toBe('orange');
});
test('should return unique items considering case sensitivity', () => {
const input = 'apple,banana,Apple,orange,Banana,apple';
const result = findUniqueCompute(
'symbol',
',',
'\n',
input,
true,
true,
true,
true
);
expect(result).toBe('banana\nApple\norange\nBanana');
});
test('should return all unique items ignoring case sensitivity', () => {
const input = 'apple,banana,Apple,orange,Banana,apple';
const result = findUniqueCompute(
'symbol',
',',
'\n',
input,
true,
true,
false,
false
);
expect(result).toBe('apple\nbanana\norange');
});
test('should return all unique items considering case sensitivity', () => {
const input = 'apple,banana,Apple,orange,Banana,apple';
const result = findUniqueCompute(
'symbol',
',',
'\n',
input,
true,
true,
true,
false
);
expect(result).toBe('apple\nbanana\nApple\norange\nBanana');
});
test('should handle empty items deletion', () => {
const input = 'apple,,banana, ,orange';
const result = findUniqueCompute(
'symbol',
',',
'\n',
input,
true,
true,
false,
false
);
expect(result).toBe('apple\nbanana\norange');
});
test('should handle trimming items', () => {
const input = ' apple , banana , orange ';
const result = findUniqueCompute(
'symbol',
',',
'\n',
input,
false,
false,
false,
false
);
expect(result).toBe(' apple \n banana \n orange ');
});
test('should handle regex split', () => {
const input = 'apple banana orange';
const result = findUniqueCompute(
'regex',
'\\s+',
'\n',
input,
false,
false,
false,
false
);
expect(result).toBe('apple\nbanana\norange');
});
});

View File

@@ -0,0 +1,158 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { findUniqueCompute, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
const initialValues = {
splitOperatorType: 'symbol' as SplitOperatorType,
splitSeparator: ',',
joinSeparator: '\\n',
deleteEmptyItems: true,
caseSensitive: false,
trimItems: true,
absolutelyUnique: false
};
const splitOperators: {
title: string;
description: string;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description: 'Delimit input list items with a character.',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
type: 'regex',
description: 'Delimit input list items with a regular expression.'
}
];
export default function FindUnique() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const {
splitOperatorType,
splitSeparator,
joinSeparator,
deleteEmptyItems,
trimItems,
caseSensitive,
absolutelyUnique
} = optionsValues;
setResult(
findUniqueCompute(
splitOperatorType,
splitSeparator,
joinSeparator,
input,
deleteEmptyItems,
trimItems,
caseSensitive,
absolutelyUnique
)
);
};
return (
<Box>
<ToolInputAndResult
input={
<ToolTextInput
title={'Input list'}
value={input}
onChange={setInput}
/>
}
result={<ToolTextResult title={'Unique items'} value={result} />}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'Input List Delimiter',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Output List Delimiter',
component: (
<Box>
<TextFieldWithDesc
value={values.joinSeparator}
onOwnChange={(value) => updateField('joinSeparator', value)}
/>
<CheckboxWithDesc
title={'Trim top list items'}
description={
'Remove leading and trailing spaces before comparing items'
}
checked={values.trimItems}
onChange={(value) => updateField('trimItems', value)}
/>
<CheckboxWithDesc
title={'Skip empty items'}
description={
"Don't include the empty list items in the output."
}
checked={values.deleteEmptyItems}
onChange={(value) => updateField('deleteEmptyItems', value)}
/>
</Box>
)
},
{
title: 'Unique Item Options',
component: (
<Box>
<CheckboxWithDesc
title={'Find Absolutely Unique Items'}
description={
'Display only those items of the list that exist in a single copy.'
}
checked={values.absolutelyUnique}
onChange={(value) => updateField('absolutelyUnique', value)}
/>
<CheckboxWithDesc
title={'Case Sensitive Items'}
description={
'Output items with different case as unique elements in the list.'
}
checked={values.caseSensitive}
onChange={(value) => updateField('caseSensitive', value)}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Find unique',
path: 'find-unique',
// image,
description: '',
shortDescription: '',
keywords: ['find', 'unique'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,64 @@
export type SplitOperatorType = 'symbol' | 'regex';
// Function that builds the unique items array handling caseSensitive and absolutelyUnique options
function uniqueListBuilder(
array: string[],
caseSensitive: boolean,
absolutelyUnique: boolean
): string[] {
const dict: { [key: string]: number } = {};
for (const item of array) {
const key = caseSensitive ? item : item.toLowerCase();
dict[key] = (dict[key] || 0) + 1;
}
if (absolutelyUnique) {
for (const [key, value] of Object.entries(dict)) {
if (value > 1) {
delete dict[key];
}
}
}
return Object.keys(dict);
}
export function findUniqueCompute(
splitOperatorType: SplitOperatorType,
splitSeparator: string,
joinSeparator: string = '\n',
input: string,
deleteEmptyItems: boolean,
trimItems: boolean,
caseSensitive: boolean,
absolutelyUnique: boolean
): string {
let array: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input
.split(new RegExp(splitSeparator))
.filter((item) => item !== '');
break;
}
// Trim items if required
if (trimItems) {
array = array.map((item) => item.trim());
}
// Delete empty items after initial split
if (deleteEmptyItems) {
array = array.filter((item) => item !== '');
}
// Format the output with desired format
const uniqueListItems = uniqueListBuilder(
array,
caseSensitive,
absolutelyUnique
);
return uniqueListItems.join(joinSeparator);
}

View File

@@ -0,0 +1,99 @@
import { describe, expect, it } from 'vitest';
import { groupList, SplitOperatorType } from './service';
describe('groupList', () => {
it('splits by symbol, groups, pads, and formats correctly', () => {
const input = 'a,b,c,d,e,f,g,h,i,j';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ',';
const groupNumber = 3;
const itemSeparator = '-';
const leftWrap = '[';
const rightWrap = ']';
const groupSeparator = ' | ';
const deleteEmptyItems = false;
const padNonFullGroup = true;
const paddingChar = 'x';
const expectedOutput = '[a-b-c] | [d-e-f] | [g-h-i] | [j-x-x]';
const result = groupList(
splitOperatorType,
splitSeparator,
input,
groupNumber,
itemSeparator,
leftWrap,
rightWrap,
groupSeparator,
deleteEmptyItems,
padNonFullGroup,
paddingChar
);
expect(result).toBe(expectedOutput);
});
it('handles regex split, no padding, and formats correctly', () => {
const input = 'a1b2c3d4e5f6g7h8i9j';
const splitOperatorType: SplitOperatorType = 'regex';
const splitSeparator = '\\d';
const groupNumber = 4;
const itemSeparator = ',';
const leftWrap = '(';
const rightWrap = ')';
const groupSeparator = ' / ';
const deleteEmptyItems = true;
const padNonFullGroup = false;
const expectedOutput = '(a,b,c,d) / (e,f,g,h) / (i,j)';
const result = groupList(
splitOperatorType,
splitSeparator,
input,
groupNumber,
itemSeparator,
leftWrap,
rightWrap,
groupSeparator,
deleteEmptyItems,
padNonFullGroup
);
expect(result).toBe(expectedOutput);
});
it('handles empty items removal and padd the last group with a z', () => {
const input = 'a,,b,,c,,d,,e,,';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ',';
const groupNumber = 2;
const itemSeparator = ':';
const leftWrap = '<';
const rightWrap = '>';
const groupSeparator = ' & ';
const deleteEmptyItems = true;
const padNonFullGroup = true;
const paddingChar = 'z';
const expectedOutput = '<a:b> & <c:d> & <e:z>';
const result = groupList(
splitOperatorType,
splitSeparator,
input,
groupNumber,
itemSeparator,
leftWrap,
rightWrap,
groupSeparator,
deleteEmptyItems,
padNonFullGroup,
paddingChar
);
expect(result).toBe(expectedOutput);
});
});

View File

@@ -0,0 +1,183 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { groupList, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import { formatNumber } from '../../../../utils/number';
const initialValues = {
splitOperatorType: 'symbol' as SplitOperatorType,
splitSeparator: ',',
groupNumber: 2,
itemSeparator: ',',
leftWrap: '[',
rightWrap: ']',
groupSeparator: '\\n',
deleteEmptyItems: true,
padNonFullGroup: false,
paddingChar: '...'
};
const splitOperators: {
title: string;
description: string;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description: 'Delimit input list items with a character.',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
type: 'regex',
description: 'Delimit input list items with a regular expression.'
}
];
export default function FindUnique() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const {
splitOperatorType,
splitSeparator,
groupNumber,
itemSeparator,
leftWrap,
rightWrap,
groupSeparator,
deleteEmptyItems,
padNonFullGroup,
paddingChar
} = optionsValues;
setResult(
groupList(
splitOperatorType,
splitSeparator,
input,
groupNumber,
itemSeparator,
leftWrap,
rightWrap,
groupSeparator,
deleteEmptyItems,
padNonFullGroup,
paddingChar
)
);
};
return (
<Box>
<ToolInputAndResult
input={
<ToolTextInput
title={'Input list'}
value={input}
onChange={setInput}
/>
}
result={<ToolTextResult title={'Grouped items'} value={result} />}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'Input Item Separator',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Group Size and Separators',
component: (
<Box>
<TextFieldWithDesc
value={values.groupNumber}
description={'Number of items in a group'}
type={'number'}
onOwnChange={(value) =>
updateField('groupNumber', formatNumber(value, 1))
}
/>
<TextFieldWithDesc
value={values.itemSeparator}
description={'Item separator character'}
onOwnChange={(value) => updateField('itemSeparator', value)}
/>
<TextFieldWithDesc
value={values.groupSeparator}
description={'Group separator character'}
onOwnChange={(value) => updateField('groupSeparator', value)}
/>
<TextFieldWithDesc
value={values.leftWrap}
description={"Group's left wrap symbol."}
onOwnChange={(value) => updateField('leftWrap', value)}
/>
<TextFieldWithDesc
value={values.rightWrap}
description={"Group's right wrap symbol."}
onOwnChange={(value) => updateField('rightWrap', value)}
/>
</Box>
)
},
{
title: 'Empty Items and Padding',
component: (
<Box>
<CheckboxWithDesc
title={'Delete Empty Items'}
description={
"Ignore empty items and don't include them in the groups."
}
checked={values.deleteEmptyItems}
onChange={(value) => updateField('deleteEmptyItems', value)}
/>
<CheckboxWithDesc
title={'Pad Non-full Groups'}
description={
'Fill non-full groups with a custom item (enter below).'
}
checked={values.padNonFullGroup}
onChange={(value) => updateField('padNonFullGroup', value)}
/>
<TextFieldWithDesc
value={values.paddingChar}
description={
'Use this character or item to pad non-full groups.'
}
onOwnChange={(value) => updateField('paddingChar', value)}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Group',
path: 'group',
// image,
description: '',
shortDescription: '',
keywords: ['group'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,94 @@
export type SplitOperatorType = 'symbol' | 'regex';
// function that split the array into an array of subarray of desired length
function groupMaker(array: string[], groupNumber: number): string[][] {
const result: string[][] = [];
for (let i = 0; i < array.length; i += groupNumber) {
result.push(array.slice(i, i + groupNumber));
}
return result;
}
// function use to handle the case paddingNonFullGroup is enable
function groupFiller(
array: string[][],
groupNumber: number,
padNonFullGroup: boolean,
paddingChar: string = ''
): string[][] {
if (padNonFullGroup) {
const lastSubArray: string[] = array[array.length - 1];
if (lastSubArray.length < groupNumber) {
for (let i = lastSubArray.length; i < groupNumber; i++) {
lastSubArray.push(paddingChar);
}
}
array[array.length - 1] = lastSubArray;
}
return array;
}
// function that join with the item separator and wrap with left and right each subArray of the Array
function groupJoinerAndWrapper(
array: string[][],
itemSeparator: string = '',
leftWrap: string = '',
rightWrap: string = ''
): string[] {
return array.map((subArray) => {
return leftWrap + subArray.join(itemSeparator) + rightWrap;
});
}
export function groupList(
splitOperatorType: SplitOperatorType,
splitSeparator: string,
input: string,
groupNumber: number,
itemSeparator: string = '',
leftWrap: string = '',
rightWrap: string = '',
groupSeparator: string,
deleteEmptyItems: boolean,
padNonFullGroup: boolean,
paddingChar: string = ''
): string {
let array: string[];
let splitedArray: string[][];
let fullSplitedArray: string[][];
let result: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input.split(new RegExp(splitSeparator));
break;
}
// delete empty items after intial split
if (deleteEmptyItems) {
array = array.filter((item) => item !== '');
}
// split the input into an array of subArray with the desired length
splitedArray = groupMaker(array, groupNumber);
// fill the last subArray is PadNonFullGroup is enabled
fullSplitedArray = groupFiller(
splitedArray,
groupNumber,
padNonFullGroup,
paddingChar
);
// get the list of formated subArray with the item separator and left and right wrapper
result = groupJoinerAndWrapper(
fullSplitedArray,
itemSeparator,
leftWrap,
rightWrap
);
// finnaly join the group separator before returning
return result.join(groupSeparator);
}

View File

@@ -0,0 +1,25 @@
import { tool as listDuplicate } from './duplicate/meta';
import { tool as listUnwrap } from './unwrap/meta';
import { tool as listReverse } from './reverse/meta';
import { tool as listFindUnique } from './find-unique/meta';
import { tool as listFindMostPopular } from './find-most-popular/meta';
import { tool as listGroup } from './group/meta';
import { tool as listWrap } from './wrap/meta';
import { tool as listRotate } from './rotate/meta';
import { tool as listTruncate } from './truncate/meta';
import { tool as listShuffle } from './shuffle/meta';
import { tool as listSort } from './sort/meta';
export const listTools = [
listSort,
listUnwrap,
listReverse,
listFindUnique,
listFindMostPopular,
listGroup,
listWrap,
listRotate,
listShuffle,
listTruncate,
listDuplicate
];

View File

@@ -0,0 +1,105 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { reverseList, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
const initialValues = {
splitOperatorType: 'symbol' as SplitOperatorType,
splitSeparator: ',',
joinSeparator: '\\n'
};
const splitOperators: {
title: string;
description: string;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description: 'Delimit input list items with a character.',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
type: 'regex',
description: 'Delimit input list items with a regular expression.'
}
];
export default function Reverse() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const { splitOperatorType, splitSeparator, joinSeparator } = optionsValues;
setResult(
reverseList(splitOperatorType, splitSeparator, joinSeparator, input)
);
};
return (
<Box>
<ToolInputAndResult
input={
<ToolTextInput
title={'Input list'}
value={input}
onChange={setInput}
/>
}
result={<ToolTextResult title={'Reversed list'} value={result} />}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'Splitter Mode',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
</Box>
)
},
{
title: 'Item Separator',
component: (
<Box>
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Output List Options',
component: (
<Box>
<TextFieldWithDesc
description={'Output list item separator.'}
value={values.joinSeparator}
onOwnChange={(val) => updateField('joinSeparator', val)}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Reverse',
path: 'reverse',
// image,
description: '',
shortDescription: '',
keywords: ['reverse'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,28 @@
import { describe, expect } from 'vitest';
import { reverseList } from './service';
describe('reverseList Function', () => {
test('should reverse items split by symbol', () => {
const input = 'apple,banana,orange';
const result = reverseList('symbol', ',', '\n', input);
expect(result).toBe('orange\nbanana\napple');
});
test('should reverse items split by regex', () => {
const input = 'apple banana orange';
const result = reverseList('regex', '\\s+', '\n', input);
expect(result).toBe('orange\nbanana\napple');
});
test('should handle empty input', () => {
const input = '';
const result = reverseList('symbol', ',', '\n', input);
expect(result).toBe('');
});
test('should handle join separator', () => {
const input = 'apple,banana,orange';
const result = reverseList('symbol', ',', ', ', input);
expect(result).toBe('orange, banana, apple');
});
});

View File

@@ -0,0 +1,23 @@
export type SplitOperatorType = 'symbol' | 'regex';
export function reverseList(
splitOperatorType: SplitOperatorType,
splitSeparator: string,
joinSeparator: string = '\n',
input: string
): string {
let array: string[] = [];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input
.split(new RegExp(splitSeparator))
.filter((item) => item !== '');
break;
}
const reversedList = array.reverse();
return reversedList.join(joinSeparator);
}

View File

@@ -0,0 +1,153 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { rotateList, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { formatNumber } from '../../../../utils/number';
const initialValues = {
splitOperatorType: 'symbol' as SplitOperatorType,
input: '',
splitSeparator: ',',
joinSeparator: ',',
right: true,
step: 1
};
const splitOperators: {
title: string;
description: string;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description: 'Delimit input list items with a character.',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
type: 'regex',
description: 'Delimit input list items with a regular expression.'
}
];
const rotationDirections: {
title: string;
description: string;
value: boolean;
}[] = [
{
title: 'Rotate forward',
description:
'Rotate list items to the right. (Down if a vertical column list.)',
value: true
},
{
title: 'Rotate backward',
description:
'Rotate list items to the left. (Up if a vertical column list.)',
value: false
}
];
export default function Rotate() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const { splitOperatorType, splitSeparator, joinSeparator, right, step } =
optionsValues;
setResult(
rotateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
right,
step
)
);
};
return (
<Box>
<ToolInputAndResult
input={
<ToolTextInput
title={'Input list'}
value={input}
onChange={setInput}
/>
}
result={<ToolTextResult title={'Rotated list'} value={result} />}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'Item split mode',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Rotation Direction and Count',
component: (
<Box>
{rotationDirections.map(({ title, description, value }) => (
<SimpleRadio
key={`${value}`}
onClick={() => updateField('right', value)}
title={title}
description={description}
checked={values.right === value}
/>
))}
<TextFieldWithDesc
description={'Number of items to rotate'}
value={values.step}
onOwnChange={(val) =>
updateField('step', formatNumber(val, 1))
}
/>
</Box>
)
},
{
title: 'Rotated List Joining Symbol',
component: (
<Box>
<TextFieldWithDesc
value={values.joinSeparator}
onOwnChange={(value) => updateField('joinSeparator', value)}
description={
'Enter the character that goes between items in the rotated list.'
}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Rotate',
path: 'rotate',
// image,
description: '',
shortDescription: '',
keywords: ['rotate'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,105 @@
import { describe, expect, it } from 'vitest';
import { rotateList, SplitOperatorType } from './service';
describe('rotate function', () => {
it('should rotate right side if right is set to true', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const step = 1;
const right = true;
const result = rotateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
right,
step
);
expect(result).toBe('mango apple pineaple lemon orange');
});
it('should rotate left side if right is set to true', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const step = 1;
const right = false;
const result = rotateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
right,
step
);
expect(result).toBe('pineaple lemon orange mango apple');
});
it('should rotate left side with 2 step if right is set to true', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const step = 2;
const right = false;
const result = rotateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
right,
step
);
expect(result).toBe('lemon orange mango apple pineaple');
});
it('should raise an error if step is negative', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const step = -2;
const right = false;
expect(() => {
rotateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
right,
step
);
}).toThrowError('Rotation step must be greater than zero.');
});
it('should raise an error if step is undefined', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const right = false;
expect(() => {
rotateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
right
);
}).toThrowError('Rotation step contains non-digits.');
});
});

View File

@@ -0,0 +1,48 @@
export type SplitOperatorType = 'symbol' | 'regex';
function rotateArray(array: string[], step: number, right: boolean): string[] {
const length = array.length;
// Normalize the step to be within the bounds of the array length
const normalizedPositions = ((step % length) + length) % length;
if (right) {
// Rotate right
return array
.slice(-normalizedPositions)
.concat(array.slice(0, -normalizedPositions));
} else {
// Rotate left
return array
.slice(normalizedPositions)
.concat(array.slice(0, normalizedPositions));
}
}
export function rotateList(
splitOperatorType: SplitOperatorType,
input: string,
splitSeparator: string,
joinSeparator: string,
right: boolean,
step?: number
): string {
let array: string[];
let rotatedArray: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input.split(new RegExp(splitSeparator));
break;
}
if (step !== undefined) {
if (step <= 0) {
throw new Error('Rotation step must be greater than zero.');
}
rotatedArray = rotateArray(array, step, right);
return rotatedArray.join(joinSeparator);
}
throw new Error('Rotation step contains non-digits.');
}

View File

@@ -0,0 +1,119 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { shuffleList, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import { isNumber } from '../../../../utils/string';
const initialValues = {
splitOperatorType: 'symbol' as SplitOperatorType,
splitSeparator: ',',
joinSeparator: ',',
length: ''
};
const splitOperators: {
title: string;
description: string;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description: 'Delimit input list items with a character.',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
type: 'regex',
description: 'Delimit input list items with a regular expression.'
}
];
export default function Shuffle() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const { splitOperatorType, splitSeparator, joinSeparator, length } =
optionsValues;
setResult(
shuffleList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
isNumber(length) ? Number(length) : undefined
)
);
};
return (
<Box>
<ToolInputAndResult
input={
<ToolTextInput
title={'Input list'}
value={input}
onChange={setInput}
/>
}
result={<ToolTextResult title={'Shuffled list'} value={result} />}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'Input list separator',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitOperatorType', type)}
title={title}
description={description}
checked={values.splitOperatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Shuffled List Length',
component: (
<Box>
<TextFieldWithDesc
description={'Output this many random items'}
value={values.length}
onOwnChange={(val) => updateField('length', val)}
/>
</Box>
)
},
{
title: 'Shuffled List Separator',
component: (
<Box>
<TextFieldWithDesc
value={values.joinSeparator}
onOwnChange={(value) => updateField('joinSeparator', value)}
description={'Use this separator in the randomized list.'}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Shuffle',
path: 'shuffle',
// image,
description: '',
shortDescription: '',
keywords: ['shuffle'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,38 @@
export type SplitOperatorType = 'symbol' | 'regex';
// function that randomize the array
function shuffleArray(array: string[]): string[] {
const shuffledArray = array.slice(); // Create a copy of the array
for (let i = shuffledArray.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffledArray[i], shuffledArray[j]] = [shuffledArray[j], shuffledArray[i]];
}
return shuffledArray;
}
export function shuffleList(
splitOperatorType: SplitOperatorType,
input: string,
splitSeparator: string,
joinSeparator: string,
length?: number // "?" is to handle the case the user let the input blank
): string {
let array: string[];
let shuffledArray: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input.split(new RegExp(splitSeparator));
break;
}
shuffledArray = shuffleArray(array);
if (length !== undefined) {
if (length <= 0) {
throw new Error('Length value must be a positive number.');
}
return shuffledArray.slice(0, length).join(joinSeparator);
}
return shuffledArray.join(joinSeparator);
}

View File

@@ -0,0 +1,89 @@
import { describe, expect, it } from 'vitest';
import { shuffleList, SplitOperatorType } from './service';
describe('shuffle function', () => {
it('should be a 4 length list if no length value defined ', () => {
const input: string = 'apple, pineaple, lemon, orange';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const result = shuffleList(
splitOperatorType,
input,
splitSeparator,
joinSeparator
);
expect(result.split(joinSeparator).length).toBe(4);
});
it('should be a 2 length list if length value is set to 2', () => {
const input: string = 'apple, pineaple, lemon, orange';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const length = 2;
const result = shuffleList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
length
);
console.log(result);
expect(result.split(joinSeparator).length).toBe(2);
});
it('should be a 4 length list if length value is set to 99', () => {
const input: string = 'apple, pineaple, lemon, orange';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const length = 99;
const result = shuffleList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
length
);
console.log(result);
expect(result.split(joinSeparator).length).toBe(4);
});
it('should include a random element if length value is undefined', () => {
const input: string = 'apple, pineaple, lemon, orange';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const result = shuffleList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
length
);
console.log(result);
expect(result.split(joinSeparator)).toContain('apple');
});
it('should return empty string if input is empty', () => {
const input: string = '';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const result = shuffleList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
length
);
console.log(result);
expect(result).toBe('');
});
});

View File

@@ -0,0 +1,165 @@
import { Box } from '@mui/material';
import React, { useState } from 'react';
import ToolTextInput from '@components/input/ToolTextInput';
import ToolTextResult from '@components/result/ToolTextResult';
import ToolOptions from '@components/options/ToolOptions';
import { Sort, SortingMethod, SplitOperatorType } from './service';
import ToolInputAndResult from '@components/ToolInputAndResult';
import SimpleRadio from '@components/options/SimpleRadio';
import TextFieldWithDesc from '@components/options/TextFieldWithDesc';
import CheckboxWithDesc from '@components/options/CheckboxWithDesc';
import SelectWithDesc from '@components/options/SelectWithDesc';
const initialValues = {
splitSeparatorType: 'symbol' as SplitOperatorType,
sortingMethod: 'alphabetic' as SortingMethod,
increasing: true,
splitSeparator: ',',
joinSeparator: ',',
removeDuplicated: false,
caseSensitive: false
};
const splitOperators: {
title: string;
description: string;
type: SplitOperatorType;
}[] = [
{
title: 'Use a Symbol for Splitting',
description: 'Delimit input list items with a character.',
type: 'symbol'
},
{
title: 'Use a Regex for Splitting',
type: 'regex',
description: 'Delimit input list items with a regular expression.'
}
];
export default function SplitText() {
const [input, setInput] = useState<string>('');
const [result, setResult] = useState<string>('');
const compute = (optionsValues: typeof initialValues, input: any) => {
const {
splitSeparatorType,
joinSeparator,
splitSeparator,
increasing,
caseSensitive,
removeDuplicated,
sortingMethod
} = optionsValues;
setResult(
Sort(
sortingMethod,
splitSeparatorType,
input,
increasing,
splitSeparator,
joinSeparator,
removeDuplicated,
caseSensitive
)
);
};
return (
<Box>
<ToolInputAndResult
input={
<ToolTextInput
title={'Input list'}
value={input}
onChange={setInput}
/>
}
result={<ToolTextResult title={'Sorted list'} value={result} />}
/>
<ToolOptions
compute={compute}
getGroups={({ values, updateField }) => [
{
title: 'Input item separator',
component: (
<Box>
{splitOperators.map(({ title, description, type }) => (
<SimpleRadio
key={type}
onClick={() => updateField('splitSeparatorType', type)}
title={title}
description={description}
checked={values.splitSeparatorType === type}
/>
))}
<TextFieldWithDesc
description={'Set a delimiting symbol or regular expression.'}
value={values.splitSeparator}
onOwnChange={(val) => updateField('splitSeparator', val)}
/>
</Box>
)
},
{
title: 'Sort method',
component: (
<Box>
<SelectWithDesc
selected={values.sortingMethod}
options={[
{ label: 'Sort Alphabetically', value: 'alphabetic' },
{ label: 'Sort Numerically', value: 'numeric' },
{ label: 'Sort by Length', value: 'length' }
]}
onChange={(value) => updateField('sortingMethod', value)}
description={'Select a sorting method.'}
/>
<SelectWithDesc
selected={values.increasing}
options={[
{ label: 'Increasing order', value: true },
{ label: 'Decreasing order', value: false }
]}
onChange={(value) => {
updateField('increasing', value);
}}
description={'Select a sorting order.'}
/>
<CheckboxWithDesc
title={'Case Sensitive Sort'}
description={
'Sort uppercase and lowercase items separately. Capital letters precede lowercase letters in an ascending list. (Works only in alphabetical sorting mode.)'
}
checked={values.caseSensitive}
onChange={(val) => updateField('caseSensitive', val)}
/>
</Box>
)
},
{
title: 'Sorted item properties',
component: (
<Box>
<TextFieldWithDesc
description={
'Use this symbol as a joiner between items in a sorted list.'
}
value={values.joinSeparator}
onOwnChange={(val) => updateField('joinSeparator', val)}
/>
<CheckboxWithDesc
title={'Remove duplicates'}
description={'Delete duplicate list items.'}
checked={values.removeDuplicated}
onChange={(val) => updateField('removeDuplicated', val)}
/>
</Box>
)
}
]}
initialValues={initialValues}
input={input}
/>
</Box>
);
}

View File

@@ -0,0 +1,14 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Sort',
path: 'sort',
// image,
description:
'This is a super simple browser-based application that sorts items in a list and arranges them in increasing or decreasing order. You can sort the items alphabetically, numerically, or by their length. You can also remove duplicate and empty items, as well as trim individual items that have whitespace around them. You can use any separator character to separate the input list items or alternatively use a regular expression to separate them. Additionally, you can create a new delimiter for the sorted output list.',
shortDescription: 'Quickly sort a list',
keywords: ['sort'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,123 @@
import { isNumber } from 'utils/string';
export type SortingMethod = 'numeric' | 'alphabetic' | 'length';
export type SplitOperatorType = 'symbol' | 'regex';
// utils function that choose the way of numeric sorting mixed types of array
function customNumericSort(a: string, b: string, increasing: boolean): number {
const formattedA = isNumber(a) ? Number(a) : a;
const formattedB = isNumber(b) ? Number(b) : b;
if (typeof formattedA === 'number' && typeof formattedB === 'number') {
return increasing ? formattedA - formattedB : formattedB - formattedA;
} else if (typeof formattedA === 'string' && typeof formattedB === 'string') {
return formattedA.localeCompare(formattedB); // Lexicographical comparison for strings
} else if (typeof formattedA === 'number' && typeof formattedB === 'string') {
return -1; // Numbers before strings
} else {
return 1; // Strings after numbers
}
}
export function numericSort(
array: string[], // array we build after parsing the input
increasing: boolean,
joinSeparator: string,
removeDuplicated: boolean // the value if the checkbox has been selected 1 else 0
) {
array.sort((a, b) => customNumericSort(a, b, increasing));
if (removeDuplicated) {
array = array.filter((item, index) => array.indexOf(item) === index);
}
return array.join(joinSeparator);
}
// utils function that choose the way of numeric sorting mixed types of array
function customLengthSort(a: string, b: string, increasing: boolean): number {
return increasing ? a.length - b.length : b.length - a.length;
}
export function lengthSort(
array: string[], // array we build after parsing the input
increasing: boolean, // select value has to be increasing for increasing order and decreasing for decreasing order
joinSeparator: string,
removeDuplicated: boolean // the value if the checkbox has been selected 1 else 0
) {
array.sort((a, b) => customLengthSort(a, b, increasing));
if (removeDuplicated) {
array = array.filter((item, index) => array.indexOf(item) === index);
}
return array.join(joinSeparator);
}
// Utils function that chooses the way of alphabetic sorting mixed types of array
function customAlphabeticSort(
a: string,
b: string,
caseSensitive: boolean
): number {
if (!caseSensitive) {
// Case-insensitive comparison
return a.toLowerCase().localeCompare(b.toLowerCase());
} else {
// Case-sensitive comparison
return a.charCodeAt(0) - b.charCodeAt(0);
}
}
export function alphabeticSort(
array: string[], // array we build after parsing the input
increasing: boolean, // select value has to be "increasing" for increasing order and "decreasing" for decreasing order
joinSeparator: string,
removeDuplicated: boolean, // the value if the checkbox has been selected 1 else 0
caseSensitive: boolean // the value if the checkbox has been selected 1 else 0
) {
array.sort((a, b) => customAlphabeticSort(a, b, caseSensitive));
if (!increasing) {
array.reverse();
}
if (removeDuplicated) {
array = array.filter((item, index) => array.indexOf(item) === index);
}
return array.join(joinSeparator);
}
// main function
export function Sort(
sortingMethod: SortingMethod,
splitOperatorType: SplitOperatorType,
input: string,
increasing: boolean,
splitSeparator: string,
joinSeparator: string,
removeDuplicated: boolean,
caseSensitive: boolean
) {
let array: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input.split(new RegExp(splitSeparator));
break;
}
let result: string;
switch (sortingMethod) {
case 'numeric':
result = numericSort(array, increasing, joinSeparator, removeDuplicated);
break;
case 'length':
result = lengthSort(array, increasing, joinSeparator, removeDuplicated);
break;
case 'alphabetic':
result = alphabeticSort(
array,
increasing,
joinSeparator,
removeDuplicated,
caseSensitive
);
break;
}
return result;
}

View File

@@ -0,0 +1,352 @@
// Import necessary modules and functions
import { describe, expect, it } from 'vitest';
import {
alphabeticSort,
lengthSort,
numericSort,
Sort,
SortingMethod,
SplitOperatorType
} from './service';
// Define test cases for the numericSort function
describe('numericSort function', () => {
it('should sort a list in increasing order with comma separator not removeduplicated elements', () => {
const array: string[] = ['9', '8', '7', '4', '2', '2', '5'];
const increasing: boolean = true;
const separator = ', ';
const removeDuplicated: boolean = false;
const result = numericSort(array, increasing, separator, removeDuplicated);
expect(result).toBe('2, 2, 4, 5, 7, 8, 9');
});
it('should sort a list in decreasing order with " - " separator and remove duplicated elements', () => {
const array: string[] = ['2', '4', '4', '9', '6', '6', '7'];
const increasing: boolean = false;
const separator = ' - ';
const removeDuplicated: boolean = true;
const result = numericSort(array, increasing, separator, removeDuplicated);
expect(result).toBe('9 - 7 - 6 - 4 - 2');
});
it('should sort a list with numbers and characters and remove duplicated elements', () => {
const array: string[] = ['d', 'd', 'n', 'p', 'h', 'h', '6', '9', '7', '5'];
const increasing: boolean = true;
const separator = ' ';
const removeDuplicated: boolean = true;
const result = numericSort(array, increasing, separator, removeDuplicated);
expect(result).toBe('5 6 7 9 d h n p');
});
// Define test cases for the lengthSort function
describe('lengthSort function', () => {
it('should sort a list of number by length in increasing order with comma separator ', () => {
const array: string[] = ['415689521', '3', '126', '12', '1523'];
const increasing: boolean = true;
const separator = ', ';
const removeDuplicated: boolean = false;
const result = lengthSort(array, increasing, separator, removeDuplicated);
expect(result).toBe('3, 12, 126, 1523, 415689521');
});
it('should sort a list of number by length in increasing order and remove duplicated elements ', () => {
const array: string[] = [
'415689521',
'3',
'3',
'126',
'12',
'12',
'1523'
];
const increasing: boolean = true;
const separator = ', ';
const removeDuplicated: boolean = true;
const result = lengthSort(array, increasing, separator, removeDuplicated);
expect(result).toBe('3, 12, 126, 1523, 415689521');
});
it('should sort a mixed array by length in increasing order ', () => {
const array: string[] = [
'ddd',
'd',
'nfg',
'p',
'h',
'h',
'6555',
'9',
'7',
'5556'
];
const increasing: boolean = true;
const separator = ' ';
const removeDuplicated: boolean = true;
const result = lengthSort(array, increasing, separator, removeDuplicated);
expect(result).toBe('d p h 9 7 ddd nfg 6555 5556');
});
});
// Define test cases for the alphabeticSort function
describe('alphabeticSort function', () => {
// NON CASE SENSITIVE TEST
it('should sort a list of string in increasing order with comma separator ', () => {
const array: string[] = ['apple', 'pineaple', 'lemon', 'orange'];
const increasing: boolean = true;
const separator = ', ';
const removeDuplicated: boolean = false;
const caseSensitive: boolean = false;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('apple, lemon, orange, pineaple');
});
it('should sort a list of string in decreasing order with comma separator ', () => {
const array: string[] = ['apple', 'pineaple', 'lemon', 'orange'];
const increasing: boolean = false;
const separator = ', ';
const removeDuplicated: boolean = false;
const caseSensitive: boolean = false;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('pineaple, orange, lemon, apple');
});
it('should sort a list of string and symbols (uppercase and lower) in increasing order with comma separator ', () => {
const array: string[] = [
'Apple',
'pineaple',
'lemon',
'Orange',
'1',
'9',
'@',
'+'
];
const increasing: boolean = true;
const separator = ' ';
const removeDuplicated: boolean = true;
const caseSensitive: boolean = false;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('@ + 1 9 Apple lemon Orange pineaple');
});
it('should sort a list of string and symbols (uppercase and lower) in decreasing order with comma separator ', () => {
const array: string[] = [
'Apple',
'pineaple',
'lemon',
'Orange',
'1',
'9',
'@',
'+'
];
const increasing: boolean = false;
const separator = ' ';
const removeDuplicated: boolean = true;
const caseSensitive: boolean = false;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('pineaple Orange lemon Apple 9 1 + @');
});
// CASE SENSITIVE TEST
it('should sort a list of string (uppercase) in decreasing order with comma separator ', () => {
const array: string[] = ['Apple', 'Pineaple', 'Lemon', 'Orange'];
const increasing: boolean = false;
const separator = ' ';
const removeDuplicated: boolean = false;
const caseSensitive: boolean = true;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('Pineaple Orange Lemon Apple');
});
it('should sort a list of string (uppercase and lowercase) in increasing order with comma separator ', () => {
const array: string[] = [
'Apple',
'pineaple',
'lemon',
'Orange',
'1',
'9'
];
const increasing: boolean = true;
const separator = ' ';
const removeDuplicated: boolean = true;
const caseSensitive: boolean = true;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('1 9 Apple Orange lemon pineaple');
});
it('should sort a list of string (uppercase and lower) in decreasing order with comma separator ', () => {
const array: string[] = [
'Apple',
'pineaple',
'lemon',
'Orange',
'1',
'9'
];
const increasing: boolean = false;
const separator = ' ';
const removeDuplicated: boolean = true;
const caseSensitive: boolean = true;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('pineaple lemon Orange Apple 9 1');
});
it('should sort a list of string and symbols (uppercase and lower) in decreasing order with comma separator ', () => {
const array: string[] = [
'Apple',
'pineaple',
'lemon',
'Orange',
'1',
'9',
'@',
'+'
];
const increasing: boolean = true;
const separator = ' ';
const removeDuplicated: boolean = true;
const caseSensitive: boolean = true;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('+ 1 9 @ Apple Orange lemon pineaple');
});
it('should sort a list of string and symbols (uppercase and lower) in decreasing order with comma separator ', () => {
const array: string[] = [
'Apple',
'pineaple',
'lemon',
'Orange',
'1',
'9',
'@',
'+'
];
const increasing: boolean = false;
const separator = ' ';
const removeDuplicated: boolean = true;
const caseSensitive: boolean = true;
const result = alphabeticSort(
array,
increasing,
separator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('pineaple lemon Orange Apple @ 9 1 +');
});
});
// Define test cases for the lengthSort function
describe('main function', () => {
it('should do everything alph', () => {
const sortingMethod: SortingMethod = 'alphabetic';
const splitOperatorType: SplitOperatorType = 'symbol';
const input: string = 'Apple pineaple lemon Orange 1 9 @ +';
const increasing: boolean = true;
const splitSeparator: string = ' ';
const joinSeparator: string = ' ';
const removeDuplicated: boolean = true;
const caseSensitive: boolean = true;
const result = Sort(
sortingMethod,
splitOperatorType,
input,
increasing,
splitSeparator,
joinSeparator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('+ 1 9 @ Apple Orange lemon pineaple');
});
it('should do everything numeric', () => {
const sortingMethod: SortingMethod = 'numeric';
const splitOperatorType: SplitOperatorType = 'symbol';
const input: string = '1 6 9 4 6 7 3 5 8';
const increasing: boolean = true;
const splitSeparator: string = ' ';
const joinSeparator: string = ' ';
const removeDuplicated: boolean = true;
const caseSensitive: boolean = true;
const result = Sort(
sortingMethod,
splitOperatorType,
input,
increasing,
splitSeparator,
joinSeparator,
removeDuplicated,
caseSensitive
);
expect(result).toBe('1 3 4 5 6 7 8 9');
});
});
});

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function Truncate() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Truncate',
path: 'truncate',
// image,
description: '',
shortDescription: '',
keywords: ['truncate'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,31 @@
export type SplitOperatorType = 'symbol' | 'regex';
export function truncateList(
splitOperatorType: SplitOperatorType,
input: string,
splitSeparator: string,
joinSeparator: string,
end: boolean,
length?: number
): string {
let array: string[];
let truncatedArray: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input.split(new RegExp(splitSeparator));
break;
}
if (length !== undefined) {
if (length < 0) {
throw new Error('Length value must be a positive number.');
}
truncatedArray = end
? array.slice(0, length)
: array.slice(array.length - length, array.length);
return truncatedArray.join(joinSeparator);
}
throw new Error("Length value isn't a value number.");
}

View File

@@ -0,0 +1,183 @@
import { describe, expect, it } from 'vitest';
import { SplitOperatorType, truncateList } from './service';
describe('truncate function', () => {
it('should remove at the end (one element) if end is set to true', () => {
const input: string = 'apple, pineaple, lemon, orange';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = true;
const length = 3;
const result = truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end,
length
);
expect(result).toBe('apple pineaple lemon');
});
it('should return 3 elements from the start if end is set to true', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = true;
const length = 3;
const result = truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end,
length
);
expect(result).toBe('apple pineaple lemon');
});
it('should return 3 elements from the start if end is set to true', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = true;
const length = 3;
const result = truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end,
length
);
expect(result).toBe('apple pineaple lemon');
});
it('should return 3 elements from the end if end is set to true', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = false;
const length = 3;
const result = truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end,
length
);
expect(result).toBe('lemon orange mango');
});
it('should return a void string if length is set to 0', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = false;
const length = 0;
const result = truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end,
length
);
expect(result).toBe('');
});
it('should return an element (first) string if length is set to 1 and end is set to true', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = true;
const length = 1;
const result = truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end,
length
);
expect(result).toBe('apple');
});
it('should return an element (last) string if length is set to 1 and end is set to false', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = false;
const length = 1;
const result = truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end,
length
);
expect(result).toBe('mango');
});
it('should throw an error if the length value is negative', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = false;
const length = -5;
expect(() => {
truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end,
length
);
}).toThrow('Length value must be a positive number.');
});
it('should throw an error if the length value is left blank', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ' ';
const end = false;
expect(() => {
truncateList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
end
);
}).toThrow("Length value isn't a value number.");
});
});

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function Unwrap() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Unwrap',
path: 'unwrap',
// image,
description: '',
shortDescription: '',
keywords: ['unwrap'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,69 @@
export type SplitOperatorType = 'symbol' | 'regex';
function leftUnwrap(
row: string,
left: string = '',
multiLevel: boolean
): string {
if (left === '') return row; // Prevent infinite loop if left is an empty string
while (row.startsWith(left)) {
row = row.slice(left.length);
if (!multiLevel) {
break;
}
}
return row;
}
function rightUnwrap(
row: string,
right: string = '',
multiLevel: boolean
): string {
if (right === '') return row; // Prevent infinite loop if right is an empty string
while (row.endsWith(right)) {
row = row.slice(0, row.length - right.length);
if (!multiLevel) {
break;
}
}
return row;
}
export function unwrapList(
splitOperatorType: SplitOperatorType,
input: string,
splitSeparator: string,
joinSeparator: string,
deleteEmptyItems: boolean,
multiLevel: boolean,
trimItems: boolean,
left: string = '',
right: string = ''
): string {
let array: string[];
let unwrappedArray: string[] = [];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input.split(new RegExp(splitSeparator));
break;
}
if (deleteEmptyItems) {
array = array.filter(Boolean);
}
// for each element of array unwrap left side then right side and push the result to a final array
for (let row of array) {
row = leftUnwrap(row, left, multiLevel);
row = rightUnwrap(row, right, multiLevel);
unwrappedArray.push(row);
}
// trim items if needed
if (trimItems) {
unwrappedArray = unwrappedArray.map((item) => item.trim());
}
return unwrappedArray.join(joinSeparator);
}

View File

@@ -0,0 +1,170 @@
import { describe, expect, it } from 'vitest';
import { unwrapList } from './service';
describe('unwrapList function', () => {
it('should unwrap elements correctly with symbol split', () => {
const input = '##Hello##\n##World##';
const result = unwrapList(
'symbol',
input,
'\n',
' ',
true,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello World');
});
it('should unwrap elements correctly with regex split', () => {
const input = '##Hello##||##World##';
const result = unwrapList(
'regex',
input,
'\\|\\|',
' ',
true,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello World');
});
it('should handle multiple levels of unwrapping', () => {
const input = '###Hello###';
const result = unwrapList(
'symbol',
input,
'\n',
' ',
true,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello');
});
it('should handle single level of unwrapping', () => {
const input = '###Hello###';
const result = unwrapList(
'symbol',
input,
'\n',
' ',
true,
false,
true,
'#',
'#'
);
expect(result).toBe('##Hello##');
});
it('should delete empty items', () => {
const input = '##Hello##\n\n##World##';
const result = unwrapList(
'symbol',
input,
'\n',
' ',
true,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello World');
});
it('should keep empty items if deleteEmptyItems is false', () => {
const input = '##Hello##\n\n##World##';
const result = unwrapList(
'symbol',
input,
'\n',
' ',
false,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello World');
});
it('should trim items', () => {
const input = '## Hello ##\n## World ##';
const result = unwrapList(
'symbol',
input,
'\n',
' ',
true,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello World');
});
it('should handle no left or right unwrapping', () => {
const input = 'Hello\nWorld';
const result = unwrapList('symbol', input, '\n', ' ', true, true, true);
expect(result).toBe('Hello World');
});
it('should handle mixed levels of unwrapping', () => {
const input = '###Hello##\n#World###';
const result = unwrapList(
'symbol',
input,
'\n',
' ',
true,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello World');
});
it('should handle complex regex split', () => {
const input = '##Hello##||###World###||####Test####';
const result = unwrapList(
'regex',
input,
'\\|\\|',
' ',
true,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello World Test');
});
it('should handle different joinSeparator', () => {
const input = '##Hello##\n##World##';
const result = unwrapList(
'symbol',
input,
'\n',
'-',
true,
true,
true,
'#',
'#'
);
expect(result).toBe('Hello-World');
});
});

View File

@@ -0,0 +1,11 @@
import { Box } from '@mui/material';
import React from 'react';
import * as Yup from 'yup';
const initialValues = {};
const validationSchema = Yup.object({
// splitSeparator: Yup.string().required('The separator is required')
});
export default function Wrap() {
return <Box>Lorem ipsum</Box>;
}

View File

@@ -0,0 +1,13 @@
import { defineTool } from '@tools/defineTool';
import { lazy } from 'react';
// import image from '@assets/text.png';
export const tool = defineTool('list', {
name: 'Wrap',
path: 'wrap',
// image,
description: '',
shortDescription: '',
keywords: ['wrap'],
component: lazy(() => import('./index'))
});

View File

@@ -0,0 +1,31 @@
export type SplitOperatorType = 'symbol' | 'regex';
function wrap(array: string[], left: string, right: string): string[] {
return array.map((element) => left + element + right);
}
export function wrapList(
splitOperatorType: SplitOperatorType,
input: string,
splitSeparator: string,
joinSeparator: string,
deleteEmptyItems: boolean,
left: string = '',
right: string = ''
): string {
let array: string[];
let wrappedArray: string[];
switch (splitOperatorType) {
case 'symbol':
array = input.split(splitSeparator);
break;
case 'regex':
array = input.split(new RegExp(splitSeparator));
break;
}
if (deleteEmptyItems) {
array = array.filter(Boolean);
}
wrappedArray = wrap(array, left, right);
return wrappedArray.join(joinSeparator);
}

View File

@@ -0,0 +1,132 @@
import { describe, expect, it } from 'vitest';
import { SplitOperatorType, wrapList } from './service';
describe('wrap function', () => {
it('should return the same input if no left and right are blanked', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ', ';
const deleteEmptyItems = false;
const result = wrapList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
deleteEmptyItems
);
expect(result).toBe('apple, pineaple, lemon, orange, mango');
});
it('should append to left if defined', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ', ';
const left = 'the ';
const deleteEmptyItems = false;
const result = wrapList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
deleteEmptyItems,
left
);
expect(result).toBe(
'the apple, the pineaple, the lemon, the orange, the mango'
);
});
it('should append to right if defined', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ', ';
const left = '';
const right = 'z';
const deleteEmptyItems = false;
const result = wrapList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
deleteEmptyItems,
left,
right
);
expect(result).toBe('applez, pineaplez, lemonz, orangez, mangoz');
});
it('should append to both side if both defined', () => {
const input: string = 'apple, pineaple, lemon, orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ', ';
const deleteEmptyItems = false;
const left = 'K';
const right = 'z';
const result = wrapList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
deleteEmptyItems,
left,
right
);
expect(result).toBe('Kapplez, Kpineaplez, Klemonz, Korangez, Kmangoz');
});
it('should append to both side if both defined and not delete empty items', () => {
const input: string = 'apple, pineaple, lemon, orange, mango, ';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ', ';
const deleteEmptyItems = false;
const left = 'K';
const right = 'z';
const result = wrapList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
deleteEmptyItems,
left,
right
);
expect(result).toBe('Kapplez, Kpineaplez, Klemonz, Korangez, Kmangoz, Kz');
});
it('should append to both side if both defined and delete empty items', () => {
const input: string = 'apple, pineaple, lemon, , orange, mango';
const splitOperatorType: SplitOperatorType = 'symbol';
const splitSeparator = ', ';
const joinSeparator = ', ';
const deleteEmptyItems = true;
const left = 'K';
const right = 'z';
const result = wrapList(
splitOperatorType,
input,
splitSeparator,
joinSeparator,
deleteEmptyItems,
left,
right
);
expect(result).toBe('Kapplez, Kpineaplez, Klemonz, Korangez, Kmangoz');
});
});