# spintax-py

A combinatorial string generation library that creates all possible combinations from templates with variable elements.

This is a Python implementation of the [spintax](https://github.com/johnhenry/spintax) JavaScript library, providing identical functionality for generating string variations using pattern syntax.

## Installation

Install from PyPI:

```bash
pip install spintaxpy
```

Or install from source:

```bash
pip install .
```

## Basic Usage

```python
from spintaxpy import parse

# Basic usage with choices
variations = parse("Hello, {world|friend|universe}!")

# Iterate through all combinations
for variation in variations:
    print(variation)
# Output:
# Hello, world!
# Hello, friend!
# Hello, universe!

# With numerical ranges
counts = parse("Count: {1,5}")
# Generates: "Count: 1", "Count: 2", "Count: 3", "Count: 4", "Count: 5"

# Multiple variables
products = parse("{Product|Service} #{1,3} ({Standard|Premium})")
# Generates all combinations: "Product #1 (Standard)", "Product #1 (Premium)", etc.

# Back references
associations = parse("The {blue|straw|rasp}berries taste like {$0}berries")
# Generates: "The blueberries taste like blueberries", 
#            "The strawberries taste like strawberries", 
#            "The raspberries taste like raspberries"
```

## Key Features

- **String pattern parsing**: Create string variations using intuitive pattern syntax
- **Combinatorial expansion**: Generate all possible combinations of variable elements
- **Memory-efficient**: Uses Python generators for lazy evaluation
- **Multiple variable types**: Choose from predefined options or numerical ranges
- **Back references**: Reference previously selected choices within the same template

## Pattern Syntax

### Choices

Define choices with the pipe (`|`) separator:

```python
parse("Color: {red|green|blue}")
# Generates: "Color: red", "Color: green", "Color: blue"
```

### Ranges

Define ranges with comma-separated numbers:

```python
parse("Value: {1,5}")
# Generates: "Value: 1", "Value: 2", "Value: 3", "Value: 4", "Value: 5"

# With step value
parse("Value: {0,10,2}")
# Generates: "Value: 0", "Value: 2", "Value: 4", "Value: 6", "Value: 8", "Value: 10"
```

### Pattern Combinations

Multiple patterns in a template will generate all possible combinations:

```python
parse("{small|large} {box|circle} in {red|blue}")
# Generates 8 combinations of size, shape and color
```

### Back References

Reference previous choices using `{$n}` where n is the 0-based index:

```python
parse("The {blue|straw|rasp}berries taste like {$0}berries")
# $0 refers to the first choice (blue|straw|rasp)
```

## Whitespace Handling

- In **range patterns** (containing numbers and commas), all whitespace is ignored
  - `{1,5}` is the same as `{ 1, 5 }`
- In **choices patterns** (with `|` separators), whitespace is preserved as part of the option
  - `{option1|option2}` is different from `{option1 |option2}`
  - In the second case, the first option is "option1 " (with a trailing space)

## Advanced Examples

### URL Generation

```python
parse("https://example.com/{products|users}/{1,5}")
# Generates:
# https://example.com/products/1
# https://example.com/products/2
# ...
# https://example.com/users/5
```

### API Testing

```python
parse("/api/v{1,3}/{users|items}/{1,100,10}")
# Generates:
# /api/v1/users/1
# /api/v1/users/11
# ...
# /api/v3/items/91
```

### Configuration Generation

```python
parse('{"timeout": {1000,5000,1000}, "retries": {1,3}, "mode": "{strict|lenient}"}')
# Generates valid JSON strings with all combinations of parameters
```

## Iteration Order

When multiple patterns are in a template, combinations are generated by iterating through the rightmost pattern first (inner loop), then the next one to the left, and so on:

```python
list(parse("The color is {red|green|blue}. The letter is {A|B|C}"))

# Produces:
# The color is red. The letter is A
# The color is red. The letter is B
# The color is red. The letter is C
# The color is green. The letter is A
# The color is green. The letter is B
# The color is green. The letter is C
# The color is blue. The letter is A
# The color is blue. The letter is B
# The color is blue. The letter is C
```

## API Reference

### `parse(template, **options)`

Parses a template string with special patterns and returns a generator.

**Parameters:**
- `template` (str): Template string with `{...}` patterns
- `pattern_start` (str, optional): Pattern start delimiter (default: `'{'`)
- `pattern_end` (str, optional): Pattern end delimiter (default: `'}'`)
- `separator_range` (str, optional): Separator for range patterns (default: `','`)
- `separator_choices` (str, optional): Separator for choices patterns (default: `'|'`)
- `back_reference_marker` (str, optional): Marker for back references (default: `'$'`)

**Returns:** Iterator of all pattern combinations

**Example:**
```python
for result in parse('Count: {1,5}'):
    print(result)
```

### `count(template, **options)`

Counts the number of combinations for a given string template.

**Parameters:** Same as `parse()`

**Returns:** Total number of combinations (int)

**Example:**
```python
from spintaxpy import count

count("Hello!")  # 1
count("Hello {world|people|nurse}!")  # 3
count("{small|large} {box|circle}")  # 4
```

### `choose(template, **options)`

Creates a function that returns a single combination (random or by index).

**Parameters:** Same as `parse()`

**Returns:** Function that accepts indices and returns a single combination

**Example:**
```python
from spintaxpy import choose

picker = choose("The {red|blue|green} {box|circle}")
picker()  # Random combination like "The red box"
picker(0, 1)  # Specific combination "The red circle"
picker(2, 0)  # "The green box"
```

### `spintax_range(start, end, step=1, include_end=True)`

Creates a generator that yields numbers within a specified range.

**Parameters:**
- `start` (float): Starting value (inclusive)
- `end` (float): Ending value (inclusive)
- `step` (float, optional): Increment between values (default: 1)
- `include_end` (bool, optional): Always include the end value (default: True)

**Returns:** RangeGenerator instance

**Example:**
```python
from spintaxpy import spintax_range

list(spintax_range(1, 5))  # [1, 2, 3, 4, 5]
list(spintax_range(0, 10, 3, True))  # [0, 3, 6, 9, 10]
```

## Performance Considerations

### Combinatorial Explosion

Be cautious when creating templates with many variable parts, as the number of combinations can grow exponentially:

- 3 variables with 4 options each: 4³ = 64 combinations
- 5 variables with 4 options each: 4⁵ = 1,024 combinations
- 10 variables with 4 options each: 4¹⁰ = 1,048,576 combinations

### Processing Large Result Sets

When dealing with extremely large sets of combinations, use the generator directly instead of converting to a list:

```python
# Good - processes one at a time
for combination in parse("..."):
    process(combination)

# Bad - loads all into memory
all_combinations = list(parse("..."))
```

## Best Practices

1. **Limit Pattern Count**: Keep the number of variable patterns reasonable to avoid combinatorial explosion
2. **Use Meaningful Options**: Create patterns that produce meaningful variations
3. **Process Lazily**: Use the generator directly when dealing with large result sets
4. **Test First**: Use `count()` to check the number of combinations before generating them all

## Examples

```python
from spintaxpy import parse, count, choose

# Example 1: Simple greeting variations
greetings = list(parse("Hello, {world|friend|universe}!"))
print(greetings)
# ['Hello, world!', 'Hello, friend!', 'Hello, universe!']

# Example 2: Check combination count first
template = "{small|medium|large} {red|blue|green} {box|circle|triangle}"
print(f"This will generate {count(template)} combinations")
# This will generate 27 combinations

# Example 3: Generate specific combination
picker = choose("Size: {S|M|L|XL}, Color: {Red|Blue|Green}")
print(picker(1, 2))  # Size: M, Color: Green

# Example 4: URL variations for testing
urls = parse("https://api.example.com/v{1,3}/{users|products|orders}")
for url in urls:
    print(url)
# https://api.example.com/v1/users
# https://api.example.com/v1/products
# https://api.example.com/v1/orders
# ...

# Example 5: Back references for consistency
sentences = list(parse("I {love|hate} this. Yes, I really {$0} it!"))
print(sentences)
# ['I love this. Yes, I really love it!', 'I hate this. Yes, I really hate it!']
```

## Comparison with JavaScript Version

This Python implementation provides identical functionality to the original [JavaScript spintax library](https://github.com/johnhenry/spintax). The main differences are:

- Uses Python generators instead of JavaScript iterators
- Function names follow Python conventions (e.g., `spintax_range` instead of just `range` to avoid conflicts)
- Uses keyword arguments for options instead of object parameters
- The `compile` tagged template function is not included (not applicable in Python)

All core functionality including `parse`, `count`, `choose`, and `range` work identically to their JavaScript counterparts.

## License

MIT
