Skip to content

List Converter — Python source

Transform a list between separators (newline, comma, space, pipe, semicolon, tab) with trim, dedupe, sort, and empty-removal options. Runs entirely in your browser, with a shareable link.

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

"""list-converter — convert lists between separators (newline/comma/space/pipe/...).

Language: Python (3.9+, standard library only)
Source:   CosmoDev polyglot showcase port of the List Converter tool, ported from
          cli/list-converter/list-converter.go (the Go CLI twin), which is itself
          the lock-step mirror of src/lib/list-converter.ts (the canonical
          TypeScript implementation).
License:  display source — part of CosmoDev's polyglot tool pages.

Design goals:
  - Pure + deterministic; never raises.
  - Functionally equivalent to the Go twin: same inputs -> same outputs.
  - Self-contained: stdlib only (no pip packages).

Pipeline: split on the "from" separator -> (trim each item) -> (drop empties)
-> (dedup, optionally case-insensitive, first occurrence wins) -> (stable sort,
optionally case-insensitive) -> join with the "to" separator. ``Separator`` and
``ListOptions`` mirror the Go types exactly; defaults are from=newline,
to=comma (the TS ``resolveSep`` fallbacks).
"""

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import List, Optional

__all__ = ["Separator", "ListOptions", "convert", "convert_default"]


class Separator(Enum):
    """A built-in list separator. Mirrors the Go twin's ``Separator`` enum;
    the default ``NEWLINE`` matches the TS ``from`` default."""

    NEWLINE = "\n"
    COMMA = ","
    SPACE = " "
    PIPE = "|"
    SEMICOLON = ";"
    TAB = "\t"

    @classmethod
    def from_name(cls, name: str) -> "Separator":
        """Resolve by the TS-style lowercase name (``"comma"``, ``"newline"``
        ...). Falls back to treating ``name`` as a literal separator string —
        but since this port mirrors the enum-only Go twin, callers normally
        pass the enum directly. Provided for API symmetry with the TS source."""
        lookup = {
            "newline": cls.NEWLINE,
            "comma": cls.COMMA,
            "space": cls.SPACE,
            "pipe": cls.PIPE,
            "semicolon": cls.SEMICOLON,
            "tab": cls.TAB,
        }
        return lookup[name] if name in lookup else cls(name)


@dataclass
class ListOptions:
    """Options mirror the Go twin's ``Options`` struct.

    Every field defaults to the TS default, so callers can construct the object
    incrementally with keyword args (a dataclass gives that for free and keeps
    the option set self-documenting).
    """

    from_: Separator = Separator.NEWLINE
    """Source separator. (Trailing underscore avoids the ``from`` keyword.)"""

    to: Separator = Separator.COMMA
    """Target separator."""

    trim: bool = False
    """``str.strip()`` each split item (runs before ``remove_empty``)."""

    remove_empty: bool = False
    """Drop items equal to ``""`` (after ``trim`` if enabled)."""

    unique: bool = False
    """Keep the first occurrence of each item, dropping later duplicates."""

    sort: bool = False
    """Stable-sort the items ascending."""

    case_insensitive: bool = False
    """When set, ``unique``/``sort`` compare lowercased keys but keep original
    casing in the output. Mirrors Go's ``CaseInsensitive`` flag."""


def convert(input: str, options: Optional[ListOptions] = None) -> str:
    """Convert a list between separators. Never raises; an empty ``input``
    simply yields a one-item list joined back together (mirroring Go's
    ``strings.Split("", sep) == [""]``)."""
    if options is None:
        options = ListOptions()

    items: List[str] = input.split(options.from_.value)

    if options.trim:
        items = [s.strip() for s in items]
    if options.remove_empty:
        items = [s for s in items if s != ""]
    if options.unique:
        # Keyed by the (optionally lowercased) value; the first-seen item
        # survives with its original casing. dict preserves insertion order
        # and dedups by key, so one pass gives us both properties.
        seen: dict[str, str] = {}
        for s in items:
            key = s.lower() if options.case_insensitive else s
            if key not in seen:
                seen[key] = s
        items = list(seen.values())
    if options.sort:
        # sorted() is stable; key lowercases only for comparison when
        # case_insensitive is set, leaving the stored values untouched.
        items = sorted(
            items,
            key=lambda s: s.lower() if options.case_insensitive else s,
        )
    return options.to.value.join(items)


def convert_default(input: str) -> str:
    """Convenience wrapper using default options — the common "newline list to
    a comma list" case (matches the tool's default From/To)."""
    return convert(input, ListOptions())

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 →