Skip to content

Commit

Permalink
Merge pull request #582 from tokens-studio/string-nodes
Browse files Browse the repository at this point in the history
String Manipulation Nodes
  • Loading branch information
mck authored Dec 17, 2024
2 parents 3f12ab0 + 3abfe5f commit 1c876a9
Show file tree
Hide file tree
Showing 9 changed files with 438 additions and 62 deletions.
8 changes: 8 additions & 0 deletions .changeset/new-string-nodes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"@tokens-studio/graph-engine": minor
---

Added new string manipulation nodes:
- Case Convert: Transform strings between camelCase, snake_case, kebab-case, and PascalCase
- Replace: Simple string replacement without regex
- Normalize: String normalization with accent removal options
62 changes: 0 additions & 62 deletions packages/documentation/docs/nodes/string/lowercase.mdx

This file was deleted.

100 changes: 100 additions & 0 deletions packages/graph-engine/src/nodes/string/case.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';
import { StringSchema } from '../../schemas/index.js';

export enum CaseType {
CAMEL = 'camel',
SNAKE = 'snake',
KEBAB = 'kebab',
PASCAL = 'pascal'
}

/**
* This node converts strings between different case formats
*/
export default class NodeDefinition extends Node {
static title = 'Case Convert';
static type = 'studio.tokens.string.case';
static description = 'Converts strings between different case formats';

declare inputs: ToInput<{
string: string;
type: CaseType;
/**
* Characters to be replaced with spaces. Default: -_.
* @default "-_."
*/
delimiters: string;
}>;
declare outputs: ToOutput<{
string: string;
}>;

constructor(props: INodeDefinition) {
super(props);
this.addInput('string', {
type: StringSchema
});
this.addInput('type', {
type: {
...StringSchema,
enum: Object.values(CaseType),
default: CaseType.CAMEL
}
});
this.addInput('delimiters', {
type: {
...StringSchema,
default: '-_.'
}
});
this.addOutput('string', {
type: StringSchema
});
}

execute(): void | Promise<void> {
const { string, type, delimiters } = this.getAllInputs();

// Replace each delimiter with a space
const processedString = delimiters
.split('')
.reduce((result, char) => result.replaceAll(char, ' '), string);

// First normalize the string by splitting on word boundaries
const words = processedString
// Add space before capitals in camelCase/PascalCase
.split(/([A-Z][a-z]+)/)
.join(' ')
// Remove extra spaces and convert to lowercase
.trim()
.toLowerCase()
// Split into words and remove empty strings
.split(/\s+/)
.filter(word => word.length > 0);

let result: string;
switch (type) {
case CaseType.CAMEL:
result = words[0] + words.slice(1).map(capitalize).join('');
break;
case CaseType.SNAKE:
result = words.join('_');
break;
case CaseType.KEBAB:
result = words.join('-');
break;
case CaseType.PASCAL:
result = words.map(capitalize).join('');
break;
default:
result = string;
}

this.outputs.string.set(result);
}
}

function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
6 changes: 6 additions & 0 deletions packages/graph-engine/src/nodes/string/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import caseConvert from './case.js';
import interpolate from './interpolate.js';
import join from './join.js';
import lowercase from './lowercase.js';
import normalize from './normalize.js';
import pad from './pad.js';
import regex from './regex.js';
import replace from './replace.js';
import split from './split.js';
import stringify from './stringify.js';
import uppercase from './uppercase.js';

export const nodes = [
interpolate,
join,
caseConvert,
lowercase,
normalize,
pad,
regex,
replace,
split,
stringify,
uppercase
Expand Down
70 changes: 70 additions & 0 deletions packages/graph-engine/src/nodes/string/normalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';
import { StringSchema } from '../../schemas/index.js';

export enum NormalizationForm {
NFD = 'NFD',
NFC = 'NFC',
NFKD = 'NFKD',
NFKC = 'NFKC'
}

/**
* This node normalizes strings and can remove diacritical marks (accents)
*/
export default class NodeDefinition extends Node {
static title = 'Normalize';
static type = 'studio.tokens.string.normalize';
static description =
'Normalizes strings and optionally removes diacritical marks';

declare inputs: ToInput<{
string: string;
form: NormalizationForm;
removeAccents: boolean;
}>;
declare outputs: ToOutput<{
string: string;
}>;

constructor(props: INodeDefinition) {
super(props);
this.addInput('string', {
type: StringSchema
});
this.addInput('form', {
type: {
...StringSchema,
enum: Object.values(NormalizationForm),
default: NormalizationForm.NFC
}
});
this.addInput('removeAccents', {
type: {
type: 'boolean',
title: 'Remove Accents',
description: 'Whether to remove diacritical marks',
default: true
}
});
this.addOutput('string', {
type: StringSchema
});
}

execute(): void | Promise<void> {
const { string, form, removeAccents } = this.getAllInputs();

let result = string.normalize(form);

if (removeAccents) {
// Remove combining diacritical marks
result = result
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.normalize(form);
}

this.outputs.string.set(result);
}
}
47 changes: 47 additions & 0 deletions packages/graph-engine/src/nodes/string/replace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { INodeDefinition, ToInput, ToOutput } from '../../index.js';
import { Node } from '../../programmatic/node.js';
import { StringSchema } from '../../schemas/index.js';

/**
* This node replaces all occurrences of a search string with a replacement string
*/
export default class NodeDefinition extends Node {
static title = 'Replace';
static type = 'studio.tokens.string.replace';
static description =
'Replaces all occurrences of a search string with a replacement string';

declare inputs: ToInput<{
string: string;
search: string;
replace: string;
}>;
declare outputs: ToOutput<{
string: string;
}>;

constructor(props: INodeDefinition) {
super(props);
this.addInput('string', {
type: StringSchema
});
this.addInput('search', {
type: StringSchema
});
this.addInput('replace', {
type: {
...StringSchema,
default: ''
}
});
this.addOutput('string', {
type: StringSchema
});
}

execute(): void | Promise<void> {
const { string, search, replace } = this.getAllInputs();
const result = string.split(search).join(replace);
this.outputs.string.set(result);
}
}
Loading

0 comments on commit 1c876a9

Please sign in to comment.