initial commit
This commit is contained in:
+66
@@ -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/
|
||||
@@ -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
|
||||
```
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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}`);
|
||||
});
|
||||
@@ -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 everyone’s 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 He’s my best friend — the person I trust most.
|
||||
appositive_3 This is the real problem — the lack of communication.
|
||||
appositive_4 That’s the challenge — getting everyone to agree.
|
||||
appositive_5 Here’s 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 — that’s 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 I’m going to—”
|
||||
interrupt_3 “Wait, I didn’t mean—”
|
||||
interrupt_4 “Don’t 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 There’s one thing I can’t 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."
|
||||
Reference in New Issue
Block a user