Initial release of CaTML (Cat Altered Text Markup Language) compiler and runtime
This commit is contained in:
commit
2adbc60fe7
5 changed files with 1135 additions and 0 deletions
151
README.md
Normal file
151
README.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
# CaTML (Cat Altered Text Markup Language) 🐾
|
||||
|
||||
> Standard HTML is for humans. **CaTML** is for fine felines.
|
||||
|
||||
CaTML is a lightweight, zero-dependency HTML compiler and runtime written in JavaScript that translates feline markup tags and attributes directly into standard semantic HTML.
|
||||
|
||||
Repository: [https://git.p20.dev/christian/CaTML.git](https://git.p20.dev/christian/CaTML.git)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Quick Start
|
||||
|
||||
### 1. In HTML with `<script type="text/catml">`
|
||||
|
||||
Simply load `catml.js` and write your CaTML code directly:
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>My Cat Page</title>
|
||||
<script src="catml.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/catml">
|
||||
<box breed="container">
|
||||
<meow1>🐾 Welcome to My Website</meow1>
|
||||
<purr>This page is written in pure <claw>CaTML</claw>!</purr>
|
||||
<bap on-bap="alert('PURRR!')">Bap Me</bap>
|
||||
</box>
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
### 2. Standalone Page (`<catworld>`)
|
||||
|
||||
You can even write the entire document root in CaTML:
|
||||
|
||||
```html
|
||||
<catworld>
|
||||
<whiskers>
|
||||
<collar>Kitty Portal</collar>
|
||||
<script src="catml.js"></script>
|
||||
</whiskers>
|
||||
<fur>
|
||||
<box>
|
||||
<meow1>Welcome!</meow1>
|
||||
<purr>The world is feline now.</purr>
|
||||
</box>
|
||||
</fur>
|
||||
</catworld>
|
||||
```
|
||||
|
||||
### 3. JavaScript API
|
||||
|
||||
```javascript
|
||||
// Compile to HTML string
|
||||
const html = CatML.compile('<box><purr>Meow!</purr></box>');
|
||||
console.log(html); // <div><p>Meow!</p></div>
|
||||
|
||||
// Render directly into a DOM element
|
||||
CatML.render('<box><purr>Meow!</purr></box>', document.getElementById('app'));
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📖 Feline Dictionary
|
||||
|
||||
### Core Tags
|
||||
|
||||
| CaTML Tag | HTML Tag | Feline Concept / Description |
|
||||
|---|---|---|
|
||||
| `<catworld>` / `<catml>` | `<html>` | The whole cat universe |
|
||||
| `<whiskers>` | `<head>` | Metadata & sensory inputs |
|
||||
| `<collar>` | `<title>` | Identification |
|
||||
| `<fur>` | `<body>` | The visible body |
|
||||
| `<box>` / `<cardboard>` | `<div>` | If it fits, I sits (container) |
|
||||
| `<feather>` / `<mouse>` | `<span>` | Small things to chase |
|
||||
| `<meow1>` ... `<meow6>` | `<h1>` ... `<h6>` | Headings (from roar to tiny squeak) |
|
||||
| `<purr>` | `<p>` | Steady purring paragraph |
|
||||
| `<scratch>` | `<a>` | Hyperlink (scratch and jump) |
|
||||
| `<stare>` | `<img>` | Image (what the cat stares at) |
|
||||
| `<bap>` / `<paw>` | `<button>` | Action button (bap items off tables) |
|
||||
| `<sniff>` | `<input>` | Input field |
|
||||
| `<yowl>` | `<textarea>` | Multi-line text field |
|
||||
| `<bowl>` | `<ul>` | Food bowl / list |
|
||||
| `<numbered-bowl>` | `<ol>` | Ordered list |
|
||||
| `<treat>` | `<li>` | Single treat / list item |
|
||||
| `<claw>` | `<strong>` / `<b>` | Sharp emphasis |
|
||||
| `<hiss>` | `<em>` / `<i>` | Italic hissed tone |
|
||||
| `<catnip>` | `<script>` | JavaScript (gives zoomies!) |
|
||||
| `<groom>` | `<style>` | CSS styling |
|
||||
| `<litterbox>` | `<footer>` | Bottom footer |
|
||||
| `<nap>` | `<hr>` | Rest / horizontal rule |
|
||||
| `<door>` | `<form>` | Form container |
|
||||
| `<curtain>` | `<header>` | Page header |
|
||||
| `<shelf>` | `<nav>` | Navigation bar |
|
||||
| `<hammock>` | `<main>` | Main content |
|
||||
| `<perch>` | `<section>` | Section |
|
||||
|
||||
### Attributes
|
||||
|
||||
| CaTML Attribute | HTML Attribute | Description |
|
||||
|---|---|---|
|
||||
| `catch="..."` | `href="..."` | Destination URL to chase |
|
||||
| `prey="..."` | `src="..."` | Source image URL |
|
||||
| `scent="..."` | `alt="..."` | Alt text / scent profile |
|
||||
| `tag="..."` | `id="..."` | ID attribute |
|
||||
| `breed="..."` | `class="..."` | CSS class attribute |
|
||||
| `fluff="..."` | `style="..."` | Inline CSS styles |
|
||||
| `on-bap="..."` | `onclick="..."` | Click event listener |
|
||||
| `on-sniff="..."` | `oninput="..."` | Input event listener |
|
||||
| `on-change="..."` | `onchange="..."` | Change event listener |
|
||||
| `on-stalk="..."` | `onmouseover="..."` | Mouseover event listener |
|
||||
| `on-pounce="..."` | `onmousedown="..."` | Mousedown event listener |
|
||||
| `treat-type="..."` | `type="..."` | Input type (`text`, `number`, etc.) |
|
||||
| `kibble="..."` | `value="..."` | Input value |
|
||||
| `curiosity="..."` | `placeholder="..."` | Placeholder text |
|
||||
| `collar-name="..."` | `name="..."` | Name attribute |
|
||||
|
||||
---
|
||||
|
||||
## 🎮 Interactive Playground
|
||||
|
||||
Open `index.html` in any browser to experience the **CaTML Playground**:
|
||||
- Real-time syntax compiler
|
||||
- Split view: Live preview & compiled raw HTML
|
||||
- Pre-built templates:
|
||||
- *Welcome to CaTML*
|
||||
- *Interactive Bap Counter*
|
||||
- *Kitten Adoption Form*
|
||||
- *Sir Pounce-a-Lot Portfolio*
|
||||
- Built-in interactive feline vocabulary modal
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Testing
|
||||
|
||||
Run the automated test suite with Node.js:
|
||||
|
||||
```bash
|
||||
node test/test.js
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📄 License
|
||||
|
||||
MIT. Built with 🐾 purrs and catnip.
|
||||
271
catml.js
Normal file
271
catml.js
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
/**
|
||||
* CaTML (Cat Altered Text Markup Language)
|
||||
* A feline-powered HTML compiler & runtime interpreter.
|
||||
*
|
||||
* Repository: https://git.p20.dev/christian/CaTML.git
|
||||
*/
|
||||
|
||||
(function (global) {
|
||||
'use strict';
|
||||
|
||||
// Tag translations: CaTML Tag -> HTML Tag
|
||||
const TAG_MAP = {
|
||||
'catworld': 'html',
|
||||
'catml': 'html',
|
||||
'whiskers': 'head',
|
||||
'collar': 'title',
|
||||
'fur': 'body',
|
||||
'box': 'div',
|
||||
'cardboard': 'div',
|
||||
'feather': 'span',
|
||||
'mouse': 'span',
|
||||
'meow1': 'h1',
|
||||
'meow2': 'h2',
|
||||
'meow3': 'h3',
|
||||
'meow4': 'h4',
|
||||
'meow5': 'h5',
|
||||
'meow6': 'h6',
|
||||
'purr': 'p',
|
||||
'scratch': 'a',
|
||||
'stare': 'img',
|
||||
'bap': 'button',
|
||||
'paw': 'button',
|
||||
'sniff': 'input',
|
||||
'yowl': 'textarea',
|
||||
'bowl': 'ul',
|
||||
'numbered-bowl': 'ol',
|
||||
'treat': 'li',
|
||||
'claw': 'strong',
|
||||
'hiss': 'em',
|
||||
'catnip': 'script',
|
||||
'groom': 'style',
|
||||
'litterbox': 'footer',
|
||||
'nap': 'hr',
|
||||
'door': 'form',
|
||||
'shelf': 'nav',
|
||||
'hammock': 'main',
|
||||
'tree': 'article',
|
||||
'perch': 'section',
|
||||
'curtain': 'header'
|
||||
};
|
||||
|
||||
// Attribute translations: CaTML Attribute -> HTML Attribute
|
||||
const ATTR_MAP = {
|
||||
'catch': 'href',
|
||||
'prey': 'src',
|
||||
'scent': 'alt',
|
||||
'tag': 'id',
|
||||
'breed': 'class',
|
||||
'fluff': 'style',
|
||||
'on-bap': 'onclick',
|
||||
'on-sniff': 'oninput',
|
||||
'on-change': 'onchange',
|
||||
'on-stalk': 'onmouseover',
|
||||
'on-pounce': 'onmousedown',
|
||||
'treat-type': 'type',
|
||||
'kibble': 'value',
|
||||
'curiosity': 'placeholder',
|
||||
'collar-name': 'name',
|
||||
'target': 'target',
|
||||
'width': 'width',
|
||||
'height': 'height'
|
||||
};
|
||||
|
||||
/**
|
||||
* Translates a single CaTML tag name to HTML tag name
|
||||
*/
|
||||
function translateTag(tag) {
|
||||
const lower = tag.toLowerCase();
|
||||
return TAG_MAP[lower] || tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates a single CaTML attribute name to HTML attribute name
|
||||
*/
|
||||
function translateAttr(attr) {
|
||||
const lower = attr.toLowerCase();
|
||||
return ATTR_MAP[lower] || attr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translates an attribute string of a tag
|
||||
* e.g. `breed="tabby" on-bap="alert(1)"` -> `class="tabby" onclick="alert(1)"`
|
||||
*/
|
||||
function compileAttributes(attrString) {
|
||||
if (!attrString) return '';
|
||||
|
||||
// Regex matching attribute key="value", key='value', or boolean key
|
||||
const attrRegex = /([a-zA-Z0-9_-]+)(?:=("[^"]*"|'[^']*'|[^\s"'>]+))?/g;
|
||||
return attrString.replace(attrRegex, (match, attrName, attrVal) => {
|
||||
const mappedName = translateAttr(attrName);
|
||||
if (attrVal !== undefined) {
|
||||
return `${mappedName}=${attrVal}`;
|
||||
}
|
||||
return mappedName;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles CaTML markup into standard HTML string
|
||||
* Handles opening tags, closing tags, self-closing tags, and comments.
|
||||
*/
|
||||
function compile(catmlSource) {
|
||||
if (typeof catmlSource !== 'string') return '';
|
||||
|
||||
// Regex for HTML/CaTML tags:
|
||||
// Captures:
|
||||
// 1: '/' if closing tag
|
||||
// 2: tag name
|
||||
// 3: attributes
|
||||
// 4: '/' if self-closing
|
||||
const tagRegex = /<\s*(\/?)\s*([a-zA-Z0-9_-]+)([\s\S]*?)(\/?)\s*>/g;
|
||||
|
||||
return catmlSource.replace(tagRegex, (fullMatch, isClosing, tagName, attrString, isSelfClosing) => {
|
||||
// Don't modify standard DOCTYPE or XML headers
|
||||
if (tagName.toLowerCase() === '!doctype') {
|
||||
return fullMatch;
|
||||
}
|
||||
|
||||
const mappedTag = translateTag(tagName);
|
||||
|
||||
if (isClosing) {
|
||||
return `</${mappedTag}>`;
|
||||
}
|
||||
|
||||
const compiledAttrs = compileAttributes(attrString);
|
||||
const trailingSlash = isSelfClosing ? ' /' : '';
|
||||
return `<${mappedTag}${compiledAttrs ? compiledAttrs : ''}${trailingSlash}>`;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders compiled CaTML directly into a DOM element or document
|
||||
*/
|
||||
function render(catmlSource, targetElement) {
|
||||
const html = compile(catmlSource);
|
||||
if (!targetElement) {
|
||||
return html;
|
||||
}
|
||||
|
||||
if (typeof targetElement === 'string') {
|
||||
targetElement = document.querySelector(targetElement);
|
||||
}
|
||||
|
||||
if (targetElement && targetElement.innerHTML !== undefined) {
|
||||
targetElement.innerHTML = html;
|
||||
|
||||
// Execute any script tags generated from <catnip> or <script>
|
||||
const scripts = targetElement.querySelectorAll('script');
|
||||
scripts.forEach(oldScript => {
|
||||
const newScript = document.createElement('script');
|
||||
Array.from(oldScript.attributes).forEach(attr => {
|
||||
newScript.setAttribute(attr.name, attr.value);
|
||||
});
|
||||
newScript.textContent = oldScript.textContent;
|
||||
oldScript.parentNode.replaceChild(newScript, oldScript);
|
||||
});
|
||||
}
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
/**
|
||||
* In-place DOM Transformer:
|
||||
* Scans document DOM for CaTML custom tags and converts them into real HTML elements.
|
||||
*/
|
||||
function transformDOM(rootNode) {
|
||||
const root = rootNode || document.body;
|
||||
if (!root) return;
|
||||
|
||||
// List of custom tags to replace
|
||||
const catmlTags = Object.keys(TAG_MAP).filter(t =>
|
||||
!['html', 'head', 'title', 'body', 'script', 'style'].includes(TAG_MAP[t])
|
||||
);
|
||||
|
||||
let foundElements = true;
|
||||
while (foundElements) {
|
||||
foundElements = false;
|
||||
for (const catTag of catmlTags) {
|
||||
const elements = root.getElementsByTagName(catTag);
|
||||
if (elements.length > 0) {
|
||||
foundElements = true;
|
||||
const el = elements[0];
|
||||
const newTagName = TAG_MAP[catTag];
|
||||
const replacement = document.createElement(newTagName);
|
||||
|
||||
// Copy and translate attributes
|
||||
Array.from(el.attributes).forEach(attr => {
|
||||
const mappedName = translateAttr(attr.name);
|
||||
replacement.setAttribute(mappedName, attr.value);
|
||||
});
|
||||
|
||||
// Move child nodes
|
||||
while (el.firstChild) {
|
||||
replacement.appendChild(el.firstChild);
|
||||
}
|
||||
|
||||
el.parentNode.replaceChild(replacement, el);
|
||||
break; // restart query selector because DOM mutated
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatic Bootstrapper:
|
||||
* Finds all <script type="text/catml"> and renders them or replaces them.
|
||||
*/
|
||||
function autoBoot() {
|
||||
if (typeof document === 'undefined') return;
|
||||
|
||||
// 1. Process <script type="text/catml">
|
||||
const catScripts = document.querySelectorAll('script[type="text/catml"]');
|
||||
catScripts.forEach(script => {
|
||||
const targetId = script.getAttribute('target') || script.getAttribute('data-target');
|
||||
const compiledHTML = compile(script.textContent);
|
||||
|
||||
if (targetId) {
|
||||
const target = document.querySelector(targetId);
|
||||
if (target) {
|
||||
render(script.textContent, target);
|
||||
}
|
||||
} else {
|
||||
// Replace the script tag itself with a container or direct elements
|
||||
const container = document.createElement('div');
|
||||
container.className = 'catml-container';
|
||||
render(script.textContent, container);
|
||||
script.parentNode.insertBefore(container, script);
|
||||
}
|
||||
});
|
||||
|
||||
// 2. Transform any custom elements in the body
|
||||
transformDOM(document.body);
|
||||
}
|
||||
|
||||
// Public API
|
||||
const CatML = {
|
||||
compile: compile,
|
||||
parse: compile,
|
||||
render: render,
|
||||
transformDOM: transformDOM,
|
||||
autoBoot: autoBoot,
|
||||
TAG_MAP: TAG_MAP,
|
||||
ATTR_MAP: ATTR_MAP
|
||||
};
|
||||
|
||||
// Auto-run when DOM is ready in browser
|
||||
if (typeof window !== 'undefined') {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', autoBoot);
|
||||
} else {
|
||||
setTimeout(autoBoot, 0);
|
||||
}
|
||||
window.CatML = CatML;
|
||||
}
|
||||
|
||||
// CommonJS / Node export support
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = CatML;
|
||||
}
|
||||
|
||||
})(typeof globalThis !== 'undefined' ? globalThis : this);
|
||||
67
demo.html
Normal file
67
demo.html
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>CaTML Standalone Demo</title>
|
||||
<!-- Load the CaTML runtime interpreter -->
|
||||
<script src="catml.js"></script>
|
||||
<style>
|
||||
body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: #fff8f0; color: #333; padding: 20px; }
|
||||
.cardboard-box { background: #ffe4c4; border: 2px dashed #cd853f; border-radius: 12px; padding: 25px; max-width: 650px; margin: 0 auto; box-shadow: 0 4px 15px rgba(0,0,0,0.06); }
|
||||
.btn-bap { background: #ff6b6b; color: white; border: none; padding: 10px 18px; border-radius: 20px; cursor: pointer; font-size: 16px; font-weight: bold; transition: transform 0.1s; }
|
||||
.btn-bap:hover { background: #fa5252; transform: scale(1.05); }
|
||||
.sniff-box { padding: 8px 14px; border: 2px solid #deb887; border-radius: 8px; font-size: 14px; width: 60%; margin-right: 10px; }
|
||||
.treat-list { background: #fff; border-radius: 8px; padding: 15px 30px; margin: 15px 0; border: 1px solid #fed; }
|
||||
.footer-box { margin-top: 25px; border-top: 1px solid #ebd3be; padding-top: 15px; font-size: 13px; color: #8c7b75; text-align: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- Writing pure CaTML inside <script type="text/catml"> -->
|
||||
<script type="text/catml">
|
||||
<box breed="cardboard-box">
|
||||
<curtain>
|
||||
<meow1>🐾 Welcome to CaTML!</meow1>
|
||||
<purr><claw>CaTML</claw> stands for <hiss>Cat Altered Text Markup Language</hiss>.</purr>
|
||||
</curtain>
|
||||
|
||||
<nap />
|
||||
|
||||
<perch>
|
||||
<meow2>Why CaTML?</meow2>
|
||||
<purr>
|
||||
Standard HTML is for humans. Cats require proper semantics: boxes for divs, purrs for paragraphs, and baps for buttons!
|
||||
</purr>
|
||||
|
||||
<meow3>Favorite Treats (<feather breed="fluffy">ranked by tastiness</feather>)</meow3>
|
||||
<bowl breed="treat-list">
|
||||
<treat>🐟 Fresh Atlantic Salmon</treat>
|
||||
<treat>🍗 Warm Roast Chicken Bits</treat>
|
||||
<treat>🌿 Organic Catnip Sprinkles</treat>
|
||||
</bowl>
|
||||
</perch>
|
||||
|
||||
<perch>
|
||||
<meow3>Interactive Bap Station</meow3>
|
||||
<purr>Try bapping this button to test inline event handling:</purr>
|
||||
<bap breed="btn-bap" on-bap="alert('🐾 BAP! You pushed the glass off the counter! 🥛💥')">
|
||||
🐾 Bap the Glass!
|
||||
</bap>
|
||||
</perch>
|
||||
|
||||
<nap />
|
||||
|
||||
<perch>
|
||||
<meow3>Sniff Test (Input field)</meow3>
|
||||
<sniff breed="sniff-box" treat-type="text" curiosity="Sniff something here..." on-sniff="document.getElementById('sniff-output').textContent = 'Cat smells: ' + this.value;" />
|
||||
<purr tag="sniff-output" fluff="color: #d9480f; font-weight: bold; margin-top: 8px;"></purr>
|
||||
</perch>
|
||||
|
||||
<litterbox breed="footer-box">
|
||||
<purr>Built with ❤️ and fur • Powered by <scratch catch="https://git.p20.dev/christian/CaTML.git" target="_blank">CaTML Runtime</scratch></purr>
|
||||
</litterbox>
|
||||
</box>
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
571
index.html
Normal file
571
index.html
Normal file
|
|
@ -0,0 +1,571 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>CaTML - Cat Altered Text Markup Language Playground</title>
|
||||
<script src="catml.js"></script>
|
||||
<style>
|
||||
:root {
|
||||
--bg-warm: #fcf9f5;
|
||||
--cat-orange: #f76707;
|
||||
--cat-amber: #ff922b;
|
||||
--cat-cream: #ffe8cc;
|
||||
--cat-dark: #2b2622;
|
||||
--card-bg: #ffffff;
|
||||
--border-color: #e9ecef;
|
||||
--code-bg: #1e1e24;
|
||||
--code-text: #f8f9fa;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||||
background: var(--bg-warm);
|
||||
color: var(--cat-dark);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
header {
|
||||
background: white;
|
||||
border-bottom: 2px solid var(--cat-cream);
|
||||
padding: 12px 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.03);
|
||||
}
|
||||
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.brand h1 {
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
color: var(--cat-orange);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
.brand .subtitle {
|
||||
font-size: 13px;
|
||||
color: #868e96;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
select, button {
|
||||
font-family: inherit;
|
||||
font-size: 13px;
|
||||
padding: 8px 14px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #ced4da;
|
||||
background: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
select:hover, button:hover {
|
||||
border-color: var(--cat-amber);
|
||||
}
|
||||
.btn-action {
|
||||
background: var(--cat-orange);
|
||||
color: white;
|
||||
font-weight: 600;
|
||||
border: none;
|
||||
}
|
||||
.btn-action:hover {
|
||||
background: #d9480f;
|
||||
}
|
||||
.btn-docs {
|
||||
background: #f1f3f5;
|
||||
color: #495057;
|
||||
border: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.main-container {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: calc(100vh - 65px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
border-right: 1px solid var(--border-color);
|
||||
background: white;
|
||||
}
|
||||
.panel:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 10px 16px;
|
||||
background: #f8f9fa;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
color: #6c757d;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.editor-wrapper {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#catml-input {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: none;
|
||||
padding: 16px;
|
||||
font-family: "Fira Code", Consolas, "Courier New", monospace;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
background: var(--code-bg);
|
||||
color: var(--code-text);
|
||||
resize: none;
|
||||
outline: none;
|
||||
tab-size: 2;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
.tab-btn {
|
||||
padding: 4px 10px;
|
||||
font-size: 11px;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: #868e96;
|
||||
}
|
||||
.tab-btn.active {
|
||||
background: white;
|
||||
color: var(--cat-orange);
|
||||
font-weight: bold;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
.preview-frame-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
background: white;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#preview-target {
|
||||
padding: 24px;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
#html-output-raw {
|
||||
display: none;
|
||||
padding: 16px;
|
||||
font-family: Consolas, monospace;
|
||||
font-size: 13px;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-all;
|
||||
background: #f8f9fa;
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
/* Modal for Dictionary */
|
||||
.modal-overlay {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0; left: 0; right: 0; bottom: 0;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 100;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.modal-overlay.open { display: flex; }
|
||||
.modal-card {
|
||||
background: white;
|
||||
width: 700px;
|
||||
max-width: 90vw;
|
||||
max-height: 85vh;
|
||||
border-radius: 12px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
||||
}
|
||||
.modal-header {
|
||||
padding: 16px 20px;
|
||||
background: var(--cat-cream);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.modal-header h2 { font-size: 18px; color: var(--cat-orange); }
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
.dict-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.dict-table th, .dict-table td {
|
||||
border: 1px solid #dee2e6;
|
||||
padding: 8px 12px;
|
||||
text-align: left;
|
||||
}
|
||||
.dict-table th { background: #f8f9fa; }
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
background: #e9ecef;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<header>
|
||||
<div class="brand">
|
||||
<h1>🐾 CaTML Playground</h1>
|
||||
<span class="subtitle">Cat Altered Text Markup Language</span>
|
||||
</div>
|
||||
<div class="controls">
|
||||
<select id="template-select">
|
||||
<option value="welcome">Template: Welcome to CaTML</option>
|
||||
<option value="bapGame">Template: Interactive Bap Game</option>
|
||||
<option value="adoptionForm">Template: Feline Adoption Form</option>
|
||||
<option value="portfolio">Template: Whiskers Portfolio</option>
|
||||
</select>
|
||||
<button class="btn-docs" onclick="toggleDictionary()">📖 Dictionary</button>
|
||||
<button class="btn-action" onclick="runCatML()">▶ Run / Compile</button>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="main-container">
|
||||
<!-- Left Panel: Editor -->
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span>CaTML Code Input</span>
|
||||
<span style="font-size: 10px; text-transform: none; color: #adb5bd;">Type in CaTML - updates automatically</span>
|
||||
</div>
|
||||
<div class="editor-wrapper">
|
||||
<textarea id="catml-input" spellcheck="false" placeholder="Write your CaTML code here..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Panel: Output & Live Preview -->
|
||||
<div class="panel">
|
||||
<div class="panel-header">
|
||||
<span>Output</span>
|
||||
<div class="tabs">
|
||||
<button class="tab-btn active" id="tab-btn-preview" onclick="showTab('preview')">Live Browser Preview</button>
|
||||
<button class="tab-btn" id="tab-btn-html" onclick="showTab('html')">Generated HTML</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="preview-frame-container">
|
||||
<div id="preview-target"></div>
|
||||
<pre id="html-output-raw"></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Dictionary Modal -->
|
||||
<div class="modal-overlay" id="dict-modal" onclick="if(event.target===this) toggleDictionary()">
|
||||
<div class="modal-card">
|
||||
<div class="modal-header">
|
||||
<h2>🐾 CaTML Feline Vocabulary Guide</h2>
|
||||
<button onclick="toggleDictionary()" style="border:none;background:none;font-size:20px;cursor:pointer;">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h3>Core Tags</h3>
|
||||
<table class="dict-table">
|
||||
<tr><th>CaTML Tag</th><th>HTML Equivalent</th><th>Meaning</th></tr>
|
||||
<tr><td><span class="badge"><box></span></td><td><code><div></code></td><td>Container box ("If it fits, I sits")</td></tr>
|
||||
<tr><td><span class="badge"><purr></span></td><td><code><p></code></td><td>Paragraph text / steady purr</td></tr>
|
||||
<tr><td><span class="badge"><meow1>..<meow6></span></td><td><code><h1>..<h6></code></td><td>Headings (roaring to tiny squeak)</td></tr>
|
||||
<tr><td><span class="badge"><scratch></span></td><td><code><a></code></td><td>Hyperlink to scratch and chase</td></tr>
|
||||
<tr><td><span class="badge"><stare></span></td><td><code><img></code></td><td>Visual image</td></tr>
|
||||
<tr><td><span class="badge"><bap></span></td><td><code><button></code></td><td>Action button</td></tr>
|
||||
<tr><td><span class="badge"><sniff></span></td><td><code><input></code></td><td>Input field</td></tr>
|
||||
<tr><td><span class="badge"><bowl></span></td><td><code><ul></code></td><td>List of food</td></tr>
|
||||
<tr><td><span class="badge"><treat></span></td><td><code><li></code></td><td>Individual treat / item</td></tr>
|
||||
<tr><td><span class="badge"><claw></span> / <span class="badge"><hiss></span></td><td><code><strong></code> / <code><em></code></td><td>Bold and italic styling</td></tr>
|
||||
<tr><td><span class="badge"><groom></span></td><td><code><style></code></td><td>CSS Stylesheet</td></tr>
|
||||
<tr><td><span class="badge"><catnip></span></td><td><code><script></code></td><td>JavaScript code (zoomies!)</td></tr>
|
||||
<tr><td><span class="badge"><litterbox></span></td><td><code><footer></code></td><td>Footer container</td></tr>
|
||||
</table>
|
||||
|
||||
<h3>Attributes</h3>
|
||||
<table class="dict-table">
|
||||
<tr><th>CaTML Attribute</th><th>HTML Equivalent</th><th>Purpose</th></tr>
|
||||
<tr><td><span class="badge">catch="..."</span></td><td><code>href="..."</code></td><td>Link URL</td></tr>
|
||||
<tr><td><span class="badge">prey="..."</span></td><td><code>src="..."</code></td><td>Image Source</td></tr>
|
||||
<tr><td><span class="badge">scent="..."</span></td><td><code>alt="..."</code></td><td>Alternative text</td></tr>
|
||||
<tr><td><span class="badge">tag="..."</span></td><td><code>id="..."</code></td><td>Element ID</td></tr>
|
||||
<tr><td><span class="badge">breed="..."</span></td><td><code>class="..."</code></td><td>CSS Class</td></tr>
|
||||
<tr><td><span class="badge">fluff="..."</span></td><td><code>style="..."</code></td><td>Inline Styles</td></tr>
|
||||
<tr><td><span class="badge">on-bap="..."</span></td><td><code>onclick="..."</code></td><td>Click handler</td></tr>
|
||||
<tr><td><span class="badge">on-sniff="..."</span></td><td><code>oninput="..."</code></td><td>Input listener</td></tr>
|
||||
<tr><td><span class="badge">treat-type="..."</span></td><td><code>type="..."</code></td><td>Input type</td></tr>
|
||||
<tr><td><span class="badge">curiosity="..."</span></td><td><code>placeholder="..."</code></td><td>Placeholder prompt</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const TEMPLATES = {
|
||||
welcome: `<groom>
|
||||
.cat-hero {
|
||||
background: linear-gradient(135deg, #ffe8cc, #ffd8a8);
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
border: 2px dashed #f76707;
|
||||
}
|
||||
.highlight { color: #d9480f; font-weight: bold; }
|
||||
.bap-btn {
|
||||
background: #f76707;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 10px 18px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
</groom>
|
||||
|
||||
<box breed="cat-hero">
|
||||
<meow1>🐾 Welcome to CaTML!</meow1>
|
||||
<purr>
|
||||
You are writing <claw>Cat Altered Text Markup Language</claw>.
|
||||
Standard web markup translated for fine felines.
|
||||
</purr>
|
||||
|
||||
<meow2>Why CaTML?</meow2>
|
||||
<bowl>
|
||||
<treat>🐱 <claw><box></claw> instead of divs (if I fits, I sits)</treat>
|
||||
<treat>🐟 <claw><bowl></claw> and <claw><treat></claw> for lists</treat>
|
||||
<treat>🐾 <claw><bap></claw> buttons with <hiss>on-bap</hiss> events</treat>
|
||||
</bowl>
|
||||
|
||||
<bap breed="bap-btn" on-bap="alert('🐾 MEOW! You clicked with your furry paw!')">
|
||||
Bap Me Now!
|
||||
</bap>
|
||||
</box>`,
|
||||
|
||||
bapGame: `<groom>
|
||||
.counter-box {
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
background: #f8f9fa;
|
||||
border-radius: 16px;
|
||||
border: 3px solid #ffa94d;
|
||||
}
|
||||
.score-display {
|
||||
font-size: 48px;
|
||||
color: #e8590c;
|
||||
font-weight: 900;
|
||||
margin: 15px 0;
|
||||
}
|
||||
.btn-counter {
|
||||
background: #e8590c;
|
||||
color: white;
|
||||
font-size: 18px;
|
||||
padding: 12px 28px;
|
||||
border: none;
|
||||
border-radius: 24px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(232, 89, 12, 0.3);
|
||||
}
|
||||
.btn-counter:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
</groom>
|
||||
|
||||
<box breed="counter-box">
|
||||
<meow1>🐾 The Infinite Counter-Bapper</meow1>
|
||||
<purr>How many cups can your paws knock off the edge?</purr>
|
||||
|
||||
<box tag="score" breed="score-display">0</box>
|
||||
|
||||
<bap breed="btn-counter" on-bap="let el=document.getElementById('score'); el.innerText=parseInt(el.innerText)+1;">
|
||||
🥛 Bap The Cup!
|
||||
</bap>
|
||||
|
||||
<purr fluff="margin-top: 15px; color: #868e96; font-size: 13px;">
|
||||
Each bap increases your feline chaos level.
|
||||
</purr>
|
||||
</box>`,
|
||||
|
||||
adoptionForm: `<groom>
|
||||
.form-card {
|
||||
background: #fff;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
||||
}
|
||||
.input-group { margin-bottom: 16px; }
|
||||
.input-group label { display: block; font-weight: bold; margin-bottom: 6px; font-size: 14px; }
|
||||
.field {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border: 2px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.field:focus { border-color: #f76707; outline: none; }
|
||||
.submit-paw {
|
||||
width: 100%;
|
||||
background: #2b8a3e;
|
||||
color: white;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
border: none;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
</groom>
|
||||
|
||||
<box breed="form-card">
|
||||
<meow2>🐾 Royal Kitten Adoption Application</meow2>
|
||||
<purr>Tell us about your home and treat budget.</purr>
|
||||
|
||||
<door>
|
||||
<box breed="input-group">
|
||||
<purr><claw>Human Name:</claw></purr>
|
||||
<sniff breed="field" treat-type="text" curiosity="e.g. Servant John" />
|
||||
</box>
|
||||
|
||||
<box breed="input-group">
|
||||
<purr><claw>Daily Salmon Guarantee:</claw></purr>
|
||||
<sniff breed="field" treat-type="number" kibble="3" curiosity="Cans per day" />
|
||||
</box>
|
||||
|
||||
<box breed="input-group">
|
||||
<purr><claw>Special Sleeping Spots:</claw></purr>
|
||||
<yowl breed="field" curiosity="Describe all pillows, laps, and warm laptops available..."></yowl>
|
||||
</box>
|
||||
|
||||
<bap breed="submit-paw" on-bap="alert('Application received! A council of cats will inspect your lap.')">
|
||||
Pledge Loyalty (Submit)
|
||||
</bap>
|
||||
</door>
|
||||
</box>`,
|
||||
|
||||
portfolio: `<groom>
|
||||
.profile-wrap { display: flex; gap: 20px; align-items: center; margin-bottom: 20px; }
|
||||
.avatar { font-size: 60px; background: #ffe8cc; border-radius: 50%; padding: 15px; }
|
||||
.badge { background: #ffe3e3; color: #e03131; padding: 4px 8px; border-radius: 4px; font-size: 12px; }
|
||||
</groom>
|
||||
|
||||
<box>
|
||||
<curtain>
|
||||
<box breed="profile-wrap">
|
||||
<feather breed="avatar">🐱</feather>
|
||||
<box>
|
||||
<meow1>Sir Pounce-a-Lot</meow1>
|
||||
<purr><hiss>Senior Software Mouse Catcher & Nap Architect</hiss></purr>
|
||||
<feather breed="badge">Open to Treats</feather>
|
||||
</box>
|
||||
</box>
|
||||
</curtain>
|
||||
|
||||
<perch>
|
||||
<meow3>Core Proficiencies</meow3>
|
||||
<bowl>
|
||||
<treat><claw>Zoomies at 3 AM</claw> - 99.8% Efficiency</treat>
|
||||
<treat><claw>Keyboard Warming</claw> - Full-stack coverage</treat>
|
||||
<treat><claw>Laser Chasing</claw> - High-frequency agile response</treat>
|
||||
</bowl>
|
||||
</perch>
|
||||
|
||||
<litterbox fluff="margin-top: 30px; border-top: 1px solid #eee; padding-top: 10px;">
|
||||
<purr>Contact: <scratch catch="mailto:pounce@catml.org">pounce@catml.org</scratch></purr>
|
||||
</litterbox>
|
||||
</box>`
|
||||
};
|
||||
|
||||
const editor = document.getElementById('catml-input');
|
||||
const previewTarget = document.getElementById('preview-target');
|
||||
const htmlOutputRaw = document.getElementById('html-output-raw');
|
||||
const templateSelect = document.getElementById('template-select');
|
||||
const modal = document.getElementById('dict-modal');
|
||||
|
||||
function runCatML() {
|
||||
const source = editor.value;
|
||||
const compiledHTML = CatML.compile(source);
|
||||
|
||||
// Update Preview
|
||||
CatML.render(source, previewTarget);
|
||||
|
||||
// Update Raw HTML
|
||||
htmlOutputRaw.textContent = compiledHTML;
|
||||
}
|
||||
|
||||
let debounceTimer;
|
||||
editor.addEventListener('input', () => {
|
||||
clearTimeout(debounceTimer);
|
||||
debounceTimer = setTimeout(runCatML, 250);
|
||||
});
|
||||
|
||||
templateSelect.addEventListener('change', (e) => {
|
||||
const selected = e.target.value;
|
||||
if (TEMPLATES[selected]) {
|
||||
editor.value = TEMPLATES[selected];
|
||||
runCatML();
|
||||
}
|
||||
});
|
||||
|
||||
function showTab(tab) {
|
||||
if (tab === 'preview') {
|
||||
previewTarget.style.display = 'block';
|
||||
htmlOutputRaw.style.display = 'none';
|
||||
document.getElementById('tab-btn-preview').classList.add('active');
|
||||
document.getElementById('tab-btn-html').classList.remove('active');
|
||||
} else {
|
||||
previewTarget.style.display = 'none';
|
||||
htmlOutputRaw.style.display = 'block';
|
||||
document.getElementById('tab-btn-preview').classList.remove('active');
|
||||
document.getElementById('tab-btn-html').classList.add('active');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleDictionary() {
|
||||
modal.classList.toggle('open');
|
||||
}
|
||||
|
||||
// Initialize with default template
|
||||
editor.value = TEMPLATES.welcome;
|
||||
runCatML();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
75
test/test.js
Normal file
75
test/test.js
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
const assert = require('assert');
|
||||
const CatML = require('../catml.js');
|
||||
|
||||
console.log('🐾 Running CaTML Test Suite...\n');
|
||||
|
||||
// Test 1: Tag Translations
|
||||
console.log('Test 1: Basic tag translations');
|
||||
const source1 = '<box><purr>Hello world</purr></box>';
|
||||
const expected1 = '<div><p>Hello world</p></div>';
|
||||
assert.strictEqual(CatML.compile(source1), expected1);
|
||||
console.log('✓ Passed: Basic tags');
|
||||
|
||||
// Test 2: Headings and lists
|
||||
console.log('Test 2: Headings and food bowls');
|
||||
const source2 = '<meow1>Grand Meow</meow1><bowl><treat>Tuna</treat><treat>Salmon</treat></bowl>';
|
||||
const expected2 = '<h1>Grand Meow</h1><ul><li>Tuna</li><li>Salmon</li></ul>';
|
||||
assert.strictEqual(CatML.compile(source2), expected2);
|
||||
console.log('✓ Passed: Headings and lists');
|
||||
|
||||
// Test 3: Attributes (catch, prey, scent, breed, tag, fluff)
|
||||
console.log('Test 3: Attribute translation');
|
||||
const source3 = '<stare prey="cat.jpg" scent="Cute kitty" tag="avatar" breed="round-cat" fluff="margin: 10px;" />';
|
||||
const compiled3 = CatML.compile(source3);
|
||||
assert(compiled3.includes('<img'));
|
||||
assert(compiled3.includes('src="cat.jpg"'));
|
||||
assert(compiled3.includes('alt="Cute kitty"'));
|
||||
assert(compiled3.includes('id="avatar"'));
|
||||
assert(compiled3.includes('class="round-cat"'));
|
||||
assert(compiled3.includes('style="margin: 10px;"'));
|
||||
console.log('✓ Passed: Attributes');
|
||||
|
||||
// Test 4: Interactive elements & Events (bap, sniff, on-bap)
|
||||
console.log('Test 4: Interactive buttons & events');
|
||||
const source4 = '<bap on-bap="pounce()" breed="red-laser">Catch!</bap><sniff treat-type="text" curiosity="Search prey..." on-sniff="search()" />';
|
||||
const compiled4 = CatML.compile(source4);
|
||||
assert(compiled4.includes('<button onclick="pounce()" class="red-laser">Catch!</button>'));
|
||||
assert(compiled4.includes('<input type="text" placeholder="Search prey..." oninput="search()"'));
|
||||
console.log('✓ Passed: Interactive & events');
|
||||
|
||||
// Test 5: Full document wrapper (<catworld>, <whiskers>, <collar>, <fur>, <litterbox>)
|
||||
console.log('Test 5: Full document translation');
|
||||
const source5 = `
|
||||
<catworld>
|
||||
<whiskers>
|
||||
<collar>My Cat Page</collar>
|
||||
</whiskers>
|
||||
<fur>
|
||||
<curtain><meow1>Header</meow1></curtain>
|
||||
<hammock>
|
||||
<purr>Content</purr>
|
||||
</hammock>
|
||||
<litterbox><purr>Footer</purr></litterbox>
|
||||
</fur>
|
||||
</catworld>
|
||||
`.trim();
|
||||
|
||||
const compiled5 = CatML.compile(source5);
|
||||
assert(compiled5.includes('<html>'));
|
||||
assert(compiled5.includes('<head>'));
|
||||
assert(compiled5.includes('<title>My Cat Page</title>'));
|
||||
assert(compiled5.includes('<body>'));
|
||||
assert(compiled5.includes('<header><h1>Header</h1></header>'));
|
||||
assert(compiled5.includes('<main>'));
|
||||
assert(compiled5.includes('<footer><p>Footer</p></footer>'));
|
||||
assert(compiled5.includes('</html>'));
|
||||
console.log('✓ Passed: Full document structure');
|
||||
|
||||
// Test 6: Script / Style elements (<catnip>, <groom>)
|
||||
console.log('Test 6: Script & styles');
|
||||
const source6 = '<groom>box { color: orange; }</groom><catnip>console.log("purr");</catnip>';
|
||||
const compiled6 = CatML.compile(source6);
|
||||
assert.strictEqual(compiled6, '<style>box { color: orange; }</style><script>console.log("purr");</script>');
|
||||
console.log('✓ Passed: Styles and scripts');
|
||||
|
||||
console.log('\n🎉 ALL CaTML TESTS PASSED! Purrfection achieved! 🐱');
|
||||
Loading…
Add table
Reference in a new issue