Generate random and incrementing string sequences using regex and custom functions.
Interactive Demo available at: https://carlosnz.github.io/custom_string_patterns
custom_string_patterns is based on randexp by @fent -- a package to generate random strings that match given regular expressions. custom_string_patterns takes it a step further by providing a mechanism for string patterns to contain incrementing values and custom replacement functions beyond what regex can do.
Install: npm i custom_string_patterns
or: yarn add custom_string_patterns
import Pattern from 'custom-string-patterns'
// Generate strings with 3 random letters, a hyphen, and an incrementing 4-digit value
const serial = new Pattern(/[A-Z]{3}-<+dddd>/)
console.log(await serial.gen()) // XAP-0001
console.log(await serial.gen()) // BYW-0002
console.log(await serial.gen()) // APL-0003
console.log(await serial.gen()) // SUH-0004
// NOTE: Assumes wrapped in an Async function, since `.gen()` is an Async method
// Generate strings using an external counter and a custom replacer function that doubles
// the input parameter and inserts it into the string
const extCounter = new Pattern(/COUNT: <+d> \| DOUBLED: <?double>/, {
getCounter: DBCounterFunction,
customReplacers: { double: (n) => n * 2 },
})
// where "DBCounterFunction" is a call to a database that stores a count value,
// returning the next number and incrementing the counter (current value: 123)
console.log(await extCounter.gen({ customArgs: { double: 5 } }))
// => COUNT: 123 | DOUBLED: 10
console.log(await extCounter.gen({ customArgs: { double: -2 } }))
// => COUNT: 124 | DOUBLED: -4
Please check out the demo file (demo.ts
) and the test suite (patterns.test.ts
) for more detailed examples.
To run the demo file:
import { runDemo } from 'custom-string-patterns'
runDemo()
A new pattern object is constructed with new Pattern ( pattern, options )
The syntax of pattern is a regular expression (string or object), but with custom enhancements to represent counters, replacement functions, or data fields.
Please see randexp documentation for explanation of how a basic regex is used to generate a random string.
All counter/custom/data replacements are wrapped in < >
, and the first character inside indicates which type of replacement it should be:
+
- a Counter, e.g.<+ddd>
?
- a Function, e.g.<?toUpper>
(or<?toUpper(1...)
-- see below)- no prefix - object data replacement, e.g.
<user.firstName>
Counters are placed into the pattern string by using <+dd>
, where d
represents a digit in the output string -- numbers will be padded with leading zeroes to match (at least) the length of the ddd
sequence. i.e. <+dddd>
with output number 55
yields 0055
More complex number formatting can be achieved using a numberFormat
parameter in "options" (see below).
Replacement functions are defined in "options" (see below), but are invoked as part of the string pattern using <?func>
, where "func" is the name of the function defined in options.
In this case, the pre-processor would look for a key named "func" in customReplacers, and apply whatever function definition was provided.
The arguments for the replacement functions can be provided in two different ways:
- Passing in with
customArgs
field when a new string is generated (see.gen()
method). - It's possible to pass the results of the random strings generated by the regex pattern as arguments. For example, you could generate random credit card numbers where the first 15 digits are randomly generated but the last digit needs to be a checksum digit dependent on the first 15 (see this example in demo file). To achieve this, first the relevant elements of the regex must be defined in capture groups. Each captured substring is indexed starting from 1, and these are written in standard function syntax as part of the pattern.
E.g. If you want a replacement functiontoUpper
to act on the output of capture group 1, you'd include<?toUpper(1)>
at the appropriate place in the pattern.
Note that if both are defined, customArgs
takes precedence over capture groups.
The remaining replacements define properties on a "data" object that is passed in to the .gen()
method (in data
field)
For example, if the replacement string was <user.firstName>
and we have a "user" object, such as:
const user = { firstName: 'Albert', lastName: 'Einstein' }
When we generate a new string, we can call .gen( { data: { user } } )
to replace it with "Albert"
"options" is an object argument, with the following properties available (all are optional):
{
getCounter, setCounter, counterIncrement, counterInit, customReplacers, numberFormat
}
You can provide your own "Counter" function. By default, the Pattern Generator uses a simple internal counter, but it is re-initialised with each new instance of the generator, and there is no data persistence. If you require this (e.g. for a system that is generating ongoing serial numbers), you will need to provide a function to retrieve the current count value from your database or API.
This function takes no parameters and should return either a number or a string. (The exception to this is, when using a Generator function to yield the counter output, you can just return the IteratorYieldResult
directly, rather than having to wrap your Generator in a seperate function just to extract the .value
property.) Note, the "counter" doesn't have to be numerical -- you could have a system that generates a sequence of "AAA, AAB, AAC, etc...". As long as calling this function returns the appropriate value in the sequence, it's fine to use.
Ideally, your getCounter
function should also take care of incrementing the counter, so it can be called time after time and returns a new value each time. However, if this is not possible (i.e. it can only read a value), then you'll also have to provide a seperate function to update the value:
Only required if your getCounter()
function does not also take care of incrementing the counter, you'll need to provide another function to update it. If setCounter
exists, the Pattern Generator will call it with the new counter value (usually getCounter() + 1
) as its argument. Note that you should only use seperate get/set counter functions in an isolated system -- if your counter resides on a database that is accessed by multiple clients, you should retrieve and increment the counter atomically, or you'll likely run into concurrency issues.
By default, if using a custom setCounter
function, or the internal counter, counts will be incremented by +1
every time. It's possible to over-ride this by providing a incrementFunction()
function -- it takes the current counter value as input and should return the new value
Only relevant if using the internal counter (i.e. no getCounter
function is provided). This value simply specifies what number to start counting from -- the default is 1
.
Only relevant if using the internal counter (i.e. no getCounter
function is provided). Specify the step unit for the counter -- default is 1
. Non-integer values can be provided, but you're likely to run into binary precision problems fairly quickly. This is basically a simpler alternative to providing an incrementFunction
funtion (above) if all you want it to set a step value.
The Functions required to produce the output for the customReplacer strings (above). This parameter is a single object, where the keys are the names of the functions referenced in the pattern string, and the values are the functions themselves. Different arguments can be provided to these functions each time a new string is generated by the .gen()
method (see below).
The number output of the Counter can be formatted beyond the number of padding digits described above. If numberFormat
is provided, the counter will be displayed using the NumberFormat standard of Javascript's Intl object. The numberFormat
value must be a valid Intl.NumberFormat
object, e.g.
numberFormat: new Intl.NumberFormat('ja-JP', { style: 'currency', currency: 'JPY' }
which would render the counter value as a Japanese yen (¥) string.
Call the .gen()
method on the Pattern object to return a new string -- see examples in Usage above.
Note that .gen()
is Async, since it's expected that many of the custom functions used by the Pattern Generator (e.g. getCounter
, customReplacers, etc) are likely to be asynchronous functions.
The .gen()
method can take a single, optional parameter object, with the following fields:
{ shouldIncrement, customArgs, data }
If false
, the Pattern Generator will return a new string without incrementing the counter on this occasion. Note that this will only work with the internal (default) counter, or if using seperate get/set counter functions, since we can't override the internal behaviour of a getCounter
function that also auto-increments. (Default: true
)
If your customReplacer functions take arguments, then you supply the arguments to the .gen()
method here. customArgs
is an object with the same structure as customReplacers
(above), but instead of the values being functions, they are the arguments supplied to those functions.
The data to be used in data replacement fields.
If only one output string is required, you can generate it using the short-hand function instead of constructing and calling .gen()
on a Pattern object: patternGen(pattern, options, args)
E.g.
import { patternGen } from 'custom-string-patterns'
console.log(await patternGen(/[A-Z]{3}-<text>-<+dddd>/), { getCounter }, { data: { text: 'XXX' } })
// => PAL-XXX-005
Extended features of randexp can be access via the custom_string_patterns constructor options, namely:
defaultRangeAdd
,defaultRangeSubtract
: corresponds torandexp.defaultRange.add()
andrandexp.defaultRange.subtract()
, respectively. e.g.:
new Pattern(/<pattern>/, { defaultRangeAdd(0, 65535) } )
regexMax
: corresponds torandexp.max
, e.g.:
new Pattern(/<pattern>/, { regexMax: 10000 } )
npm/yarn scripts available from dev environment
test
-- run Jest test suitebuild
-- compile and build (to "build" folder)demo
-- run demo filedev
-- run dev file