refactor(backends): extract shared I/O helpers for single-stream codecs
Every single-stream backend (gzip, xz, bzip2, zstd, lz4, brotli, snappy,
lzma) had the same ~40 lines of boilerplate per compress/extract for
resolving CmprssInput into a Read, resolving CmprssOutput into a Write,
and rejecting directory inputs/outputs. Consolidate that into a new
stream module and have each backend call open_input, open_output, and
guard_file_output.
Net -323 lines across the eight backends. Also normalizes the
directory-rejection error messages and extends the directory-input
guard to xz and bzip2, which previously only surfaced the lower-level
'Is a directory' error from File::open.