Skip to content

Commit 0cf7e80

Browse files
committed
Adding a new feature of diagram generation for the regex
- In beta development
1 parent 0f70dea commit 0cf7e80

File tree

7 files changed

+617
-8
lines changed

7 files changed

+617
-8
lines changed

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,14 @@ Explain, test, and generate examples for regular expressions.
1212
- Human-readable, line-by-line explanations of regex patterns
1313
- Example string generation for any regex
1414
- Detailed match testing with feedback
15+
- Visual railroad diagrams for regex patterns
1516
- Both a Python API and a CLI
1617

1718
## Features
1819
- **Regex Explanation:** Get clear, context-aware explanations for any regex pattern
1920
- **Test Regex:** Test if a string matches a pattern and see why/why not
2021
- **Generate Examples:** Generate example strings that match a regex
22+
- **Visual Diagrams:** Generate railroad diagrams to visualize regex patterns
2123
- **CLI & API:** Use from the command line or as a Python library
2224
- **Regex Flags:** Supports Python regex flags (e.g., `re.IGNORECASE`)
2325

@@ -46,14 +48,25 @@ Test if a string matches a pattern:
4648
rexplain test "^hello.*" "hello world!"
4749
```
4850

51+
Generate a railroad diagram:
52+
```bash
53+
rexplain diagram "^\\w+$" --output diagram.svg
54+
rexplain diagram "^\\w+$" --detailed --output detailed.svg
55+
```
56+
4957
### Python API Usage
5058

5159
```python
52-
from rexplain import explain, examples, test
60+
from rexplain import explain, examples, test, diagram
5361

5462
print(explain(r"\d+"))
5563
print(examples(r"[A-Z]{2}\d{2}", count=2))
5664
print(test(r"foo.*", "foobar"))
65+
66+
# Generate diagrams
67+
diagram(r"^\w+$", "simple.svg")
68+
diagram(r"^\w+$", "detailed.svg", detailed=True)
69+
svg_content = diagram(r"^\w+$") # Returns SVG as string
5770
```
5871

5972
#### Example: Detailed Explanation
@@ -82,6 +95,9 @@ Generates example strings that match the pattern.
8295
### `test(pattern: str, test_string: str, flags: int = 0) -> dict`
8396
Tests if a string matches the pattern and explains why/why not.
8497

98+
### `diagram(pattern: str, output_path: str = None, detailed: bool = False) -> str`
99+
Generates a railroad diagram for the regex pattern. Returns SVG content or saves to file.
100+
85101
## Contributing
86102

87103
Contributions are welcome! To contribute:
@@ -90,10 +106,6 @@ Contributions are welcome! To contribute:
90106
- Run tests
91107
- Open a pull request
92108

93-
## License
94-
95-
MIT
96-
97109
## Running Tests & Coverage
98110

99111
To run all tests with coverage (threshold: 90%):
@@ -104,3 +116,7 @@ pytest
104116
```
105117

106118
If coverage is below 90%, pytest will fail. Coverage details will be shown in the terminal.
119+
120+
## License
121+
122+
MIT

docs/examples.md

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,56 @@ print(examples(r"[A-Z]{2}\d{2}", count=2))
2121
```python
2222
from rexplain import test
2323
print(test(r"foo.*", "foobar"))
24+
```
25+
26+
## Railroad Diagrams
27+
28+
### Basic Diagram Generation
29+
30+
```python
31+
from rexplain import diagram
32+
33+
# Generate a simple diagram
34+
svg_content = diagram(r"^\w+$")
35+
print(svg_content[:100]) # Show first 100 characters
36+
37+
# Save diagram to file
38+
diagram(r"^\w+$", "simple_diagram.svg")
39+
```
40+
41+
### Detailed Diagram Generation
42+
43+
```python
44+
from rexplain import diagram
45+
46+
# Generate a detailed diagram based on parsed components
47+
diagram(r"^\w+@\w+\.\w+$", "email_diagram.svg", detailed=True)
48+
```
49+
50+
### CLI Usage
51+
52+
```bash
53+
# Generate basic diagram
54+
rexplain diagram "^\\w+$" --output basic.svg
55+
56+
# Generate detailed diagram
57+
rexplain diagram "^\\w+@\\w+\\.\\w+$" --detailed --output email.svg
58+
59+
# Print SVG to stdout
60+
rexplain diagram "^\\d{3}-\\d{2}-\\d{4}$"
61+
```
62+
63+
### Complex Pattern Examples
64+
65+
```python
66+
from rexplain import diagram
67+
68+
# Email validation pattern
69+
diagram(r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$", "email_validation.svg")
70+
71+
# Phone number pattern
72+
diagram(r"^\(\d{3}\) \d{3}-\d{4}$", "phone_number.svg")
73+
74+
# Password validation pattern
75+
diagram(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[A-Za-z\d]{8,}$", "password_validation.svg")
2476
```

pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@ authors = [{name = "Dev B. Makwana"}]
1010
license = {text = "MIT"}
1111
readme = "README.md"
1212
requires-python = ">=3.8"
13+
dependencies = [
14+
"pyrailroad>=0.4.0"
15+
]
1316
classifiers = [
1417
"Development Status :: 5 - Production/Stable",
1518
"Intended Audience :: Developers",

src/rexplain/__init__.py

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
__version__ = "0.3.1"
1+
__version__ = "0.3.2"
22

33
from .core.explainer import RegexExplainer
44
from .core.generator import ExampleGenerator
55
from .core.tester import RegexTester
6+
from .core.diagram import generate_railroad_diagram, generate_detailed_railroad_diagram
67

78
def explain(pattern: str, flags: int = 0) -> str:
89
r"""
@@ -58,4 +59,29 @@ def test(pattern: str, test_string: str, flags: int = 0):
5859
MatchResult(matches=True, reason='Full match.', ...)
5960
"""
6061
result = RegexTester().test(pattern, test_string, flags=flags)
61-
return result
62+
return result
63+
64+
65+
def diagram(pattern: str, output_path: str = None, detailed: bool = False) -> str:
66+
r"""
67+
Generate a railroad diagram for a regex pattern.
68+
69+
Args:
70+
pattern (str): The regex pattern to visualize.
71+
output_path (str, optional): Path to save the SVG file. If None, returns SVG content.
72+
detailed (bool, optional): Whether to generate a detailed diagram based on parsed components. Defaults to False.
73+
74+
Returns:
75+
str: SVG content as string if output_path is None, otherwise the file path.
76+
77+
Example:
78+
>>> diagram(r"^\w+$", "diagram.svg")
79+
'diagram.svg'
80+
>>> svg_content = diagram(r"^\w+$")
81+
>>> print(svg_content[:100])
82+
'<svg xmlns="http://www.w3.org/2000/svg" ...'
83+
"""
84+
if detailed:
85+
return generate_detailed_railroad_diagram(pattern, output_path)
86+
else:
87+
return generate_railroad_diagram(pattern, output_path)

src/rexplain/cli/main.py

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from rexplain.core.explainer import RegexExplainer
88
from rexplain.core.generator import ExampleGenerator
99
from rexplain.core.tester import RegexTester
10+
from rexplain.core.diagram import generate_railroad_diagram, generate_detailed_railroad_diagram
1011
from rexplain import __version__
1112
except ImportError as e:
1213
print("IMPORT ERROR:", e, file=sys.stderr)
@@ -20,6 +21,10 @@ def generate(self, pattern, count=3):
2021
class RegexTester:
2122
def test(self, pattern, string):
2223
return type('Result', (), {"matches": True, "reason": "[Stub] Always matches", "to_dict": lambda self: {"matches": True, "reason": "[Stub] Always matches"}})()
24+
def generate_railroad_diagram(pattern, output_path=None):
25+
return f"[Stub] Diagram for: {pattern}"
26+
def generate_detailed_railroad_diagram(pattern, output_path=None):
27+
return f"[Stub] Detailed diagram for: {pattern}"
2328
__version__ = "unknown"
2429

2530
PROJECT_ABOUT = (
@@ -31,7 +36,7 @@ def test(self, pattern, string):
3136
def main():
3237
parser = argparse.ArgumentParser(
3338
description='rexplain: Regex explanation toolkit',
34-
epilog='Examples:\n rexplain explain "^\\d{3}-\\d{2}-\\d{4}$" --examples 2\n rexplain test "foo.*" "foobar"\n rexplain --version\n rexplain --about',
39+
epilog='Examples:\n rexplain explain "^\\d{3}-\\d{2}-\\d{4}$" --examples 2\n rexplain test "foo.*" "foobar"\n rexplain diagram "^\\w+$" --output diagram.svg\n rexplain --version\n rexplain --about',
3540
formatter_class=argparse.RawTextHelpFormatter
3641
)
3742
parser.add_argument('--version', action='store_true', help='Show version and exit')
@@ -54,6 +59,12 @@ def main():
5459
test_parser.add_argument('pattern', help='Regex pattern to test')
5560
test_parser.add_argument('string', help='String to test against the pattern')
5661

62+
# rexplain diagram "pattern" --output diagram.svg
63+
diagram_parser = subparsers.add_parser('diagram', help='Generate a railroad diagram for a regex pattern')
64+
diagram_parser.add_argument('pattern', help='Regex pattern to visualize')
65+
diagram_parser.add_argument('--output', '-o', help='Output file path for SVG (default: print to stdout)')
66+
diagram_parser.add_argument('--detailed', '-d', action='store_true', help='Generate detailed diagram based on parsed components')
67+
5768
args = parser.parse_args()
5869

5970
# Handle global flags
@@ -90,6 +101,17 @@ def main():
90101
output = result.to_dict() if hasattr(result, 'to_dict') else result
91102
print(output)
92103
sys.exit(0 if getattr(result, 'matches', False) else 1)
104+
elif args.command == 'diagram':
105+
if args.detailed:
106+
result = generate_detailed_railroad_diagram(args.pattern, args.output)
107+
else:
108+
result = generate_railroad_diagram(args.pattern, args.output)
109+
110+
if not args.output:
111+
print(result)
112+
else:
113+
print(f"Diagram saved to: {result}")
114+
sys.exit(0)
93115
else:
94116
parser.print_help()
95117
sys.exit(1)

0 commit comments

Comments
 (0)