From 249b829a9a3a1e055f3ec2a995f7cbc1ae105122 Mon Sep 17 00:00:00 2001 From: genwhittTTD Date: Wed, 15 Apr 2026 15:43:00 -0400 Subject: [PATCH 1/3] gwh-APIDOCS-3914-change-heading-case add js+bat+modify gitignore --- .gitignore | 3 + docs/endpoints/md-sentence-case-run.bat | 1 + docs/endpoints/md-sentence-case.js | 206 ++++++++++++++++++++++++ 3 files changed, 210 insertions(+) create mode 100644 docs/endpoints/md-sentence-case-run.bat create mode 100644 docs/endpoints/md-sentence-case.js diff --git a/.gitignore b/.gitignore index 72dfb7711..b9dddd528 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* .aider* + +# Temporary exclusion for Claude project 4/10/26 +# md-sentence-case* \ No newline at end of file diff --git a/docs/endpoints/md-sentence-case-run.bat b/docs/endpoints/md-sentence-case-run.bat new file mode 100644 index 000000000..f0263addf --- /dev/null +++ b/docs/endpoints/md-sentence-case-run.bat @@ -0,0 +1 @@ +node md-sentence-case.js \ No newline at end of file diff --git a/docs/endpoints/md-sentence-case.js b/docs/endpoints/md-sentence-case.js new file mode 100644 index 000000000..063b33f8d --- /dev/null +++ b/docs/endpoints/md-sentence-case.js @@ -0,0 +1,206 @@ +#!/usr/bin/env node + +/** + * md-sentence-case.js + * + * Converts Markdown headings (h2–h4) and heading link display text + * from title case to sentence case, with a configurable exclusions list. + * + * Usage: + * node md-sentence-case.js [--dry-run] [directory] + * + * Options: + * --dry-run Preview changes without writing any files + * directory Path to the repo/folder to process (defaults to current dir) + * + * Examples: + * node md-sentence-case.js --dry-run ./docs + * node md-sentence-case.js ./docs + * node md-sentence-case.js # runs on current directory + */ + +const fs = require('fs'); +const path = require('path'); + +// ─── CONFIGURE YOUR EXCLUSIONS HERE ────────────────────────────────────────── +// +// Words and phrases listed here will NOT be lowercased. +// - Matching is case-insensitive, so "aws" and "AWS" are both caught. +// - Put longer/more specific phrases BEFORE shorter ones so they match first +// (e.g. "Amazon Web Services" before "AWS"). + +const EXCLUSIONS = [ + // ── Multi-word phrases first (must come before single words they contain) ── + 'Mobile SDK', + 'Amazon S3', + 'Secure Signals', + 'Prebid.js', + 'FAQs', + + // ── Acronyms & initialisms ───────────────────────────────────────────────── + 'API', + 'AWS', + 'CTV', + 'DII', + 'DSP', + 'EUID', + 'FAQ', + 'GMA', + 'ID', + 'IMA', + 'JSON', + 'POST', + 'SDK', + 'SSO', + 'UID2', + 'URL', + + // ── Proper nouns & brand names ───────────────────────────────────────────── + 'Google', + 'Gradle', + 'GraphQL', + 'I', // first-person pronoun + 'JavaScript', + 'Maven', + 'Prebid', + 'ProGuard', + 'Sharing', + 'Swift', + 'Base64', + 'SHA-256', + + // ── Add your own exclusions below this line ──────────────────────────────── + +]; + +// ───────────────────────────────────────────────────────────────────────────── + +const args = process.argv.slice(2); +const dryRun = args.includes('--dry-run'); +const target = args.find(a => !a.startsWith('--')) || '.'; + +/** + * Escapes a string for safe use inside a RegExp. + */ +function escapeForRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +/** + * Converts a heading/link text string to sentence case, + * then restores any words in the EXCLUSIONS list. + */ +function toSentenceCase(text) { + // Lowercase everything, then uppercase the very first character. + let result = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase(); + + // Restore excluded words. Longer phrases are matched first (list order). + for (const word of EXCLUSIONS) { + const pattern = new RegExp(`\\b${escapeForRegex(word)}\\b`, 'gi'); + result = result.replace(pattern, word); + } + + return result; +} + +/** + * Processes the content of a single Markdown file. + * Returns the updated content and a flag indicating whether anything changed. + */ +function processContent(content, filePath) { + let changed = false; + const relPath = path.relative(process.cwd(), filePath); + + // Detect and normalise line endings so the heading regex works on all + // platforms. Windows files use \r\n; the \r is a line terminator in JS + // regex, which causes (.+) to stop short and the heading match to fail. + const lineEnding = content.includes('\r\n') ? '\r\n' : '\n'; + const lines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n'); + + const processed = lines.map((line, idx) => { + const lineNum = idx + 1; + let newLine = line; + + // ── 1. Headings h2–h4 ────────────────────────────────────────────────── + newLine = newLine.replace(/^(#{2,4} )(.+?)(\s*)$/, (_match, prefix, headingText, trailing) => { + const converted = toSentenceCase(headingText); + if (converted !== headingText) { + console.log(` ${relPath}:${lineNum} heading "${headingText}" → "${converted}"`); + changed = true; + } + return prefix + converted + trailing; + }); + + // ── 2. Display text of internal/relative links ──────────────────────── + // Matches [Text](url) where url is NOT an external http/https link, + // covering anchor links (#...), relative file paths (../foo.md), etc. + newLine = newLine.replace(/\[([^\]]+)\]\(((?!https?:\/\/)[^)]+)\)/g, (_match, displayText, url) => { + const converted = toSentenceCase(displayText); + if (converted !== displayText) { + console.log(` ${relPath}:${lineNum} link "[${displayText}]" → "[${converted}]"`); + changed = true; + } + return `[${converted}](${url})`; + }); + + return newLine; + }); + + // Restore the original line endings when reassembling the file. + return { content: processed.join(lineEnding), changed }; +} + +/** + * Recursively finds all .md files under a directory, + * skipping node_modules and .git. + */ +function findMarkdownFiles(dir) { + const results = []; + + for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { + const fullPath = path.join(dir, entry.name); + + if (entry.isDirectory()) { + if (entry.name === 'node_modules' || entry.name === '.git') continue; + results.push(...findMarkdownFiles(fullPath)); + } else if (entry.isFile() && /\.mdx?$/.test(entry.name)) { + results.push(fullPath); + } + } + + return results; +} + +// ─── MAIN ───────────────────────────────────────────────────────────────────── + +const absTarget = path.resolve(target); +const files = findMarkdownFiles(absTarget); + +console.log(`\nmd-sentence-case${dryRun ? ' [DRY RUN]' : ''}`); +console.log(`Target : ${absTarget}`); +console.log(`Files : ${files.length} Markdown file(s) found\n`); + +if (files.length === 0) { + console.log('Nothing to do.'); + process.exit(0); +} + +let totalChanged = 0; + +for (const file of files) { + const original = fs.readFileSync(file, 'utf8'); + const { content, changed } = processContent(original, file); + + if (changed) { + totalChanged++; + if (!dryRun) { + fs.writeFileSync(file, content, 'utf8'); + } + } +} + +const verb = dryRun ? 'Would modify' : 'Modified'; +console.log(`\n${verb} ${totalChanged} of ${files.length} file(s).`); +if (dryRun) { + console.log('Run without --dry-run to apply changes.'); +} From 8153eb4839cd924d888484ff5bc568272e107f4a Mon Sep 17 00:00:00 2001 From: genwhittTTD Date: Wed, 22 Apr 2026 16:30:10 -0400 Subject: [PATCH 2/3] gwh-APIDOCS-3914-change-heading-case test --- docs/getting-started/gs-account-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/getting-started/gs-account-setup.md b/docs/getting-started/gs-account-setup.md index 03b073302..5feed0ff1 100644 --- a/docs/getting-started/gs-account-setup.md +++ b/docs/getting-started/gs-account-setup.md @@ -14,7 +14,7 @@ This page provides general information required for you to get your account set ## Contact Info -To get access to the UID2 framework, contact the appropriate team at The Trade Desk listed below. +To get access to the UID2 framework, contact the appropriate team at The Trade Desk listed below. If you have an existing relationship with The Trade Desk (the current UID2 Administrator), connect directly with your contact to get started with UID2. From 8171ce26554de7a6f85120535d510589a5781c66 Mon Sep 17 00:00:00 2001 From: genwhittTTD Date: Thu, 23 Apr 2026 09:10:20 -0400 Subject: [PATCH 3/3] remove 2 new files --- docs/endpoints/md-sentence-case-run.bat | 1 - docs/endpoints/md-sentence-case.js | 206 ------------------------ 2 files changed, 207 deletions(-) delete mode 100644 docs/endpoints/md-sentence-case-run.bat delete mode 100644 docs/endpoints/md-sentence-case.js diff --git a/docs/endpoints/md-sentence-case-run.bat b/docs/endpoints/md-sentence-case-run.bat deleted file mode 100644 index f0263addf..000000000 --- a/docs/endpoints/md-sentence-case-run.bat +++ /dev/null @@ -1 +0,0 @@ -node md-sentence-case.js \ No newline at end of file diff --git a/docs/endpoints/md-sentence-case.js b/docs/endpoints/md-sentence-case.js deleted file mode 100644 index 063b33f8d..000000000 --- a/docs/endpoints/md-sentence-case.js +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env node - -/** - * md-sentence-case.js - * - * Converts Markdown headings (h2–h4) and heading link display text - * from title case to sentence case, with a configurable exclusions list. - * - * Usage: - * node md-sentence-case.js [--dry-run] [directory] - * - * Options: - * --dry-run Preview changes without writing any files - * directory Path to the repo/folder to process (defaults to current dir) - * - * Examples: - * node md-sentence-case.js --dry-run ./docs - * node md-sentence-case.js ./docs - * node md-sentence-case.js # runs on current directory - */ - -const fs = require('fs'); -const path = require('path'); - -// ─── CONFIGURE YOUR EXCLUSIONS HERE ────────────────────────────────────────── -// -// Words and phrases listed here will NOT be lowercased. -// - Matching is case-insensitive, so "aws" and "AWS" are both caught. -// - Put longer/more specific phrases BEFORE shorter ones so they match first -// (e.g. "Amazon Web Services" before "AWS"). - -const EXCLUSIONS = [ - // ── Multi-word phrases first (must come before single words they contain) ── - 'Mobile SDK', - 'Amazon S3', - 'Secure Signals', - 'Prebid.js', - 'FAQs', - - // ── Acronyms & initialisms ───────────────────────────────────────────────── - 'API', - 'AWS', - 'CTV', - 'DII', - 'DSP', - 'EUID', - 'FAQ', - 'GMA', - 'ID', - 'IMA', - 'JSON', - 'POST', - 'SDK', - 'SSO', - 'UID2', - 'URL', - - // ── Proper nouns & brand names ───────────────────────────────────────────── - 'Google', - 'Gradle', - 'GraphQL', - 'I', // first-person pronoun - 'JavaScript', - 'Maven', - 'Prebid', - 'ProGuard', - 'Sharing', - 'Swift', - 'Base64', - 'SHA-256', - - // ── Add your own exclusions below this line ──────────────────────────────── - -]; - -// ───────────────────────────────────────────────────────────────────────────── - -const args = process.argv.slice(2); -const dryRun = args.includes('--dry-run'); -const target = args.find(a => !a.startsWith('--')) || '.'; - -/** - * Escapes a string for safe use inside a RegExp. - */ -function escapeForRegex(str) { - return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} - -/** - * Converts a heading/link text string to sentence case, - * then restores any words in the EXCLUSIONS list. - */ -function toSentenceCase(text) { - // Lowercase everything, then uppercase the very first character. - let result = text.charAt(0).toUpperCase() + text.slice(1).toLowerCase(); - - // Restore excluded words. Longer phrases are matched first (list order). - for (const word of EXCLUSIONS) { - const pattern = new RegExp(`\\b${escapeForRegex(word)}\\b`, 'gi'); - result = result.replace(pattern, word); - } - - return result; -} - -/** - * Processes the content of a single Markdown file. - * Returns the updated content and a flag indicating whether anything changed. - */ -function processContent(content, filePath) { - let changed = false; - const relPath = path.relative(process.cwd(), filePath); - - // Detect and normalise line endings so the heading regex works on all - // platforms. Windows files use \r\n; the \r is a line terminator in JS - // regex, which causes (.+) to stop short and the heading match to fail. - const lineEnding = content.includes('\r\n') ? '\r\n' : '\n'; - const lines = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n').split('\n'); - - const processed = lines.map((line, idx) => { - const lineNum = idx + 1; - let newLine = line; - - // ── 1. Headings h2–h4 ────────────────────────────────────────────────── - newLine = newLine.replace(/^(#{2,4} )(.+?)(\s*)$/, (_match, prefix, headingText, trailing) => { - const converted = toSentenceCase(headingText); - if (converted !== headingText) { - console.log(` ${relPath}:${lineNum} heading "${headingText}" → "${converted}"`); - changed = true; - } - return prefix + converted + trailing; - }); - - // ── 2. Display text of internal/relative links ──────────────────────── - // Matches [Text](url) where url is NOT an external http/https link, - // covering anchor links (#...), relative file paths (../foo.md), etc. - newLine = newLine.replace(/\[([^\]]+)\]\(((?!https?:\/\/)[^)]+)\)/g, (_match, displayText, url) => { - const converted = toSentenceCase(displayText); - if (converted !== displayText) { - console.log(` ${relPath}:${lineNum} link "[${displayText}]" → "[${converted}]"`); - changed = true; - } - return `[${converted}](${url})`; - }); - - return newLine; - }); - - // Restore the original line endings when reassembling the file. - return { content: processed.join(lineEnding), changed }; -} - -/** - * Recursively finds all .md files under a directory, - * skipping node_modules and .git. - */ -function findMarkdownFiles(dir) { - const results = []; - - for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { - const fullPath = path.join(dir, entry.name); - - if (entry.isDirectory()) { - if (entry.name === 'node_modules' || entry.name === '.git') continue; - results.push(...findMarkdownFiles(fullPath)); - } else if (entry.isFile() && /\.mdx?$/.test(entry.name)) { - results.push(fullPath); - } - } - - return results; -} - -// ─── MAIN ───────────────────────────────────────────────────────────────────── - -const absTarget = path.resolve(target); -const files = findMarkdownFiles(absTarget); - -console.log(`\nmd-sentence-case${dryRun ? ' [DRY RUN]' : ''}`); -console.log(`Target : ${absTarget}`); -console.log(`Files : ${files.length} Markdown file(s) found\n`); - -if (files.length === 0) { - console.log('Nothing to do.'); - process.exit(0); -} - -let totalChanged = 0; - -for (const file of files) { - const original = fs.readFileSync(file, 'utf8'); - const { content, changed } = processContent(original, file); - - if (changed) { - totalChanged++; - if (!dryRun) { - fs.writeFileSync(file, content, 'utf8'); - } - } -} - -const verb = dryRun ? 'Would modify' : 'Modified'; -console.log(`\n${verb} ${totalChanged} of ${files.length} file(s).`); -if (dryRun) { - console.log('Run without --dry-run to apply changes.'); -}