← back to Dw Slideshow Gen

lib/util.js

41 lines

// lib/util.js — tiny shared helpers (zero deps)
import fs from 'node:fs'
import path from 'node:path'

export const slugify = (s = '') =>
  String(s).toLowerCase().trim()
    .replace(/['’"]/g, '')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '')
    .slice(0, 60) || 'untitled'

export const titleCase = (s = '') =>
  String(s).replace(/\w\S*/g, w => w[0].toUpperCase() + w.slice(1))

// ISO-ish date string used in spec ids / output folders. We can't call Date.now()
// in some sandboxes, but the CLI runs in a normal shell, so a plain Date is fine here.
export const today = () => {
  const d = new Date()
  const p = n => String(n).padStart(2, '0')
  return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
}
export const stamp = () => new Date().toISOString()

export const ensureDir = d => { fs.mkdirSync(d, { recursive: true }); return d }
export const readJSON = p => JSON.parse(fs.readFileSync(p, 'utf8'))
export const writeJSON = (p, obj) => {
  ensureDir(path.dirname(p))
  fs.writeFileSync(p, JSON.stringify(obj, null, 2))
  return p
}
export const exists = p => { try { fs.accessSync(p); return true } catch { return false } }

// cap a string to n chars on a word boundary, with an ellipsis
export const cap = (s = '', n = 80) => {
  s = String(s).trim()
  if (s.length <= n) return s
  const cut = s.slice(0, n)
  const sp = cut.lastIndexOf(' ')
  return (sp > n * 0.6 ? cut.slice(0, sp) : cut).replace(/[,;:.\s]+$/, '') + '…'
}