Skip to content

Word & Character Counter — Python source

Live word, character, sentence, and paragraph counts plus reading-time estimate as you type.

This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.

"""word-counter — Python
======================

Polyglot showcase port of CosmoDev's "word-counter" tool, ported from the
canonical TypeScript at src/lib/wordCount.ts (the live web library and
unit-test surface).

Display source — part of CosmoDev's polyglot tool pages
(dev.cosmolabs.org), where each tool's pure logic is shown in six
languages side by side. Pure string logic; no cryptographic surface.

Public surface: count_words, count_chars, count_chars_no_spaces,
                count_sentences, count_paragraphs, reading_time_min.
"""

from __future__ import annotations

import re

# Compiled once at import — equivalent to the JavaScript literals
# /[^.!?]+[.!?]+/g and /\n{2,}/ used in the canonical implementation.
_SENTENCE_RE = re.compile(r"[^.!?]+[.!?]+")
_PARAGRAPH_RE = re.compile(r"\n{2,}")


def count_words(text: str) -> int:
    """Count whitespace-separated words.

    ``str.split()`` with no argument splits on runs of Unicode whitespace and
    discards leading/trailing empty fields, so empty or whitespace-only input
    yields ``[]`` and therefore 0.
    """
    return len(text.split())


def count_chars(text: str) -> int:
    """Total character count, whitespace included.

    Counts Unicode code points via ``len()``. The canonical TypeScript uses
    ``.length`` (UTF-16 code units); the two agree for all BMP text and
    diverge only for astral symbols (emoji, etc.), which JavaScript counts
    as 2.
    """
    return len(text)


def count_chars_no_spaces(text: str) -> int:
    """Character count with all whitespace removed (Unicode-aware)."""
    return len("".join(ch for ch in text if not ch.isspace()))


def count_sentences(text: str) -> int:
    """Number of sentences: maximal runs of non-terminal characters followed
    by one or more sentence terminators (. ! ?).

    Text without terminal punctuation therefore counts as 0, matching the
    canonical behaviour.
    """
    return len(_SENTENCE_RE.findall(text))


def count_paragraphs(text: str) -> int:
    """Number of paragraphs separated by one or more blank lines (2+
    newlines). Segments that are blank after stripping do not count."""
    return sum(1 for segment in _PARAGRAPH_RE.split(text) if segment.strip())


def reading_time_min(words: int) -> int:
    """Estimated reading time in whole minutes at 200 words per minute.

    Returns 0 for empty input, otherwise at least 1. Integer arithmetic —
    ``(words + 100) // 200`` — reproduces ``round(words / 200)`` exactly for
    non-negative word counts and sidesteps Python's banker's rounding, which
    would otherwise disagree with JavaScript's ``Math.round`` on exact halves
    (e.g. 2.5 → Python 2, JavaScript 3).
    """
    if words <= 0:
        return 0
    return max(1, (words + 100) // 200)

Also available in 13 other languages

Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →