Free code template

Utility script

A small script with its options at the top, input checks, a summary line and tests.

It opens with this template loaded and runs in your browser. You don't need an account to use it; a free account saves your work and exports it.

The job it does

Any repeated chore: rename, total, convert, check. Write it once with tests instead of doing it by hand again. It comes with its own checks, so after you change it you can see at a glance that it still does the job.

What's inside

This is the template exactly as the Code Lab opens it, read-only. Open it to run it, change it and export it.

  • 2 files
  • Runs from rename.js
  • JavaScript
The brief: Rename receipts (utility script)

A chore you do by hand more than twice deserves a script. This one renames a batch of receipt files to one standard name, as a dry run first: options at the top, input checked before anything happens, and one summary line at the end. The functions are tested in rename.test.js.

Change the options and the file list, or replace standardName with your own chore, and keep the tests passing.

Done when
  • All my tests pass
  • Output contains “3 files renamed, 1 skipped”
  • standardName("Till 7.PNG", {"prefix": "r", "date": "…) returns "r-2026-01-31-till-7.png"
  • No uncaught errors
rename.js
// Utility script: rename a batch of files to one standard name, as a dry run first.
// Options live at the top, inputs are checked, and the script ends with one summary line.
const OPTIONS = {
  prefix: 'receipt',
  date: '2026-03-02',
  dryRun: true,
};

/** "Till 3 Receipt (copy).PDF" → "receipt-2026-03-02-till-3.pdf"; null when the name has nothing usable. */
export function standardName(name, { prefix, date }) {
  const dot = name.lastIndexOf('.');
  const ext = dot > 0 ? name.slice(dot + 1).toLowerCase() : '';
  const stem = (dot > 0 ? name.slice(0, dot) : name)
    .toLowerCase()
    .replace(/\(copy\)|copy of|receipt/g, ' ')
    .replace(/[^a-z0-9]+/g, '-')
    .replace(/^-+|-+$/g, '');
  if (!stem || !ext) return null;
  return `${prefix}-${date}-${stem}.${ext}`;
}

/** Plans every rename; two files that would get the same name get -2, -3… instead of overwriting each other. */
export function planRenames(names, opts) {
  if (!/^\d{4}-\d{2}-\d{2}$/.test(opts.date)) throw new Error('date must look like 2026-03-02');
  const used = new Set();
  const plan = [], skipped = [];
  for (const from of names) {
    let to = standardName(from, opts);
    if (!to) { skipped.push(from); continue; }
    const first = to;
    for (let n = 2; used.has(to); n++) to = first.replace(/(\.[a-z0-9]+)$/, `-${n}$1`);
    used.add(to);
    if (to !== from) plan.push({ from, to });
  }
  return { plan, skipped };
}

const files = ['Till 3 Receipt.PDF', 'till 3 receipt (copy).pdf', 'Till-7 receipt.pdf', 'notes'];
const { plan, skipped } = planRenames(files, OPTIONS);
for (const r of plan) console.log(`${OPTIONS.dryRun ? '[dry run] ' : ''}${r.from} → ${r.to}`);
if (skipped.length) console.log('Skipped (no name or extension):', skipped.join(', '));
console.log(`${plan.length} files renamed, ${skipped.length} skipped`);
rename.test.js
import { standardName, planRenames } from './rename';

const opts = { prefix: 'receipt', date: '2026-03-02' };

test('standardName: lower case, dashes, prefix and date first', () => {
  expect(standardName('Till 3 Receipt.PDF', opts)).toBe('receipt-2026-03-02-till-3.pdf');
});
test('standardName: a name with no extension is skipped', () => {
  expect(standardName('notes', opts)).toBe(null);
});
test('planRenames: two files never get the same name', () => {
  const { plan } = planRenames(['Till 3 Receipt.pdf', 'till 3 receipt (copy).pdf'], opts);
  expect(plan.map((r) => r.to)).toEqual(['receipt-2026-03-02-till-3.pdf', 'receipt-2026-03-02-till-3-2.pdf']);
});
test('planRenames: a bad date stops before anything is renamed', () => {
  expect(() => planRenames(['a.pdf'], { prefix: 'x', date: '2/3/26' })).toThrow('date must look like');
});

The lesson behind it

Learn why it works, with a podcast and a quiz for each lesson.

  • API client with retries

    A fetch wrapper with a timeout, retries with growing waits, and errors that say what failed.

  • CSV cleaner

    Read a CSV, trim and fix values, drop broken rows with a reason, and write clean CSV back.

  • Unit-test starter

    A function with a test file: the happy path, the edges and the errors, grouped and named clearly.

All code templates · Every template