RemiZstd is a set of bindings for Nim of the ZStandard C library. They are essentially a port of the Crystal bindings by Didactic Drunk. The original Crystal bindings can be found here: https://github.com/didactic-drunk/zstd.cr
If you want an alternative set of bindings, check out https://github.com/wltsmrz/nim_zstd
Some features include:
export ZSTD_CLEVEL=1 sets the
default compression level just like the zstd
command line utilities.You will need the ZStandard development files on your system. Something like one of the following should work on Linux:
slackpkg install zstdpacman -S zstdapt-get install libzstd-devyum install libzstd-develOnce you have the ZStandard development files installed, you will then need to prepare the repository for use. Note that I do not use Git nor Mercurial, so the process here this may seem a bit out of the ordinary. However, in the end, you can still use Nimble just like with other Nim packages.
nimble install to install
the library.To import all of the library (minus the
low-level FFI stuff), you can just use
import remizstd. Or, you can import
just what you need:
remizstd/common: Custom
dictionaries and some common
types/methods/valuesremizstd/compress:
Context-based compressionremizstd/decompress:
Context-based decompressionremizstd/compstream:
Compressing streamsremizstd/decompstream:
Decompression streamsremizstd/libffi: Low-level C
bindingsFull example:
import std/streams
import remizstd/[compstream, decompstream]
# Generate the test file
let filename = "/tmp/somefile.txt"
writeFile(filename, "This is a test file for remizstd :D")
# Compress to a file stream
var outFile = newFileStream("/tmp/somefile.txt.zst", fmWrite)
assert(not isNil(outFile))
withCompressStream(outFile, 9, cio): # Compression level 9
cio.syncClose = true
cio.write(readFile(filename))
# We set cio.syncClose to true, so outFile is automatically closed as well
# Decompress from one file stream to another file stream
let cfilename = "/tmp/somefile.txt.zst"
let dfilename = "/tmp/somefile.txt.orig"
var inFile = newFileStream(cfilename, fmRead)
outFile = newFileStream(dfilename, fmWrite)
assert(not isNil(outFile))
withDecompressStream(inFile, dio):
outFile.write(dio.readAll())
# Note we didn't set dio.syncClose to true, so we have to close our output file manually
outFile.close()
assert(readFile(filename) == readFile(dfilename))The streaming API is used to compress data to a stream, or decompress data from a stream. It also includes a few templates that provide useful constructs that will automatically handle closing of the (de)compression stream for you.
Example usage of the streaming API:
import std/streams
import remizstd/[compstream]
# Compression to a string stream
var dest = newStringStream()
withCompressStream(dest, cio):
cio.write("This is some test data")The context-based API uses wrappers around
the lower-level ZstdCCtx pointers.
These allow you to allocate a (de)compression
context once, then re-use it for successive
operations. This will generally be more friendly
towards your system’s memory.
Note: Contexts are mostly just an optimization for speed and resource usage. It does not change the compression ratio.
Note: In multi-threaded environments, use one different context per thread for parallel execution.
Example usage of the context-based API:
import remizstd/[compress, decompress]
let buf = "this is a test buffer"
# Compression using a context
var
cctx = newCompressCtx(1) # Compression level of 1
cbuf = cctx.compress(buf)
# Decompression using a context
var
dctx = newDecompressCtx()
dbuf = dctx.decompress(cbuf)
assert(dbuf == buf)
func libVersion(): stringReturns the current version of libzstd.
type CompressionLevel* = cintA compression level.
type ZStdError = object of CatchableErrorAll errors in remizstd are of this type, or a subtype.
type CompressDictObj = objectA wraper for a ZstdCDict
pointer. You should always use CompressDict
instead.
type CompressDict = ref CompressDictObjA managed wraper for a ZstdCDict
pointer.
type DecompressDictObj = objectA wraper for a ZstdDDict
pointer. You should always use DecompressDict
instead.
type DecompressDict = ref DecompressDictObjA managed wraper for a ZstdDDict
pointer.
type Dict = ref object of RootObjAn in-memory representation of a custom dictionary. It has no exported fields.
var DefaultCompLevel: CompressionLevel The default compression level. When the
bindings are first loaded, this will check to
see if the ZSTD_CLEVEL environment
variable is set. If it is, it uses the integer
value from that variable. Otherwise this
defaults to 3.
var MinCompLevel*: CompressionLevelThe lowest supported compression level. This
will be set when the program is started using a
call to the lower level
min_c_level().
var MaxCompLevel*: CompressionLevelThe highest supported compression level. This
will be set when the program is started using a
call to the lower level
max_c_level().
proc newDict*(buf: string, level: CompressionLevel = DefaultCompLevel): DictCreates a new custom dictionary using
buf as the dictionary data.
proc dictID*(d: Dict): cuintReturns the ID of the Dict.
type CompressCtxObj* = objectA wrapper and some state around
ZstdCCtx used to compress data. You
should use CompressCtx instead.
type CompressCtx* = ref CompressCtxObjA managed context used to compress data.
proc setDict*(ctx: CompressCtx, d: Dict)Sets the custom dictionary on a CompressCtx.
proc newCompressCtx*(level: CompressionLevel, dict: Dict = nil): CompressCtxCreates a new context for compressing data at the given compression level. You can optionally pass a custom dictionary to this to use for compression.
Example showing a custom dictionary:
import remizstd/[common, compress]
let myCompLevel = 7
# Load and create custom dictionary with compression level 7
let dictData = readFile("/path/to/dict/file.dat")
var dictionary = newDict(dictData, myCompLevel)
# Compression using a context
var
cctx = newCompressCtx(myCompLevel, dictionary)
cbuf = cctx.compress("Just some test data to compress, could be binary instead")
proc newCompressCtx*(dict: Dict = nil): CompressCtxCreates a new context for compressing data using the DefaultCompLevel. You can optionally pass a custom dictionary to this to use for compression.
proc compress*(ctx: CompressCtx, src: string): stringCompresses src using the given
context, then returns the compressed data.
Example:
import remizstd/compress
# Compression using a context
var
cctx = newCompressCtx(1) # Compression level of 1
cbuf = cctx.compress("this is some test data)
# cbuf now holds the compressed data
func level*(ctx: CompressCtx): CompressionLevelReturns the current compression level for the context.
proc setLevel*(ctx: CompressCtx, level: CompressionLevel)Changes the context’s compression level.
func threads*(ctx: CompressCtx): intReturns the number of threads the context will use for compression.
proc setThreads*(ctx: CompressCtx, num: int)Sets the number of threads the context will use for compression.
func checksum*(ctx: CompressCtx): bool {.inline.}Returns true if the checksum
flag is set in the context, or
false otherwise.
proc setChecksum*(ctx: CompressCtx, val: bool) {.inline.}Changes whether or not the checkum flag is set in the context.
func compressBound*(ctx: CompressCtx, size: int): intGiven data of length size, this
returns the maximum compressed size in worst
case single-pass scenario.
func memsize*(ctx: CompressCtx): csize_tReturns the size of the wrapped
ZstdCCtx in memory.
type DecompressCtxObj* = objectA wrapper and some state around
ZstdDCtx used to compress data. You
should use DecompressCtx
instead.
type DecompressCtx* = ref DecompressCtxObjA managed context used to decompress data.
type FrameSizeUnknownError* = object of ZstdErrorIndicates that the library cannot automatically determine the destination size. Instead, you should use the streaming API in remizstd/decompstream.
See decompress().
proc setDict*(ctx: DecompressCtx, d: Dict)Sets the custom dictionary on a decompression context.
proc newDecompressCtx*(dict: Dict = nil): DecompressCtxCreates a new context for decompressing data. You can optionally pass a custom dictionary to this to use for decompression.
func frameContentSize*(ctx: DecompressCtx, src: string): (uint64, bool)Attempts to determine the size of
src after decompression. If
successful, this returns the size of
src after decompression and
true. If this cannot determine the
size, this returns zero and
false.
proc decompress*(ctx: DecompressCtx, src: string, size = 0): stringDecompresses src and returns the
decompressed data. If size is zero,
then this attempts to determine the correct size
for the return value by calling frameContentSize().
If size is zero and this cannot
determine the size of the return value
automatically, this will raise a FrameSizeUnknownError.
Example showing compression, then decompression:
import remizstd/[compress, decompress]
let buf = "this is a test buffer"
# Compression using a context
var
cctx = newCompressCtx(1) # Compression level of 1
cbuf = cctx.compress(buf)
# Decompression using a context
var
dctx = newDecompressCtx()
dbuf = dctx.decompress(cbuf)
assert(dbuf == buf)
func memsize*(ctx: DecompressCtx): csize_tReturns the size of the wrapped
ZstdDCtx in memory.
type
CompressStream* = ref object of RootObj
syncClose*: boolRepresents a stream that uncompressed data
can be written to in order to be compressed. The
syncClose field controls whether or
not the underlying stream is closed when the close() proc is
called.
A CompressStream essentially
wraps CompressCtx to
provide a stream-like API.
Note: This does not currently implement the full api as found in std/streams.
func level*(strm: CompressStream): CompressionLevel {.inline.}Returns the current compression level for this stream.
proc setLevel*(strm: CompressStream, level: CompressionLevel) {.inline.}Sets the compression level for this stream.
func threads*(strm: CompressStream): int {.inline.}Returns the number of threads this stream will use for compression.
proc setThreads*(strm: CompressStream, num: int) {.inline.}Sets the number of threads this stream will use for compression.
func checksum*(strm: CompressStream): bool {.inline.}Returns true if the checksum
flag is used by the underlying compression
context, or false otherwise.
proc setChecksum*(strm: CompressStream, val: bool) {.inline.}Sets whether or not the underlying context uses the checkum flag.
func closed*(strm: CompressStream): bool {.inline.}Returns true if the stream is
closed, or false otherwise. This
does not consider the underlying stream.
proc newCompressStream*(io: Stream, level: CompressionLevel, outputBufSize = 0): CompressStreamCreates a new stream for compression using
the given compression level. Data written to
this stream will be compressed and output to
io. If outputBufSize
is zero, then the default output stream size as
reported by libzstd will be used.
proc newCompressStream*(io: Stream): CompressStreamCreates a new stream for compression using
the DefaultCompLevel.
Data written to this stream will be compressed
and output to io.
proc write*(strm: CompressStream, buf: string) {.inline.}Compresses buf and writes the
compressed data to the underlying stream.
proc close*(strm: CompressStream)Closes the compression stream. If the
syncClose field is true, then the
underlying stream is also closed, otherwise it
remains open.
template withCompressStream*(io, strmVar, forms: untyped)Creates a CompressStream bound
to strmVar that will write
compressed data to io, then
executes forms inside of a block.
The compression stream will use the DefaultCompLevel.
This ensures that close() is
called when the block exits.
Example:
import std/streams
import remizstd/[compstream]
# Compression to a string stream
var dest = newStringStream()
withCompressStream(dest, cio):
cio.write("This is some test data")
template withCompressStream*(io, level, strmVar, forms: untyped)Creates a CompressStream bound
to strmVar that will write
compressed data to io using the
given compression level, then executes
forms inside of a block. This
ensures that close() is
called when the block exits.
template withCompressStream*(io, level, outputBufSize, strmVar, forms: untyped) =Creates a CompressStream bound
to strmVar that will write
compressed data to io using the
given compression level, then executes
forms inside of a block. This also
sets the size of the output buffer. This ensures
that close()
is called when the block exits.
type
DecompressStream* = ref object of RootObj
syncClose*: boolRepresents a stream that compressed data can
be written to in order to be decompressed. The
syncClose field controls whether or
not the underlying stream is closed when the close() proc
is called.
A DecompressStream essentially
wraps DecompressCtx
to provide a stream-like API.
Note: This does not currently implement the full api as found in std/streams.
func dict*(strm: DecompressStream): Dict {.inline.}Returns the custom dictionary assigned to the underlying compression context.
proc setDict*(strm: DecompressStream, d: Dict) {.inline.}Sets the custom dictionary assigned to the underlying compression context.
proc newDecompressStream*(io: Stream, dict: Dict = nil, inputBufSize = 0): DecompressStreamCreates a new DecompressStream.
Data written to this stream will be decompressed
and output to io. If
inputBufSize is zero, then the
default input stream size as reported by libzstd
will be used. dict may optionally
be a custom decompression dictionary.
proc setIO*(strm: DecompressStream, newStream: Stream)Sets a new underlying stream to write decompressed data into. This re-opens the DecompressStream.
proc read*(strm: DecompressStream, num: Natural): (string, int)Decompresses up to num bytes of
data, then returns the decompressed data and the
actual number of bytes that was
decompressed.
This will return an empty string and zero when the end of the compressed data is reached.
proc read*(strm: DecompressStream, dest: var string): (string, int)Reads up to len(dest) bytes of
data into dest, then returns the
actual number of bytes that was decompressed
into dest.
This will return zero when the end of the compressed data is reached.
proc readAll*(strm: DecompressStream, bufferSize: Natural = 1024 * 1024): stringDecompresses all data from the stream and returns it.
proc close*(strm: DecompressStream)Closes the decompression stream. If the
syncClose field is
true, then the underlying stream is
also closed, otherwise it will remain open.
template withDecompressStream*(io, strmVar, forms: untyped)Creates a DecompressStream
bound to strmVar that will write
decompressed data to io, then
executes forms inside of a block.
This ensures that close() is
called when the block exits.
Example, decompressing between streams:
import std/streams
import remizstd/[decompstream]
# Decompress a file stream into a string stream
var src = newFileStream("/path/to/compressed.zstd", fmRead)
var dest = newStringStream()
withDeompressStream(dest, dio):
src.write(dio.readAll())Example, decompressing to a string:
import std/streams
import remizstd/[decompstream]
# Decompress a file stream into a string stream
var src = newFileStream("/path/to/compressed.zstd", fmRead)
withDeompressStream(dest, dio):
echo "Decompressed data:"
echo dio.readAll()
template withDecompressStream*(io, inputBufSize, strmVar, forms: untyped)Creates a DecompressStream
with a custom buffer size bound to
strmVar that will write
decompressed data to io, then
executes forms inside of a block.
This ensures that close() is
called when the block exits.
template withDecompressStream*(io, dict, inputBufSize, strmVar, forms: untyped)Creates a DecompressStream
with a custom dictionary and buffer size bound
to strmVar that will write
decompressed data to io, then
executes forms inside of a block.
This ensures that close() is
called when the block exits.