Compare commits

6 Commits
v1.0.0 ... main

Author SHA1 Message Date
6d963949b5 fix: resolve CI/CD issues and linting errors
Some checks failed
CI / test (push) Failing after 3h0m45s
CI / build (push) Has been cancelled
2026-02-06 01:26:35 +00:00
7badbed0cb fix: resolve CI/CD issues and linting errors
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
2026-02-06 01:26:34 +00:00
04e4ef362e fix: resolve CI/CD issues and linting errors
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
2026-02-06 01:26:34 +00:00
d1ccf5919a fix: resolve CI/CD issues and linting errors
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
2026-02-06 01:26:33 +00:00
f1b542f93c fix: resolve CI/CD issues and linting errors
Some checks failed
CI / test (push) Has been cancelled
CI / build (push) Has been cancelled
2026-02-06 01:26:33 +00:00
ac24bb48dd fix: resolve CI/CD issues and linting errors
Some checks failed
CI / build (push) Has been cancelled
CI / test (push) Has been cancelled
2026-02-06 01:26:33 +00:00
6 changed files with 64 additions and 41 deletions

View File

@@ -0,0 +1,38 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test:
runs-on: ubuntu-latest
timeout: 600
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
cache: 'pip'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install -e .
python -m pip install pytest pytest-cov ruff
- name: Run tests
run: python -m pytest tests/ -v --tb=short
- name: Run linting
run: python -m ruff check regex_humanizer/
- name: Run type checking
run: python -m pip install mypy && python -m mypy regex_humanizer/ --ignore-missing-imports

View File

@@ -1,13 +1,11 @@
"""Command-line interface for Regex Humanizer."""
import json
import sys
from typing import Optional
import click
from .parser import parse_regex
from .translator import translate_regex
from .test_generator import generate_test_cases
from .flavors import get_flavor_manager, get_available_flavors
from .flavors import get_flavor_manager
from .interactive import start_interactive_mode
@@ -194,7 +192,7 @@ def validate(ctx: click.Context, pattern: str, flavor: str):
click.echo(f"AST node count: {len(get_all_nodes(ast))}")
except Exception as e:
click.echo(f"\nPattern: {pattern}")
click.echo(f"Validation: FAILED")
click.echo("Validation: FAILED")
click.echo(f"Error: {e}")
sys.exit(1)

View File

@@ -1,9 +1,5 @@
"""Interactive REPL mode for exploring regex patterns."""
import sys
import os
from typing import Optional
from .parser import parse_regex
from .translator import translate_regex
from .test_generator import generate_test_cases
from .flavors import get_flavor_manager
@@ -51,14 +47,14 @@ class InteractiveSession:
os.makedirs(os.path.dirname(self.history_file), exist_ok=True)
with open(self.history_file, 'w') as f:
for cmd in self.history[-1000:]:
f.write(cmd + '\\n')
f.write(cmd + '\n')
except Exception:
pass
def run(self):
"""Run the interactive session."""
print("\\nRegex Humanizer - Interactive Mode")
print("Type 'help' for available commands, 'quit' to exit.\\n")
print("\nRegex Humanizer - Interactive Mode")
print("Type 'help' for available commands, 'quit' to exit.\n")
while True:
try:
@@ -79,7 +75,7 @@ class InteractiveSession:
self._process_command(user_input.strip())
except (KeyboardInterrupt, EOFError):
print("\\nGoodbye!")
print("\nGoodbye!")
break
def _process_command(self, command: str):
@@ -148,10 +144,10 @@ Examples:
result = translate_regex(pattern, self.flavor)
header = f"Pattern: {pattern}"
print("\\n" + "=" * (len(header)))
print("\n" + "=" * (len(header)))
print(header)
print("=" * (len(header)))
print("\\nEnglish Explanation:")
print("\nEnglish Explanation:")
print("-" * (len(header)))
print(result)
print()
@@ -170,17 +166,17 @@ Examples:
result = generate_test_cases(pattern, self.flavor, 3, 3)
header = f"Pattern: {pattern}"
print("\\n" + "=" * (len(header)))
print("\n" + "=" * (len(header)))
print(header)
print("=" * (len(header)))
print(f"\\nFlavor: {self.flavor}")
print(f"\nFlavor: {self.flavor}")
print("\\nMatching strings:")
print("\nMatching strings:")
print("-" * (len(header)))
for i, s in enumerate(result["matching"], 1):
print(f" {i}. {s}")
print("\\nNon-matching strings:")
print("\nNon-matching strings:")
print("-" * (len(header)))
for i, s in enumerate(result["non_matching"], 1):
print(f" {i}. {s}")
@@ -266,17 +262,17 @@ Examples:
def _cmd_example(self, args: str):
"""Show an example pattern."""
examples = [
r"^\\d{3}-\\d{4}$",
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
r"^(?:http|https)://[^\\s]+$",
r"\\b\\d{4}-\\d{2}-\\d{2}\\b",
r"(?i)(hello|hi|greetings)\\s+world!?",
r"^\d{3}-\d{4}$",
r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$",
r"^(?:http|https)://[^\s]+$",
r"\b\d{4}-\d{2}-\d{2}\b",
r"(?i)(hello|hi|greetings)\s+world!?",
]
import random
example = random.choice(examples)
print(f"\\nExample pattern: {example}")
print("\\nType: explain " + example)
print(f"\nExample pattern: {example}")
print("\nType: explain " + example)
print("Type: test " + example)
print()

View File

@@ -1,6 +1,4 @@
"""Regex parser for converting regex patterns to AST nodes."""
from typing import Optional, Union, Any
from typing import Optional, Any
from dataclasses import dataclass, field
from enum import Enum
@@ -355,7 +353,7 @@ class RegexParser:
self.pos = end + 1
return RegexNode(
node_type=NodeType.UNICODE_PROPERTY,
raw=f'\\p{{{prop}}}')
raw=f'\\p{{{prop}}}',
position=self.pos - len(f'\\p{{{prop}}}')
)
@@ -558,7 +556,6 @@ class RegexParser:
is_non_capturing=False
)
elif next_char in 'iDsx':
flag = next_char
self.pos += 1
children = self._parse_sequence()
return RegexNode(
@@ -592,7 +589,6 @@ class RegexParser:
if char in '*+?':
self.pos += 1
quant_type = char
if char == '*':
min_count = 0
max_count = float('inf')
@@ -634,7 +630,6 @@ class RegexParser:
is_possessive = True
self.pos += 1
quant_type = '{' + quant_content + '}'
else:
return None

View File

@@ -1,8 +1,6 @@
"""Test case generator for regex patterns."""
import random
import string
from typing import Optional, Callable
from typing import Optional
from .parser import parse_regex, RegexNode, NodeType
@@ -120,12 +118,13 @@ class TestCaseGenerator:
"""Generate strings that do NOT match the pattern."""
try:
ast = parse_regex(pattern, self.flavor)
return self._generate_non_matching_from_ast(ast, count, max_length)
return self._generate_non_matching_from_ast(pattern, ast, count, max_length)
except Exception:
return self._generate_fallback_non_matching(pattern, count)
def _generate_non_matching_from_ast(
self,
pattern: str,
node: RegexNode,
count: int,
max_length: int

View File

@@ -1,6 +1,3 @@
"""Translator for converting regex AST to human-readable English."""
from typing import Optional
from .parser import (
RegexNode, NodeType, LiteralNode, CharacterClassNode,
QuantifierNode, GroupNode, RegexParser
@@ -282,7 +279,7 @@ class RegexTranslator:
def _translate_backreference(self, node: RegexNode) -> str:
"""Translate a backreference."""
return f"same as capture group \\\{node.raw}"
return f"same as capture group \\{node.raw}"
def translate_regex(pattern: str, flavor: str = "pcre") -> str: