o
odinpkg.dev
packages / library / jc-odin

jc-odin

b5e7496library

Odin port of jc (kellyjonbrazil/jc): 150 parsers, 514 passing fixtures.

No license · updated 2 months ago

jc-odin

An Odin port of jc (Kelly Brazil) — converts the output of common Unix commands and many text/file formats into JSON.

Status

  • 150 parsers ported from the original 240
  • 514 fixture-based tests pass against the upstream jc reference outputs (0 failing)
  • Built and tested with Odin dev-2026-04 on macOS arm64

Build & run

odin build src -out:jc
./jc --list-parsers
echo "127.0.0.1 localhost" | ./jc --hosts -p
free | ./jc --free
uname -a | ./jc --uname -p

Flags:

flag meaning
--<parser-name> choose a parser (e.g. --free)
-p pretty-print
-r raw output (skip post-processing)
--list-parsers list every registered parser
--about runtime info as JSON
-h/--help usage

Tests

The test harness compares each parser's output against the upstream jc JSON fixtures so we can prove behavioural parity.

./run_tests.sh           # build + run every fixture pair

It reads test cases from tests/cases*.tsv (one shard per implementation batch). Each row points at a .out (raw input) and .json (expected output) file from the upstream jc test fixtures. Comparison is semantic JSON equality (via python3 -c "import json; …"), so insertion order in objects matters but whitespace does not.

To run the suite you need the upstream jc repository checked out as a sibling directory at ../jc/ (or anywhere — the absolute paths in cases.tsv files point at /Users/... today; adjust if you relocate).

Layout

src/
  main.odin              entry, argv, --flags, stdin slurp
  json.odin              Value union (bool, i64, u64, f64, string, [dynamic]Value, ^Object),
                         encoder + minimal decoder
  registry.odin          ParserInfo + register_parser
  registry_all.odin      master register_all() that calls every batch
  registry_batch_*.odin  one shard per implementation batch
  util.odin              line/word splitters, simple_table_parse, sparse_table_parse,
                         convert_to_int, convert_size_to_int, etc.
  parser_<name>.odin     one parser per file
tests/
  cases*.tsv             parser_flag<TAB>input_path<TAB>expected_path
run_tests.sh             build + diff harness
AGENT_SPEC.md            conventions used during the multi-agent port

Implemented parser categories

  • System info: env, hosts, uname, lsb-release, os-release, free, uptime
  • Identity / accounts: id, group, passwd, shadow, gshadow, hash, hashsum, cksum
  • Filesystem / processes: df, du, mount, ps, lsof, jobs, fstab, kv, kv-dup, pgpass, ls, wc, find, findmnt, lsattr, file, stat (limited), swapon, lsblk, zipinfo
  • Networking: ifconfig, route, arp, host, blkid, ping, traceroute, tracepath, ip-route, iwconfig, wg-show, ufw-appinfo, dmidecode, lspci, certbot
  • Service / time: systemctl (and -lj/-ls/-luf variants), ntpq, time, zpool-iostat, zpool-status, timedatectl, timezone
  • Text formats: ini, csv, m3u, semver, email-address, path, path-list
  • Hardware: lsblk, lspci, dmidecode, efibootmgr, amixer, airport, udevadm, chage
  • Linux /proc/*: 33 parsers covering loadavg, meminfo, cpuinfo, version, modules, filesystems, buddyinfo, consoles, crypto, devices, diskstats, mtrr, vmstat, stat, swaps, zoneinfo, driver-rtc, interrupts, iomem, ioports, locks, partitions, pagetypeinfo, slabinfo, softirqs, vmallocinfo, plus net-{arp,dev,dev-mcast,if-inet6,igmp,igmp6,ipv6-route,netlink,netstat,packet,protocols,route,tcp,unix} and pid-{io,statm,stat,fdinfo,maps,mountinfo,numa-maps,smaps,status}
  • Misc commands: acpi, debconf-show, dpkg-l, lsmod, last, history, sysctl, finger, w, who, cbt, veracrypt, nsd-control, iostat, mpstat, vmstat, ssh-conf, apt-cache-show, apt-get-sqq, crontab, crontab-u, pip-list, pip-show, iptables, needrestart, resolve-conf, update-alt-q, update-alt-gs

Run ./jc --list-parsers to see every registered parser.

Skipped (and why)

These parsers exist upstream but are not ported here — typically because they need substantial library support that Odin core does not provide, or their fixtures embed timezone-dependent timestamps that cannot be reproduced exactly on a different machine.

Reason Examples
Heavy stdlib dependency url, ip-address (Python urllib.parse/ipaddress)
Format parsers Odin core lacks xml, yaml, plist, toml
asn1 / cryptographic x509-cert, x509-csr, x509-crl, jwt
TZ-dependent *_epoch fields date, stat, mdadm, certbot-certs (some), tune2fs, rpm-qi
Very large / polymorphic netstat, ss, lsusb, top, iftop, hciconfig
Heavy regex/state machines dig, nmcli, ufw (ruleset), cef, clf, http-headers, xrandr
Sub-format wrappers (*_s streaming) csv-s, cef-s, ping-s, top-s, syslog-s, …

PRs filling these are welcome.

Architecture

A parser is a single proc:

ParserProc :: proc(data: string, raw: bool) -> Value

Every parser file does two things:

package main

register_<name> :: proc() {
    register_parser(ParserInfo{
        name        = "<flag>",          // "--<flag>" on the CLI
        description = "...",
        tags        = []string{"command"},
        parse       = parse_<name>,
    })
}

parse_<name> :: proc(data: string, raw: bool) -> Value {
    // mirror jc/jc/parsers/<name>.py exactly
}

Registration is centralised in registry_all.odin, which calls one register_batch_<id>() per implementation batch. Each batch lives in its own shard so multiple developers (or agents) can extend the project without stepping on each other's file edits.

The test harness, the AGENT_SPEC briefing, and the batch shards together made it practical to do most of this port via parallel agents fanning out from a small hand-written core.

Acknowledgements

  • Original jc © Kelly Brazil — MIT license. https://github.com/kellyjonbrazil/jc
  • The fixtures used to drive the port live in the upstream jc repository's tests/fixtures/ directory and are not redistributed here.

License

MIT, matching upstream jc.