chmod Calculator — Python source
Compute Unix file permissions between octal (e.g. 755), symbolic (rwxr-xr-x), and decimal - including setuid, setgid, and sticky bits. Toggle permissions interactively, fully client-side, with a shareable link.
This is the Python implementation — the same logic the interactive tool runs, in a shareable, citable form.
"""chmod-calculator — POSIX permission mode converter (octal <-> symbolic).
Language: Python (3.9+, standard library only)
Source: CosmoDev polyglot showcase port of the Chmod Calculator tool, ported
from src/lib/chmod.ts (the canonical TypeScript lib) and held in
lock-step with cli/chmod-calculator/chmod-calculator.go.
License: display source — part of CosmoDev's polyglot tool pages.
Design goals:
- Pure + deterministic; never raises.
- Functionally equivalent to the TS/Go references: same inputs -> same outputs.
- Self-contained: stdlib only (no pip packages).
Converts between 3-4 digit octal ("755" / "4755"), 9-char symbolic
("rwxr-xr-x"), and the raw decimal mode, including the setuid / setgid /
sticky special bits (the s/S and t/T markers in the exec slot).
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Literal, Optional, Tuple
__all__ = [
"ChmodResult",
"from_octal",
"from_symbolic",
"mode_to_octal",
"mode_to_symbolic",
"octal_to_mode",
"symbolic_to_mode",
]
Pos = Literal["owner", "group", "other"]
@dataclass(frozen=True)
class ChmodResult:
"""Full chmod breakdown — the Python mirror of the TS ``ChmodResult`` /
Go ``Result`` structs."""
octal: str
"""4-digit zero-padded octal, e.g. ``"0755"``."""
symbolic: str
"""9-char ``rwxrwxrwx`` with special markers, e.g. ``"rwsr-xr-x"``."""
decimal: int
"""Raw integer mode (0-4095)."""
setuid: bool
setgid: bool
sticky: bool
def _parse_triplet(tri: str, pos: Pos) -> Optional[Tuple[int, int]]:
"""Parse a 3-char rwx triplet at ``pos``.
The exec slot may carry a special-bit marker: s/S (setuid in owner, setgid
in group) or t/T (sticky in other). Returns ``(digit, special)`` or ``None``
when invalid.
"""
if len(tri) != 3:
return None
digit = 0
if tri[0] == "r":
digit |= 4
elif tri[0] != "-":
return None
if tri[1] == "w":
digit |= 2
elif tri[1] != "-":
return None
special = 0
c = tri[2]
if c == "x":
digit |= 1
elif c == "-":
pass
elif c in ("s", "S") and pos in ("owner", "group"):
if c == "s":
digit |= 1
special = 4 if pos == "owner" else 2
elif c in ("t", "T") and pos == "other":
if c == "t":
digit |= 1
special = 1
else:
return None
return (digit, special)
def _format_triplet(digit: int, has_special: bool, marker: str) -> str:
"""Render a 0-7 digit + optional special bit as a 3-char triplet.
``marker`` is ``'s'`` (owner/group) or ``'t'`` (other); upper-cased when the
exec bit is absent — yielding ``'S'`` / ``'T'``.
"""
out = ("r" if digit & 4 else "-") + ("w" if digit & 2 else "-")
exec_bit = digit & 1 != 0
if has_special and exec_bit:
out += marker
elif has_special:
out += marker.upper()
elif exec_bit:
out += "x"
else:
out += "-"
return out
def symbolic_to_mode(sym: str) -> Optional[int]:
"""Parse symbolic notation (``"rwxr-xr-x"``) into a raw mode integer, or ``None``."""
s = sym.strip()
if len(s) != 9:
return None
o = _parse_triplet(s[0:3], "owner")
g = _parse_triplet(s[3:6], "group")
ot = _parse_triplet(s[6:9], "other")
if o is None or g is None or ot is None:
return None
special = o[1] | g[1] | ot[1]
return special * 0o1000 + (o[0] << 6) + (g[0] << 3) + ot[0]
def octal_to_mode(octal: str) -> Optional[int]:
"""Parse a 3-4 digit octal string (``"755"`` / ``"4755"``) into a raw mode, or ``None``."""
s = octal.strip()
if len(s) not in (3, 4) or any(c < "0" or c > "7" for c in s):
return None
return int(s, 8)
def mode_to_symbolic(mode: int) -> str:
"""Render a raw mode as 9-char symbolic notation."""
special = (mode >> 9) & 7
return (
_format_triplet((mode >> 6) & 7, special & 4 != 0, "s")
+ _format_triplet((mode >> 3) & 7, special & 2 != 0, "s")
+ _format_triplet(mode & 7, special & 1 != 0, "t")
)
def mode_to_octal(mode: int) -> str:
"""Render a raw mode as a 4-digit zero-padded octal string."""
return f"{mode & 0o7777:04o}"
def _build_result(mode: int) -> ChmodResult:
special = (mode >> 9) & 7
return ChmodResult(
octal=mode_to_octal(mode),
symbolic=mode_to_symbolic(mode),
decimal=mode & 0o7777,
setuid=special & 4 != 0,
setgid=special & 2 != 0,
sticky=special & 1 != 0,
)
def from_symbolic(sym: str) -> Optional[ChmodResult]:
"""Build a full result from symbolic notation, or ``None`` if invalid."""
mode = symbolic_to_mode(sym)
return None if mode is None else _build_result(mode)
def from_octal(octal: str) -> Optional[ChmodResult]:
"""Build a full result from an octal string, or ``None`` if invalid."""
mode = octal_to_mode(octal)
return None if mode is None else _build_result(mode)
# --- showcase assertions (the canonical suite lives in src/lib) ------------------
if __name__ == "__main__":
r = from_octal("755")
assert r is not None
assert (r.octal, r.symbolic, r.decimal) == ("0755", "rwxr-xr-x", 0o755)
assert not (r.setuid or r.setgid or r.sticky)
assert from_symbolic("rwxr-xr-x").octal == "0755" # type: ignore[union-attr]
su = from_octal("4755") # setuid over rwxr-xr-x -> exec slot becomes 's'
assert su is not None
assert (su.symbolic, su.decimal, su.setuid) == ("rwsr-xr-x", 0o4755, True)
st = from_octal("1644") # sticky over rw-r--r--, no exec -> marker 'T'
assert st is not None
assert (st.symbolic, st.decimal, st.sticky, st.setuid) == ("rw-r--r-T", 0o1644, True, False)
assert octal_to_mode("999") is None # '9' is not an octal digit
assert symbolic_to_mode("rwx") is None # wrong length
assert from_octal("0000").symbolic == "---------" # type: ignore[union-attr]
print("All chmod showcase tests passed.")
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 →