Skip to content

Commit 1593db4

Browse files
authored
Merge pull request #15 from chasefleming/feature/options
Add options arg to transform values
2 parents 9fffead + 312f758 commit 1593db4

File tree

5 files changed

+248
-77
lines changed

5 files changed

+248
-77
lines changed

README.md

Lines changed: 39 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# enum-xyz
22

3-
JavaScript enums using proxies.
3+
`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.
44

55
> Based on [this tweet](https://twitter.com/2ality/status/1486139713354448897)
66
@@ -24,9 +24,25 @@ import Enum from "enum-xyz";
2424
const { Summer, Autumn, Winter, Spring } = Enum.String();
2525
2626
console.log(Summer); // Outputs: "Summer"
27-
console.log(Autumn); // Outputs: "Autumn"
28-
console.log(Winter); // Outputs: "Winter"
29-
console.log(Spring); // Outputs: "Spring"
27+
```
28+
29+
#### Options for String Enums:
30+
31+
- `casing`: Transforms the string based on the specified casing style. Available options are `snakeCase`, `camelCase`, `PascalCase`, `kebabCase`, `lowercase`, and `uppercase`.
32+
- `transform`: Provide a custom function to transform the enum values. This function takes the original value and returns a transformed value.
33+
34+
##### Example:
35+
36+
```
37+
const { userId, userAddress } = Enum.String({ casing: 'kebabCase' });
38+
console.log(userId); // Outputs: "user-id"
39+
40+
const options = {
41+
casing: 'kebabCase',
42+
transform: (value) => `https://api.example.com/${value}`
43+
};
44+
const { userEndpoint, orderEndpoint } = Enum.String(options);
45+
console.log(userEndpoint); // Outputs: "https://api.example.com/user-endpoint"
3046
```
3147

3248
### Numeric Enums
@@ -39,16 +55,19 @@ import Enum from "enum-xyz";
3955
const { A, B, C, D } = Enum.Numeric();
4056
4157
console.log(A); // Outputs: 0
42-
console.log(B); // Outputs: 1
43-
console.log(C); // Outputs: 2
44-
console.log(D); // Outputs: 3
4558
```
4659

47-
To start from a different index:
60+
#### Options for Numeric Enums:
61+
62+
- `startIndex`: Start the numeric enum from a specific index.
63+
- `step`: Increment the numeric values by a specific step (e.g., 2, 5, 10).
64+
65+
##### Example:
4866

4967
```
50-
const { A, B, C, D } = Enum.Numeric(5);
68+
const { A, B, C } = Enum.Numeric({ startIndex: 5, step: 2 });
5169
console.log(A); // Outputs: 5
70+
console.log(B); // Outputs: 7
5271
```
5372

5473
### Symbol Enums
@@ -59,5 +78,15 @@ import Enum from "enum-xyz";
5978
const { blue, red } = Enum.Symbol();
6079
6180
console.log(blue); // Outputs: Symbol(blue)
62-
console.log(red); // Outputs: Symbol(red)
81+
```
82+
83+
#### Options for Symbol Enums:
84+
85+
- `global`: Create a global symbol using Symbol.for().
86+
87+
##### Example:
88+
89+
```
90+
const { blueGlobal } = Enum.Symbol({ global: true });
91+
console.log(blueGlobal); // Outputs: Symbol.for('blueGlobal')
6392
```

index.js

Lines changed: 61 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,76 @@
1-
const createEnum = (type, startIndex = 0) => {
2-
let currentIndex = startIndex
1+
function toCamelCase(str) {
2+
return str.charAt(0).toLowerCase() + str.slice(1).replace(/([-_]\w)/g, g => g[1].toUpperCase());
3+
}
4+
5+
function toPascalCase(str) {
6+
return str.charAt(0).toUpperCase() + str.slice(1).replace(/([-_]\w)/g, g => g[1].toUpperCase());
7+
}
8+
9+
function toKebabCase(str) {
10+
return str
11+
.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1-$2')
12+
.toLowerCase()
13+
.replace(/^-/, ''); // Remove leading hyphen if present
14+
}
15+
16+
function toSnakeCase(str) {
17+
return str.replace(/([a-z0-9]|(?=[A-Z]))([A-Z])/g, '$1_$2').toLowerCase();
18+
}
19+
20+
const createEnum = (type, options = {}) => {
21+
let currentIndex = options.startIndex || 0;
22+
23+
const transformValue = (value) => {
24+
// Apply prefix and suffix
25+
value = (options.prefix || '') + value + (options.suffix || '');
26+
27+
// Apply casing transformations
28+
switch (options.casing) {
29+
case 'camelCase':
30+
value = toCamelCase(value);
31+
break;
32+
case 'PascalCase':
33+
value = toPascalCase(value);
34+
break;
35+
case 'kebabCase':
36+
value = toKebabCase(value);
37+
break;
38+
case 'snakeCase':
39+
value = toSnakeCase(value);
40+
break;
41+
}
42+
43+
// Apply custom transform function if provided
44+
if (options.transform && typeof options.transform === 'function') {
45+
value = options.transform(value);
46+
}
47+
48+
return value;
49+
};
350

451
const handler = {
552
get(_, name) {
653
if (type === 'String') {
7-
return name
54+
return transformValue(name);
855
}
956
if (type === 'Numeric') {
10-
const current = currentIndex
11-
currentIndex++
12-
return current
57+
const current = currentIndex;
58+
currentIndex += options.step || 1;
59+
return current;
1360
}
1461
if (type === 'Symbol') {
15-
return Symbol(name)
62+
return options.global ? Symbol.for(name) : Symbol(name);
1663
}
1764
// For grouping, return another proxy
18-
return new Proxy({}, handler)
65+
return new Proxy({}, handler);
1966
}
20-
}
67+
};
2168

22-
return new Proxy({}, handler)
69+
return new Proxy({}, handler);
2370
}
2471

2572
export default {
26-
String: () => createEnum('String'),
27-
Numeric: (startIndex = 0) => createEnum('Numeric', startIndex),
28-
Symbol: () => createEnum('Symbol')
29-
}
73+
String: (options) => createEnum('String', options),
74+
Numeric: (options = {}) => createEnum('Numeric', options),
75+
Symbol: (options = {}) => createEnum('Symbol', options)
76+
}

index.test.js

Lines changed: 145 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,151 @@
11
import Enum from './index'
22

3-
test('creates enum and assigns strings', () => {
4-
const { Summer, Autumn, Winter, Spring } = Enum.String()
3+
describe('String Enums', () => {
4+
test('creates enum and assigns strings', () => {
5+
const { Summer, Autumn, Winter, Spring } = Enum.String()
6+
7+
expect(Summer).toEqual('Summer')
8+
expect(Autumn).toEqual('Autumn')
9+
expect(Winter).toEqual('Winter')
10+
expect(Spring).toEqual('Spring')
11+
})
12+
13+
test('creates enum with snakeCase casing', () => {
14+
const { userId, userAddress, orderNumber } = Enum.String({ casing: 'snakeCase' })
15+
16+
expect(userId).toEqual('user_id')
17+
expect(userAddress).toEqual('user_address')
18+
expect(orderNumber).toEqual('order_number')
19+
})
20+
21+
test('creates enum with camelCase casing', () => {
22+
const { User_Id, User_Address, Order_Number } = Enum.String({ casing: 'camelCase' })
23+
24+
expect(User_Id).toEqual('userId')
25+
expect(User_Address).toEqual('userAddress')
26+
expect(Order_Number).toEqual('orderNumber')
27+
})
28+
29+
test('creates enum with PascalCase casing', () => {
30+
const { user_id, user_address, order_number } = Enum.String({ casing: 'PascalCase' })
31+
32+
expect(user_id).toEqual('UserId')
33+
expect(user_address).toEqual('UserAddress')
34+
expect(order_number).toEqual('OrderNumber')
35+
})
36+
37+
test('creates enum with kebabCase casing', () => {
38+
const { UserId, UserAddress, OrderNumber } = Enum.String({ casing: 'kebabCase' })
39+
40+
expect(UserId).toEqual('user-id')
41+
expect(UserAddress).toEqual('user-address')
42+
expect(OrderNumber).toEqual('order-number')
43+
})
44+
45+
test('creates enum with prefix', () => {
46+
const { Summer, Winter } = Enum.String({ prefix: 'Season_' });
47+
48+
expect(Summer).toEqual('Season_Summer');
49+
expect(Winter).toEqual('Season_Winter');
50+
});
51+
52+
test('creates enum with suffix', () => {
53+
const { Summer, Winter } = Enum.String({ suffix: '_Season' });
54+
55+
expect(Summer).toEqual('Summer_Season');
56+
expect(Winter).toEqual('Winter_Season');
57+
});
58+
59+
test('creates enum with both prefix and suffix', () => {
60+
const { Summer, Winter } = Enum.String({ prefix: 'Season_', suffix: '_Time' });
61+
62+
expect(Summer).toEqual('Season_Summer_Time');
63+
expect(Winter).toEqual('Season_Winter_Time');
64+
});
65+
66+
test('creates enum with transform function', () => {
67+
const transformFn = (value) => `Transformed_${value}`;
68+
const { Summer, Winter } = Enum.String({ transform: transformFn });
69+
70+
expect(Summer).toEqual('Transformed_Summer');
71+
expect(Winter).toEqual('Transformed_Winter');
72+
});
73+
74+
test('creates enum with transform function and casing', () => {
75+
const transformFn = (value) => `Transformed_${value}`;
76+
const { summer_time, winter_time } = Enum.String({ transform: transformFn, casing: 'PascalCase' });
77+
78+
expect(summer_time).toEqual('Transformed_SummerTime');
79+
expect(winter_time).toEqual('Transformed_WinterTime');
80+
});
81+
});
582

6-
expect(Summer).toEqual('Summer')
7-
expect(Autumn).toEqual('Autumn')
8-
expect(Winter).toEqual('Winter')
9-
expect(Spring).toEqual('Spring')
83+
describe('Numeric Enums', () => {
84+
test('creates enum and assigns numeric value', () => {
85+
const { A, B, C, D } = Enum.Numeric()
86+
87+
expect(A).toBe(0)
88+
expect(B).toBe(1)
89+
expect(C).toBe(2)
90+
expect(D).toBe(3)
91+
})
92+
93+
test('creates enum and assigns numeric value starting at index of choice', () => {
94+
const { A, B, C, D } = Enum.Numeric({ startIndex: 1 })
95+
96+
expect(A).toBe(1)
97+
expect(B).toBe(2)
98+
expect(C).toBe(3)
99+
expect(D).toBe(4)
100+
})
101+
102+
test('creates enum and assigns numeric value with a specific step', () => {
103+
const { A, B, C, D } = Enum.Numeric({ startIndex: 0, step: 5 });
104+
105+
expect(A).toBe(0);
106+
expect(B).toBe(5);
107+
expect(C).toBe(10);
108+
expect(D).toBe(15);
109+
});
110+
111+
test('ensures numeric enums are stateless and start from the first accessed key', () => {
112+
const { B, A, C } = Enum.Numeric()
113+
const { D, E } = Enum.Numeric()
114+
const { F, G } = Enum.Numeric({ startIndex: 5 })
115+
const { H, I } = Enum.Numeric()
116+
117+
expect(B).toBe(0)
118+
expect(A).toBe(1)
119+
expect(C).toBe(2)
120+
expect(D).toBe(0)
121+
expect(E).toBe(1)
122+
expect(F).toBe(5)
123+
expect(G).toBe(6)
124+
expect(H).toBe(0)
125+
expect(I).toBe(1)
126+
})
10127
})
11128

12-
test('creates enum and assigns numeric value', () => {
13-
const { A, B, C, D } = Enum.Numeric()
14-
15-
expect(A).toBe(0)
16-
expect(B).toBe(1)
17-
expect(C).toBe(2)
18-
expect(D).toBe(3)
129+
describe('Symbol Enums', () => {
130+
test('creates enum and assigns symbol values', () => {
131+
const { blue, red } = Enum.Symbol()
132+
const { blue: blueMood, happy } = Enum.Symbol()
133+
134+
expect(blue).toBe(blue)
135+
expect(blue).not.toBe(red)
136+
expect(blue).not.toBe(blueMood)
137+
expect(blue).not.toBe('blue')
138+
expect(blue).not.toBe(Symbol('blue'))
139+
})
140+
141+
test('creates global symbol values', () => {
142+
const { globalBlue, globalRed } = Enum.Symbol({ global: true });
143+
const { globalBlue: anotherGlobalBlue } = Enum.Symbol({ global: true });
144+
145+
expect(globalBlue).toBe(globalBlue);
146+
expect(globalBlue).toBe(anotherGlobalBlue); // Both should reference the same global symbol
147+
expect(globalBlue).not.toBe(globalRed);
148+
expect(globalBlue).not.toBe('globalBlue');
149+
expect(globalBlue).toBe(Symbol.for('globalBlue')); // Should match the global symbol
150+
})
19151
})
20-
21-
test('creates enum and assigns numeric value starting at index of choice', () => {
22-
const { A, B, C, D } = Enum.Numeric(1)
23-
24-
expect(A).toBe(1)
25-
expect(B).toBe(2)
26-
expect(C).toBe(3)
27-
expect(D).toBe(4)
28-
})
29-
30-
test('ensures numeric enums are stateless and start from the first accessed key', () => {
31-
const { B, A, C } = Enum.Numeric()
32-
const { D, E } = Enum.Numeric()
33-
const { F, G } = Enum.Numeric(5)
34-
const { H, I } = Enum.Numeric()
35-
36-
expect(B).toBe(0)
37-
expect(A).toBe(1)
38-
expect(C).toBe(2)
39-
expect(D).toBe(0)
40-
expect(E).toBe(1)
41-
expect(F).toBe(5)
42-
expect(G).toBe(6)
43-
expect(H).toBe(0)
44-
expect(I).toBe(1)
45-
})
46-
47-
test('creates enum and assigns symbol values', () => {
48-
const { blue, red } = Enum.Symbol()
49-
const { blue: blueMood, happy } = Enum.Symbol()
50-
51-
expect(blue).toBe(blue)
52-
expect(blue).not.toBe(red)
53-
expect(blue).not.toBe(blueMood)
54-
expect(blue).not.toBe('blue')
55-
expect(blue).not.toBe(Symbol('blue'))
56-
})

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "enum-xyz",
33
"type": "module",
4-
"version": "0.2.0",
4+
"version": "0.3.0",
55
"description": "JavaScript enums using proxies.",
66
"homepage": "https://github.com/chasefleming/enum-xyz",
77
"author": "Chase Fleming",

0 commit comments

Comments
 (0)