chmod Calculator — Ruby 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 Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# chmod-calculator — POSIX permission mode converter (octal <-> symbolic).
#
# Language: Ruby (3.2, standard library only — core, no gems)
# 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; returns nil on invalid input (never raises).
# - Functionally equivalent to the TS/Go references: same inputs -> same outputs.
# - Self-contained: core classes only (no gems).
#
# 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).
module Chmod
# Full chmod breakdown — the Ruby mirror of the TS `ChmodResult` / Go `Result`.
# Data (Ruby 3.2's immutable value object) mirrors the frozen dataclass of
# python.py.
Result = Data.define(:octal, :symbolic, :decimal, :setuid, :setgid, :sticky)
class << self
# Parse symbolic notation ("rwxr-xr-x") into a raw mode integer, or nil.
def symbolic_to_mode(sym)
s = sym.strip
return nil if s.length != 9
o = parse_triplet(s[0, 3], :owner)
g = parse_triplet(s[3, 3], :group)
ot = parse_triplet(s[6, 3], :other)
return nil if o.nil? || g.nil? || ot.nil?
special = o[1] | g[1] | ot[1]
special * 0o1000 + (o[0] << 6) + (g[0] << 3) + ot[0]
end
# Parse a 3-4 digit octal string ("755" / "4755") into a raw mode, or nil.
def octal_to_mode(octal)
s = octal.strip
return nil if s.length != 3 && s.length != 4
return nil unless s.each_char.all? { |ch| ch.between?('0', '7') }
s.to_i(8)
end
# Render a raw mode as 9-char symbolic notation.
def mode_to_symbolic(mode)
special = (mode >> 9) & 7
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')
end
# Render a raw mode as a 4-digit zero-padded octal string.
def mode_to_octal(mode)
format('%04<value>o', value: mode & 0o7777)
end
# Build a full result from symbolic notation, or nil if invalid.
def from_symbolic(sym)
mode = symbolic_to_mode(sym)
mode.nil? ? nil : build_result(mode)
end
# Build a full result from an octal string, or nil if invalid.
def from_octal(octal)
mode = octal_to_mode(octal)
mode.nil? ? nil : build_result(mode)
end
private
# Parse a 3-char rwx triplet at `pos` (:owner / :group / :other). 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 nil.
def parse_triplet(tri, pos)
return nil if tri.length != 3
digit = 0
case tri[0]
when 'r' then digit |= 4
when '-' then nil
else return nil
end
case tri[1]
when 'w' then digit |= 2
when '-' then nil
else return nil
end
special = 0
case tri[2]
when 'x' then digit |= 1
when '-' then nil
when 's', 'S'
return nil unless pos == :owner || pos == :group
digit |= 1 if tri[2] == 's'
special = pos == :owner ? 4 : 2
when 't', 'T'
return nil unless pos == :other
digit |= 1 if tri[2] == 't'
special = 1
else
return nil
end
[digit, special]
end
# 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'.
def format_triplet(digit, has_special, marker)
out = +(digit & 4 != 0 ? 'r' : '-')
out << (digit & 2 != 0 ? 'w' : '-')
exec = (digit & 1) != 0
out << if has_special && exec
marker
elsif has_special
marker.upcase
elsif exec
'x'
else
'-'
end
out
end
def build_result(mode)
special = (mode >> 9) & 7
Result.new(
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,
)
end
end
end
# --- showcase assertions (the canonical suite lives in src/lib) ------------------
if $PROGRAM_NAME == __FILE__
r = Chmod.from_octal('755')
raise '755 octal' unless r.octal == '0755'
raise '755 symbolic' unless r.symbolic == 'rwxr-xr-x'
raise '755 decimal' unless r.decimal == 0o755
raise '755 no special bits' if r.setuid || r.setgid || r.sticky
raise 'symbolic round-trip' unless Chmod.from_symbolic('rwxr-xr-x')&.octal == '0755'
su = Chmod.from_octal('4755') # setuid over rwxr-xr-x -> exec slot becomes 's'
raise "4755 renders 's' marker" unless su.symbolic == 'rwsr-xr-x'
raise '4755 decimal' unless su.decimal == 0o4755
raise '4755 setuid only' unless su.setuid && !su.sticky
st = Chmod.from_octal('1644') # sticky over rw-r--r--, no exec -> marker 'T'
raise "1644 renders 'T' marker" unless st.symbolic == 'rw-r--r-T'
raise '1644 decimal' unless st.decimal == 0o1644
raise '1644 sticky only' unless st.sticky && !st.setuid
raise "'9' is not an octal digit" unless Chmod.octal_to_mode('999').nil?
raise 'wrong length rejected' unless Chmod.symbolic_to_mode('rwx').nil?
raise 'zero round-trip' unless Chmod.from_octal('0000').symbolic == '---------'
puts 'All chmod showcase tests passed.'
end
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 →