initial commit

This commit is contained in:
Mo Elzubeir
2025-08-15 00:19:06 -05:00
commit 0b362bed4b
5 changed files with 360 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
.next
next-env.d.ts
# production
/build
# misc
.DS_Store
*.pem
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# vercel
.vercel
# typescript
*.tsbuildinfo
# VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# Emacs
*~
\#*\#
/.emacs.desktop
/.emacs.desktop.lock
*.elc
auto-save-list
tramp
.\#*
# OSX
.DS_Store
.AppleDouble
.LSOverride
Icon
._*
.Spotlight-V100
.Trashes
# Trae specific
.trae/
.trae-cache/
+24
View File
@@ -0,0 +1,24 @@
# Em Dash Guide
Don't you hate how em dashes ruin your carefully curated AI slop?
I mean, you worked hard on that shit. You prompted and prompted and prompted.
Or maybe you have agents running amok, ruining everyone's feed with your
garbage.
And then people start commenting, bot! Bot! Bot!
Not cool, man.. not cool.
Most of what I've seen online replace em dashes with one character or two
at the most. Not this! No sir!
It has to make SENSE!!!! right? right?
Alright, just, do the thing
```
npm i
npm start
```
+14
View File
@@ -0,0 +1,14 @@
{
"name": "emdash-api",
"version": "1.0.0",
"description": "API to linguistically and grammatically replace em dashes.",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "node server.js"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5"
}
}
+142
View File
@@ -0,0 +1,142 @@
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
app.use(express.text());
function removeEmDash(text) {
if (!text || typeof text !== 'string') {
return text;
}
function isDateTimeOrPeriod(str) {
const dateTimePatterns = [
/^\d+:\d+/,
/^\d+$/, // number.. could be date, who knows
/^(January|February|March|April|May|June|July|August|September|October|November|December|Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)$/i, // this is dumb
/^(Monday|Tuesday|Wednesday|Thursday|Friday|Saturday|Sunday|Mon|Tue|Wed|Thu|Fri|Sat|Sun)$/i, // days
/^(Spring|Summer|Fall|Autumn|Winter)$/i, // seasons, did you see that, even autumn my zoul!
/^\d{4}$/,
/^Q[1-4]$/i, // quarters for that jerome powell post
];
return dateTimePatterns.some(pattern => pattern.test(str.trim()));
}
let result = text
// 1. parenthetical/interruptive information (word — phrase — word) -> parentheses
.replace(/([a-zA-Z])\s*—\s*([^—]+?)\s*—\s*([a-zA-Z])/g, '$1 ($2) $3')
// 2. date/time ranges (handle early to avoid conflicts)
.replace(/(\d+:\d+\s*[ap]\.?m\.?)\s*—\s*(\d+:\d+\s*[ap]\.?m\.?)/gi, '$1 to $2')
// 3. interrupted speech in dialogue (word—") -> keep as interruption (but clean format)
.replace(/([a-zA-Z])\s*—\s*$/g, '$1 ')
// 4. attribution quotes ("quote" — Author) -> en dash
.replace(/"([^"]+)"\s*—\s*([A-Z][^.!?]*)/g, '"$1" $2')
// 5. dramatic pause/reveal (phrase — Word!) -> ellipsis
.replace(/([a-zA-Z\s]+)\s*—\s*([A-Z][a-zA-Z]*[!.])/g, '$1 ... $2')
// 6. sudden break in thought (clause — but/and/or...) -> comma
.replace(/([a-zA-Z])\s*—\s*(but|and|or|yet|so)\s+/g, '$1, $2 ')
// 7. appositive emphasis (noun — a descriptor) -> comma
.replace(/([a-zA-Z])\s*—\s*(a\s+[a-zA-Z][^.!?]*)/g, '$1, $2')
// 8. summary/amplification at end (items — conclusion.) -> semicolon
.replace(/([a-zA-Z.,\s]*[a-zA-Z.,])\s*—\s*([a-z][^.!?]*\.)/g, '$1 ; $2')
// 9. after sentence punctuation -> space
.replace(/([.!?:;])\s*—\s*([A-Za-z])/g, '$1 $2')
// 10. after comma -> space
.replace(/([,])\s*—\s*([A-Za-z])/g, '$1 $2')
// 11. before closing punctuation -> space
.replace(/([a-zA-Z])\s*—\s*([.,;:!?"'\)\]])/g, '$1 $2')
// 12. after opening punctuation -> space
.replace(/(["'(\[])\s*—\s*([A-Za-z])/g, '$1 $2')
// 13. between letters (simple cases) -> comma
.replace(/([a-zA-Z])\s*—\s*([a-zA-Z])/g, '$1, $2')
// 14. leading em dash -> remove
.replace(/^\s*—\s*/g, '')
// 15. trailing em dash -> remove
.replace(/\s*—\s*$/g, '')
// 16. catch-all remaining -> check if it's a date/time range, otherwise en dash
.replace(/(\S+)\s*—\s*(\S+)/g, (_, left, right) => {
if (isDateTimeOrPeriod(left) && isDateTimeOrPeriod(right)) {
return `${left} ${right}`;
}
return `${left} ${right}`;
});
// sanitation step: clean up spacing issues
result = result
// remove spaces before colons and ellipses
.replace(/\s+\.\.\./g, '...')
.replace(/\s+:/g, ':')
.replace(/\s+;/g, ';')
// clean up multiple spaces
.replace(/\s+/g, ' ')
.trim();
return result;
}
app.post('/emdash', (req, res) => {
try {
const text = typeof req.body === 'string' ? req.body : req.body.text;
if (!text) {
return res.status(400).json({ error: 'Text is required, come on now' });
}
const result = removeEmDash(text);
res.json({
original: text,
result: result
});
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
app.get('/emdash', (req, res) => {
const text = req.query.text;
if (!text) {
return res.status(400).json({ error: 'Text query parameter is required, come on now' });
}
const result = removeEmDash(text);
res.json({
original: text,
result: result
});
});
app.get('/', (req, res) => {
res.json({
message: 'Em Dash API',
description: 'API to linguistically and grammatically replace em dashes.',
endpoints: {
'POST /emdash': 'Send text in body (JSON or plain text)',
'GET /emdash?text=your-text': 'Send text as query parameter'
}
});
});
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
Executable
+114
View File
@@ -0,0 +1,114 @@
#!/bin/sh
# Portable test runner (no associative arrays required)
API_URL="${API_URL:-http://localhost:3000/emdash}"
# Detect a version-aware sort if available; fall back to plain sort
if sort -V </dev/null >/dev/null 2>&1; then
SORT_CMD='sort -V'
else
SORT_CMD='sort'
fi
# --- Key/Value dataset (TAB-separated): key<TAB>value ---
DATA='
parenthetical_1 My sister — who never cooks — made dinner last night.
parenthetical_2 The car — a bright red convertible — caught everyones attention.
parenthetical_3 This book — my all-time favorite — never gets old.
parenthetical_4 We met John — our old college roommate — at the reunion.
parenthetical_5 The plan — though risky — might just work.
suddenbreak_1 I thought it was a great idea — until I heard the cost.
suddenbreak_2 We were going to leave early — but the rain kept us inside.
suddenbreak_3 She almost told him the truth — and then decided against it.
suddenbreak_4 I started walking toward the door — then stopped.
suddenbreak_5 He was about to sign the contract — when the phone rang.
appositive_1 She is an expert — a true authority in her field.
appositive_2 Hes my best friend — the person I trust most.
appositive_3 This is the real problem — the lack of communication.
appositive_4 Thats the challenge — getting everyone to agree.
appositive_5 Heres your reward — an extra day off.
summary_1 Long nights, endless rehearsals, countless cups of coffee — all for opening night.
summary_2 Broken glass, a smashed door, missing valuables — it was clearly a break-in.
summary_3 Late trains, heavy traffic, bad weather — today was not my day.
summary_4 Jeans, T-shirts, sneakers — his wardrobe never changed.
summary_5 Pain, sweat, and hard work — thats what built this company.
listintro_1 We need several things — bread, milk, eggs, and butter.
listintro_2 She packed everything — sunscreen, towels, snacks, and water bottles.
listintro_3 The class will cover three topics — history, geography, and culture.
listintro_4 He ordered a variety of drinks — coffee, tea, soda, and juice.
listintro_5 They brought the essentials — maps, flashlights, and first-aid kits.
interrupt_1 “I was just about to—”
interrupt_2 “If you think Im going to—”
interrupt_3 “Wait, I didnt mean—”
interrupt_4 “Dont you dare—”
interrupt_5 “I was trying to say—”
dramaticpause_1 And the winner is — Michael.
dramaticpause_2 The answer to your question is — yes.
dramaticpause_3 The person behind it all was — my own brother.
dramaticpause_4 The solution is simple — work together.
dramaticpause_5 Our biggest competitor is — ourselves.
internalcommas_1 She laughed, cried, and shouted — but she never gave up.
internalcommas_2 They came early, stayed late, and worked hard — yet still missed the deadline.
internalcommas_3 He was tired, hungry, and sore — and still kept running.
internalcommas_4 The team played well, passed accurately, and defended strongly — until the final minutes.
internalcommas_5 I planned, packed, and saved — only to have the trip canceled.
namely_1 Theres one thing I cant stand — dishonesty.
namely_2 He has one true passion — music.
namely_3 Our goal is clear — success.
namely_4 This is my greatest fear — failure.
namely_5 She only wants one thing — respect.
pauseemphasis_1 The recipe — though simple — is delicious.
pauseemphasis_2 That trip — despite the rain — was unforgettable.
pauseemphasis_3 The meeting — if it happens — could change everything.
pauseemphasis_4 Her response — as expected — was sarcastic.
pauseemphasis_5 The project — while behind schedule — will still be completed.
listinlist_1 We traveled to Paris, France; Rome, Italy; and Berlin, Germany — all in one summer.
listinlist_2 The menu included pasta, salad, and breadsticks; steak, potatoes, and vegetables — everything we could want.
listinlist_3 They visited Chicago, Illinois; Denver, Colorado; and Austin, Texas — and still had time for more.
listinlist_4 The tour covered Madrid, Spain; Lisbon, Portugal; and Dublin, Ireland — a whirlwind trip.
listinlist_5 We met clients from Tokyo, Japan; Seoul, South Korea; and Beijing, China — all in the same week.
daterange_1 Office hours are 9:00 a.m.—5:00 p.m.
daterange_2 The sale runs June 1—June 15.
daterange_3 The conference is scheduled for March 10—March 14.
daterange_4 The course is offered September—December.
daterange_5 The store is open Monday—Saturday.
attribution_1 “The best way out is always through.” — Robert Frost
attribution_2 “Do or do not, there is no try.” — Yoda
attribution_3 “Injustice anywhere is a threat to justice everywhere.” — Martin Luther King Jr.
attribution_4 “I think, therefore I am.” — René Descartes
attribution_5 “The only thing we have to fear is fear itself.” — Franklin D. Roosevelt
punchend_1 I was ready to forgive — until he laughed.
punchend_2 We almost won — if not for that last-minute mistake.
punchend_3 She looked happy — until she saw the bill.
punchend_4 He was about to say yes — when the phone rang.
punchend_5 I thought we were friends — until you lied to me.
'
echo "Running tests..."
# Print DATA, strip empty lines, sort by key, then read lines
printf "%s" "$DATA" | awk 'NF' | $SORT_CMD |
while IFS="$(printf '\t')" read -r key text; do
# Safety: skip if no key or text
[ -z "$key" ] && continue
[ -z "$text" ] && continue
echo "-----------------------------------"
echo "Testing [$key]: $text"
# Hit API with URL-encoded text
response="$(curl -s -G --data-urlencode "text=$text" "$API_URL")"
# Pretty-print JSON if jq exists AND response is valid JSON
if command -v jq >/dev/null 2>&1 && printf '%s' "$response" | jq -e . >/dev/null 2>&1; then
printf '%s' "$response" | jq '.original, .result'
else
printf '%s\n' "$response"
if ! command -v jq >/dev/null 2>&1; then
echo "(Install jq for better formatting)"
fi
fi
done
echo "-----------------------------------"
echo "Tests complete."