Skip to content

Commit

Permalink
Merge pull request #15 from chasefleming/feature/options
Browse files Browse the repository at this point in the history
Add options arg to transform values
  • Loading branch information
chasefleming authored Aug 18, 2023
2 parents 9fffead + 312f758 commit 1593db4
Show file tree
Hide file tree
Showing 5 changed files with 248 additions and 77 deletions.
49 changes: 39 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# enum-xyz

JavaScript enums using proxies.
`enum-xyz` offers a way to generate enums in JavaScript leveraging the power of Proxies. It supports various casing styles, transformations, and other customization options.

> Based on [this tweet](https://twitter.com/2ality/status/1486139713354448897)
Expand All @@ -24,9 +24,25 @@ import Enum from "enum-xyz";
const { Summer, Autumn, Winter, Spring } = Enum.String();
console.log(Summer); // Outputs: "Summer"
console.log(Autumn); // Outputs: "Autumn"
console.log(Winter); // Outputs: "Winter"
console.log(Spring); // Outputs: "Spring"
```

#### Options for String Enums:

- `casing`: Transforms the string based on the specified casing style. Available options are `snakeCase`, `camelCase`, `PascalCase`, `kebabCase`, `lowercase`, and `uppercase`.
- `transform`: Provide a custom function to transform the enum values. This function takes the original value and returns a transformed value.

##### Example:

```
const { userId, userAddress } = Enum.String({ casing: 'kebabCase' });
console.log(userId); // Outputs: "user-id"
const options = {
casing: 'kebabCase',
transform: (value) => `https://api.example.com/${value}`
};
const { userEndpoint, orderEndpoint } = Enum.String(options);
console.log(userEndpoint); // Outputs: "https://api.example.com/user-endpoint"
```

### Numeric Enums
Expand All @@ -39,16 +55,19 @@ import Enum from "enum-xyz";
const { A, B, C, D } = Enum.Numeric();
console.log(A); // Outputs: 0
console.log(B); // Outputs: 1
console.log(C); // Outputs: 2
console.log(D); // Outputs: 3
```

To start from a different index:
#### Options for Numeric Enums:

- `startIndex`: Start the numeric enum from a specific index.
- `step`: Increment the numeric values by a specific step (e.g., 2, 5, 10).

##### Example:

```
const { A, B, C, D } = Enum.Numeric(5);
const { A, B, C } = Enum.Numeric({ startIndex: 5, step: 2 });
console.log(A); // Outputs: 5
console.log(B); // Outputs: 7
```

### Symbol Enums
Expand All @@ -59,5 +78,15 @@ import Enum from "enum-xyz";
const { blue, red } = Enum.Symbol();
console.log(blue); // Outputs: Symbol(blue)
console.log(red); // Outputs: Symbol(red)
```

#### Options for Symbol Enums:

- `global`: Create a global symbol using Symbol.for().

##### Example:

```
const { blueGlobal } = Enum.Symbol({ global: true });
console.log(blueGlobal); // Outputs: Symbol.for('blueGlobal')
```
75 changes: 61 additions & 14 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,76 @@
const createEnum = (type, startIndex = 0) => {
let currentIndex = startIndex
function toCamelCase(str) {
return str.charAt(0).toLowerCase() + str.slice(1).replace(/([-_]\w)/g, g => g[1].toUpperCase());
}

function toPascalCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1).replace(/([-_]\w)/g, g => g[1].toUpperCase());
}

function toKebabCase(str) {
return str
.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2')
.toLowerCase()
.replace(/^-/, ''); // Remove leading hyphen if present
}

function toSnakeCase(str) {
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1_$2').toLowerCase();
}

const createEnum = (type, options = {}) => {
let currentIndex = options.startIndex || 0;

const transformValue = (value) => {
// Apply prefix and suffix
value = (options.prefix || '') + value + (options.suffix || '');

// Apply casing transformations
switch (options.casing) {
case 'camelCase':
value = toCamelCase(value);
break;
case 'PascalCase':
value = toPascalCase(value);
break;
case 'kebabCase':
value = toKebabCase(value);
break;
case 'snakeCase':
value = toSnakeCase(value);
break;
}

// Apply custom transform function if provided
if (options.transform && typeof options.transform === 'function') {
value = options.transform(value);
}

return value;
};

const handler = {
get(_, name) {
if (type === 'String') {
return name
return transformValue(name);
}
if (type === 'Numeric') {
const current = currentIndex
currentIndex++
return current
const current = currentIndex;
currentIndex += options.step || 1;
return current;
}
if (type === 'Symbol') {
return Symbol(name)
return options.global ? Symbol.for(name) : Symbol(name);
}
// For grouping, return another proxy
return new Proxy({}, handler)
return new Proxy({}, handler);
}
}
};

return new Proxy({}, handler)
return new Proxy({}, handler);
}

export default {
String: () => createEnum('String'),
Numeric: (startIndex = 0) => createEnum('Numeric', startIndex),
Symbol: () => createEnum('Symbol')
}
String: (options) => createEnum('String', options),
Numeric: (options = {}) => createEnum('Numeric', options),
Symbol: (options = {}) => createEnum('Symbol', options)
}
195 changes: 145 additions & 50 deletions index.test.js
Original file line number Diff line number Diff line change
@@ -1,56 +1,151 @@
import Enum from './index'

test('creates enum and assigns strings', () => {
const { Summer, Autumn, Winter, Spring } = Enum.String()
describe('String Enums', () => {
test('creates enum and assigns strings', () => {
const { Summer, Autumn, Winter, Spring } = Enum.String()

expect(Summer).toEqual('Summer')
expect(Autumn).toEqual('Autumn')
expect(Winter).toEqual('Winter')
expect(Spring).toEqual('Spring')
})

test('creates enum with snakeCase casing', () => {
const { userId, userAddress, orderNumber } = Enum.String({ casing: 'snakeCase' })

expect(userId).toEqual('user_id')
expect(userAddress).toEqual('user_address')
expect(orderNumber).toEqual('order_number')
})

test('creates enum with camelCase casing', () => {
const { User_Id, User_Address, Order_Number } = Enum.String({ casing: 'camelCase' })

expect(User_Id).toEqual('userId')
expect(User_Address).toEqual('userAddress')
expect(Order_Number).toEqual('orderNumber')
})

test('creates enum with PascalCase casing', () => {
const { user_id, user_address, order_number } = Enum.String({ casing: 'PascalCase' })

expect(user_id).toEqual('UserId')
expect(user_address).toEqual('UserAddress')
expect(order_number).toEqual('OrderNumber')
})

test('creates enum with kebabCase casing', () => {
const { UserId, UserAddress, OrderNumber } = Enum.String({ casing: 'kebabCase' })

expect(UserId).toEqual('user-id')
expect(UserAddress).toEqual('user-address')
expect(OrderNumber).toEqual('order-number')
})

test('creates enum with prefix', () => {
const { Summer, Winter } = Enum.String({ prefix: 'Season_' });

expect(Summer).toEqual('Season_Summer');
expect(Winter).toEqual('Season_Winter');
});

test('creates enum with suffix', () => {
const { Summer, Winter } = Enum.String({ suffix: '_Season' });

expect(Summer).toEqual('Summer_Season');
expect(Winter).toEqual('Winter_Season');
});

test('creates enum with both prefix and suffix', () => {
const { Summer, Winter } = Enum.String({ prefix: 'Season_', suffix: '_Time' });

expect(Summer).toEqual('Season_Summer_Time');
expect(Winter).toEqual('Season_Winter_Time');
});

test('creates enum with transform function', () => {
const transformFn = (value) => `Transformed_${value}`;
const { Summer, Winter } = Enum.String({ transform: transformFn });

expect(Summer).toEqual('Transformed_Summer');
expect(Winter).toEqual('Transformed_Winter');
});

test('creates enum with transform function and casing', () => {
const transformFn = (value) => `Transformed_${value}`;
const { summer_time, winter_time } = Enum.String({ transform: transformFn, casing: 'PascalCase' });

expect(summer_time).toEqual('Transformed_SummerTime');
expect(winter_time).toEqual('Transformed_WinterTime');
});
});

expect(Summer).toEqual('Summer')
expect(Autumn).toEqual('Autumn')
expect(Winter).toEqual('Winter')
expect(Spring).toEqual('Spring')
describe('Numeric Enums', () => {
test('creates enum and assigns numeric value', () => {
const { A, B, C, D } = Enum.Numeric()

expect(A).toBe(0)
expect(B).toBe(1)
expect(C).toBe(2)
expect(D).toBe(3)
})

test('creates enum and assigns numeric value starting at index of choice', () => {
const { A, B, C, D } = Enum.Numeric({ startIndex: 1 })

expect(A).toBe(1)
expect(B).toBe(2)
expect(C).toBe(3)
expect(D).toBe(4)
})

test('creates enum and assigns numeric value with a specific step', () => {
const { A, B, C, D } = Enum.Numeric({ startIndex: 0, step: 5 });

expect(A).toBe(0);
expect(B).toBe(5);
expect(C).toBe(10);
expect(D).toBe(15);
});

test('ensures numeric enums are stateless and start from the first accessed key', () => {
const { B, A, C } = Enum.Numeric()
const { D, E } = Enum.Numeric()
const { F, G } = Enum.Numeric({ startIndex: 5 })
const { H, I } = Enum.Numeric()

expect(B).toBe(0)
expect(A).toBe(1)
expect(C).toBe(2)
expect(D).toBe(0)
expect(E).toBe(1)
expect(F).toBe(5)
expect(G).toBe(6)
expect(H).toBe(0)
expect(I).toBe(1)
})
})

test('creates enum and assigns numeric value', () => {
const { A, B, C, D } = Enum.Numeric()

expect(A).toBe(0)
expect(B).toBe(1)
expect(C).toBe(2)
expect(D).toBe(3)
describe('Symbol Enums', () => {
test('creates enum and assigns symbol values', () => {
const { blue, red } = Enum.Symbol()
const { blue: blueMood, happy } = Enum.Symbol()

expect(blue).toBe(blue)
expect(blue).not.toBe(red)
expect(blue).not.toBe(blueMood)
expect(blue).not.toBe('blue')
expect(blue).not.toBe(Symbol('blue'))
})

test('creates global symbol values', () => {
const { globalBlue, globalRed } = Enum.Symbol({ global: true });
const { globalBlue: anotherGlobalBlue } = Enum.Symbol({ global: true });

expect(globalBlue).toBe(globalBlue);
expect(globalBlue).toBe(anotherGlobalBlue); // Both should reference the same global symbol
expect(globalBlue).not.toBe(globalRed);
expect(globalBlue).not.toBe('globalBlue');
expect(globalBlue).toBe(Symbol.for('globalBlue')); // Should match the global symbol
})
})

test('creates enum and assigns numeric value starting at index of choice', () => {
const { A, B, C, D } = Enum.Numeric(1)

expect(A).toBe(1)
expect(B).toBe(2)
expect(C).toBe(3)
expect(D).toBe(4)
})

test('ensures numeric enums are stateless and start from the first accessed key', () => {
const { B, A, C } = Enum.Numeric()
const { D, E } = Enum.Numeric()
const { F, G } = Enum.Numeric(5)
const { H, I } = Enum.Numeric()

expect(B).toBe(0)
expect(A).toBe(1)
expect(C).toBe(2)
expect(D).toBe(0)
expect(E).toBe(1)
expect(F).toBe(5)
expect(G).toBe(6)
expect(H).toBe(0)
expect(I).toBe(1)
})

test('creates enum and assigns symbol values', () => {
const { blue, red } = Enum.Symbol()
const { blue: blueMood, happy } = Enum.Symbol()

expect(blue).toBe(blue)
expect(blue).not.toBe(red)
expect(blue).not.toBe(blueMood)
expect(blue).not.toBe('blue')
expect(blue).not.toBe(Symbol('blue'))
})
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "enum-xyz",
"type": "module",
"version": "0.2.0",
"version": "0.3.0",
"description": "JavaScript enums using proxies.",
"homepage": "https://github.com/chasefleming/enum-xyz",
"author": "Chase Fleming",
Expand Down

0 comments on commit 1593db4

Please sign in to comment.