regex-generator / index.html
danielNisnevich's picture
MVP 1
8bc8450 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Regex Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(135deg, #e0c3fc 0%, #8ec5fc 100%);
min-height: 100vh;
}
.card {
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
transition: all 0.3s ease;
}
.card:hover {
transform: translateY(-2px);
box-shadow: 0 15px 35px rgba(0, 0, 0, 0.15);
}
.input-box {
background: linear-gradient(90deg, rgba(245, 245, 245, 0.8) 0%, rgba(255, 255, 255, 0.9) 100%);
border-radius: 12px;
}
.btn-primary {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border: none;
transition: all 0.3s ease;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(118, 75, 162, 0.4);
}
.regex-output {
font-family: 'Courier New', monospace;
background: rgba(0, 0, 0, 0.05);
padding: 0.5rem;
border-radius: 6px;
word-break: break-all;
}
.match-highlight {
background-color: rgba(102, 126, 234, 0.2);
padding: 0.2rem;
border-radius: 4px;
border-left: 3px solid #667eea;
display: inline-block; /* For better spacing of multiple matches */
margin-right: 4px; /* Spacing between matches */
margin-bottom: 4px; /* Spacing if matches wrap */
}
</style>
</head>
<body class="flex items-center justify-center p-4 md:p-8">
<div class="w-full max-w-4xl space-y-6">
<div class="text-center mb-8">
<h1 class="text-3xl md:text-4xl font-bold text-white drop-shadow-lg">Regex Generator</h1>
<p class="text-white/90 mt-2">Transform your natural language requests into regular expressions</p>
</div>
<div class="card p-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-800">Chatbox</h2>
<button id="generateBtn" class="btn-primary px-4 py-2 rounded-lg font-medium flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11a1 1 0 10-2 0v3.586L7.707 9.293a1 1 0 00-1.414 1.414l3 3a1 1 0 001.414 0l3-3a1 1 0 00-1.414-1.414L11 10.586V7z" clip-rule="evenodd" />
</svg>
Generate
</button>
</div>
<textarea id="chatbox" placeholder="e.g. extract emails, find phone numbers, match dates" class="input-box w-full p-4 border border-gray-200 focus:border-purple-300 focus:ring-2 focus:ring-purple-200 outline-none transition duration-200 h-24"></textarea>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
<div class="card p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Regex Output</h2>
<div id="regexOutput" class="regex-output p-4 bg-gray-50 rounded-lg">/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/</div>
<div class="mt-4 flex space-x-3">
<button id="copyRegexBtn" class="btn-primary px-4 py-2 rounded-lg font-medium flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z" />
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z" />
</svg>
Copy
</button>
<button id="testRegexBtn" class="bg-white border border-gray-200 px-4 py-2 rounded-lg font-medium flex items-center hover:bg-gray-50 transition">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM9.555 7.168A1 1 0 008 8v4a1 1 0 001.555.832l3-2a1 1 0 000-1.664l-3-2z" clip-rule="evenodd" />
</svg>
Test
</button>
</div>
</div>
<div class="card p-6">
<h2 class="text-xl font-semibold text-gray-800 mb-4">Test Input</h2>
<textarea id="testInput" placeholder="Text to test your regex against..." class="input-box w-full p-4 border border-gray-200 focus:border-purple-300 focus:ring-2 focus:ring-purple-200 outline-none transition duration-200 h-28"></textarea>
<div class="mt-4">
<h3 class="font-medium text-gray-700 mb-2">Applied Regex Output:</h3>
<div id="appliedRegexOutput" class="p-3 bg-gray-50 rounded-lg min-h-[3rem]">
</div>
</div>
</div>
</div>
<div class="card p-6">
<div class="flex items-center justify-between mb-4">
<h2 class="text-xl font-semibold text-gray-800">Saved Regex Patterns</h2>
<button id="addPatternBtn" class="btn-primary px-3 py-1 rounded-lg font-medium flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M10 5a1 1 0 011 1v3h3a1 1 0 110 2h-3v3a1 1 0 11-2 0v-3H6a1 1 0 110-2h3V6a1 1 0 011-1z" clip-rule="evenodd" />
</svg>
</button>
</div>
<div id="savedPatterns" class="space-y-2">
</div>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
const generateBtn = document.getElementById('generateBtn');
const chatboxEl = document.getElementById('chatbox');
const regexOutputEl = document.getElementById('regexOutput');
const copyRegexBtn = document.getElementById('copyRegexBtn');
const testRegexBtn = document.getElementById('testRegexBtn');
const testInputEl = document.getElementById('testInput');
const appliedRegexOutputEl = document.getElementById('appliedRegexOutput');
const addPatternBtn = document.getElementById('addPatternBtn');
const savedPatternsEl = document.getElementById('savedPatterns');
// Generate button click handler
generateBtn.addEventListener('click', function() {
const chatboxValue = chatboxEl.value.trim();
if (chatboxValue) {
regexOutputEl.textContent = generateRegexFromPrompt(chatboxValue);
testRegexBtn.click(); // Automatically test after generating
} else {
chatboxEl.focus();
}
});
// Copy regex button
copyRegexBtn.addEventListener('click', function() {
const regex = regexOutputEl.textContent;
if (!regex) return;
navigator.clipboard.writeText(regex).then(() => {
const originalHTML = this.innerHTML; // Save current content
const btn = this;
btn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
Copied!
`;
setTimeout(() => {
btn.innerHTML = originalHTML; // Restore original content
}, 2000);
}).catch(err => {
console.error('Failed to copy regex: ', err);
alert('Failed to copy. Please copy manually.');
});
});
// Test regex button
testRegexBtn.addEventListener('click', function() {
const regexStringSource = regexOutputEl.textContent.trim();
const testInputValue = testInputEl.value;
appliedRegexOutputEl.innerHTML = ''; // Clear previous results
if (!regexStringSource) {
appliedRegexOutputEl.textContent = 'No regex to test.';
return;
}
try {
let pattern;
let flagsAttribute = ''; // Flags found in the /pattern/flags string
// Try to parse /pattern/flags format first
// This regex captures the pattern between slashes and any flags after the second slash.
// It expects at least one character for the pattern: /.+/
const regexParts = regexStringSource.match(/^\/(.+)\/([gimyus]*)$/);
if (regexParts) {
// Successfully parsed /pattern/flags (e.g., "/abc/gi" or "/xyz/")
pattern = regexParts[1]; // Content between slashes
flagsAttribute = regexParts[2]; // Flags after the last slash (can be empty)
} else if (regexStringSource.startsWith('/') && regexStringSource.endsWith('/') && regexStringSource.length >= 2) {
// Handles "/pattern/" case more explicitly if regexParts didn't match (e.g. empty pattern like "//")
// This extracts content between the first and last slash.
pattern = regexStringSource.substring(1, regexStringSource.length - 1);
// flagsAttribute remains ""
} else {
// Not in /.../... format (e.g. "abc" or "/abc" (no trailing slash))
// Treat the whole string as the pattern, no flags from string
pattern = regexStringSource;
}
if (pattern === "" && !regexStringSource.match(/^\/\/([gimyus]*)$/)) { // Allow empty pattern only if it was explicitly `//` or `///flags`
appliedRegexOutputEl.textContent = 'Pattern is effectively empty or malformed.';
return;
}
// Combine flags from string with a default 'g' for global matching
let finalFlags = flagsAttribute;
if (!finalFlags.includes('g')) {
finalFlags += 'g';
}
// Ensure unique flags and valid order (though order doesn't strictly matter for RegExp constructor)
finalFlags = Array.from(new Set(finalFlags.split(''))).join('');
const regex = new RegExp(pattern, finalFlags);
const matches = testInputValue.match(regex); // .match with 'g' flag returns array of matched strings or null
if (matches && matches.length > 0) {
matches.forEach(matchText => {
const span = document.createElement('span');
span.className = 'match-highlight';
span.textContent = matchText;
appliedRegexOutputEl.appendChild(span);
});
} else {
appliedRegexOutputEl.textContent = 'No matches found';
}
} catch (e) {
console.error("Regex error:", e);
appliedRegexOutputEl.textContent = 'Error: ' + e.message;
}
});
// Add pattern button
addPatternBtn.addEventListener('click', function() {
const regex = regexOutputEl.textContent.trim();
if (regex) {
const newPatternDiv = document.createElement('div');
// Add a specific class to identify these dynamically added items
newPatternDiv.className = 'p-3 bg-gray-50 rounded-lg flex justify-between items-center saved-pattern-item';
const regexSpan = document.createElement('span');
regexSpan.className = 'regex-output';
regexSpan.textContent = regex;
const controlsDiv = document.createElement('div');
controlsDiv.className = 'flex space-x-2';
const useBtn = document.createElement('button');
useBtn.className = 'p-1 text-gray-500 hover:text-purple-600 use-pattern-btn';
useBtn.title = "Use Pattern";
useBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
</svg>`;
const deleteBtn = document.createElement('button');
deleteBtn.className = 'p-1 text-gray-500 hover:text-red-500 delete-pattern-btn';
deleteBtn.title = "Delete Pattern";
deleteBtn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clip-rule="evenodd" />
</svg>`;
controlsDiv.appendChild(useBtn);
controlsDiv.appendChild(deleteBtn);
newPatternDiv.appendChild(regexSpan);
newPatternDiv.appendChild(controlsDiv);
savedPatternsEl.appendChild(newPatternDiv);
}
});
// Event delegation for dynamically added "Use" and "Delete" buttons in saved patterns
savedPatternsEl.addEventListener('click', function(event) {
const button = event.target.closest('button'); // Get the button element
if (!button) return; // Exit if the click was not on a button or its child
const patternItem = button.closest('.saved-pattern-item');
if (!patternItem) return; // Exit if not part of a saved pattern item
const regexText = patternItem.querySelector('.regex-output').textContent;
if (button.classList.contains('use-pattern-btn')) {
regexOutputEl.textContent = regexText;
testInputEl.focus(); // Focus input for convenience
testRegexBtn.click(); // Automatically test when a saved pattern is used
} else if (button.classList.contains('delete-pattern-btn')) {
patternItem.remove();
}
});
// Simple regex generator for demo purposes
function generateRegexFromPrompt(prompt) {
prompt = prompt.toLowerCase();
if (prompt.includes('email') || prompt.includes('e-mail')) {
return '/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/';
} else if (prompt.includes('phone') || prompt.includes('number')) {
return '/(?:\\+?\\d{1,3}[-.\\s]?)?(?:\\(?\\d{3}\\)?[-.\\s]?)?\\d{3}[-.\\s]?\\d{4}/';
} else if (prompt.includes('url') || prompt.includes('website') || prompt.includes('web address')) {
return '/https?:\/\/(?:www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%._\\+.~#?&//=]*)/';
} else if (prompt.includes('date')) {
return '/(?:\\d{1,2}[\\/\\-.](?:0?[1-9]|1[0-2])[\\/\\-.]\\d{2,4})|(?:\\d{4}[\\/\\-.](?:0?[1-9]|1[0-2])[\\/\\-.]\\d{1,2})|(?:(?:Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\\s\\d{1,2}(?:st|nd|rd|th)?,?\\s\\d{4})/i';
} else if (prompt.includes('ip address') || prompt.includes('ipv4')) {
return '/\\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\b/';
} else if (prompt.includes('hex color') || prompt.includes('hexcode')) {
return '/#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})\\b/';
} else if (prompt.includes('html tag')) {
return '/<([a-z][a-z0-9]*)\\b[^>]*>(.*?)<\\/\\1>/is';
}
else {
// Default regex for extraction - any sequence of word characters
return '/\\b\\w+\\b/g';
}
}
// Set an example in testInput for initial demo if it's empty
if (!testInputEl.value) {
testInputEl.value = "My email is user@example.com, test.user@sub.example.org.\nMy site is http://example.com and https://www.another-site.co.uk/path?query=value.\nCall +1 (555) 123-4567 or 555-123-1234.\nDates: 12/31/2025, 2024-01-15, Feb 14th, 2023.\nIP: 192.168.1.1. Color: #F00 or #A2B3C4.\nTags: <p>Hello</p> <div>World</div>";
}
// Clear any hardcoded initial placeholder content in appliedRegexOutput
appliedRegexOutputEl.innerHTML = '';
// Perform an initial test if there's default regex and test input
if (regexOutputEl.textContent && testInputEl.value) {
testRegexBtn.click();
}
});
</script>
</body>
</html>