diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml
deleted file mode 100644
index e97e882..0000000
--- a/.github/workflows/cmake.yml
+++ /dev/null
@@ -1,35 +0,0 @@
-name: CMake
-
-on:
- push:
- branches: [ master ]
- pull_request:
- branches: [ master ]
-
-env:
- # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.)
- BUILD_TYPE: Release
-
-jobs:
- build:
- # The CMake configure and build commands are platform agnostic and should work equally
- # well on Windows or Mac. You can convert this to a matrix build if you need
- # cross-platform coverage.
- # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/checkout@v2
-
- - name: Install requirements
- run: chmod +x ./install.sh && ./install.sh
-
- - name: Configure CMake
- # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make.
- # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type
- run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DLIBCAESIUM_PATH=./libcaesium/target/release
-
- - name: Build
- # Build your program with the given configuration
- run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}}
-
diff --git a/.gitignore b/.gitignore
index 2e0414c..c057c10 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,185 +1,3 @@
-# Folder view configuration files
-.DS_Store
-Desktop.ini
-
-# Thumbnail cache files
-._*
-Thumbs.db
-
-# Files that might appear on external disks
-.Spotlight-V100
-.Trashes
-
-### JetBrains template
-# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
-# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
-
-# User-specific stuff:
-.idea/**/workspace.xml
-.idea/**/tasks.xml
-.idea/dictionaries
-
-# Sensitive or high-churn files:
-.idea/**/dataSources/
-.idea/**/dataSources.ids
-.idea/**/dataSources.xml
-.idea/**/dataSources.local.xml
-.idea/**/sqlDataSources.xml
-.idea/**/dynamic.xml
-.idea/**/uiDesigner.xml
-
-# Gradle:
-.idea/**/gradle.xml
-.idea/**/libraries
-
-# CMake
-cmake-build-*
-
-# Mongo Explorer plugin:
-.idea/**/mongoSettings.xml
-
-## File-based project format:
-*.iws
-
-## Plugin-specific files:
-
-# IntelliJ
-# mpeltonen/sbt-idea plugin
-.idea_modules/
-
-# JIRA plugin
-atlassian-ide-plugin.xml
-
-# Cursive Clojure plugin
-.idea/replstate.xml
-
-# Crashlytics plugin (for Android Studio and IntelliJ)
-com_crashlytics_export_strings.xml
-crashlytics.properties
-crashlytics-build.properties
-fabric.properties
-### macOS template
-# General
-.AppleDouble
-.LSOverride
-
-# Icon must end with two \r
-Icon
-
-# Thumbnails
-
-# Files that might appear in the root of a volume
-.DocumentRevisions-V100
-.fseventsd
-.TemporaryItems
-.VolumeIcon.icns
-.com.apple.timemachine.donotpresent
-
-# Directories potentially created on remote AFP share
-.AppleDB
-.AppleDesktop
-Network Trash Folder
-Temporary Items
-.apdisk
-### Linux template
-*~
-
-# temporary files which can be created if a process still has a handle open of a deleted file
-.fuse_hidden*
-
-# KDE directory preferences
-.directory
-
-# Linux trash folder which might appear on any partition or disk
-.Trash-*
-
-# .nfs files are created when an open file is removed but is still being accessed
-.nfs*
-### Windows template
-# Windows thumbnail cache files
-ehthumbs.db
-ehthumbs_vista.db
-
-# Dump file
-*.stackdump
-
-# Folder config file
-[Dd]esktop.ini
-
-# Recycle Bin used on file shares
-$RECYCLE.BIN/
-
-# Windows Installer files
-*.cab
-*.msi
-*.msm
-*.msp
-
-# Windows shortcuts
-*.lnk
-### C template
-# Prerequisites
-*.d
-
-# Object files
-*.o
-*.ko
-*.obj
-*.elf
-
-# Linker output
-*.ilk
-*.map
-*.exp
-
-# Precompiled Headers
-*.gch
-*.pch
-
-# Libraries
-*.lib
-*.a
-*.la
-*.lo
-
-# Shared objects (inc. Windows DLLs)
-*.dll
-*.so
-*.so.*
-*.dylib
-
-# Executables
-*.exe
-*.out
-*.app
-*.i*86
-*.x86_64
-*.hex
-
-# Debug files
-*.dSYM/
-*.su
-*.idb
-*.pdb
-
-# Kernel Module Compile Results
-*.mod*
-*.cmd
-.tmp_versions/
-modules.order
-Module.symvers
-Mkfile.old
-dkms.conf
-
-CMakeLists.txt.user
-CMakeCache.txt
-CMakeFiles
-CMakeScripts
-Testing
-Makefile
-cmake_install.cmake
-install_manifest.txt
-compile_commands.json
-CTestTestfile.cmake
-_deps
-build
\ No newline at end of file
+/target
+**/*.rs.bk
+test_output
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..5c98b42
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,2 @@
+# Default ignored files
+/workspace.xml
\ No newline at end of file
diff --git a/.idea/.name b/.idea/.name
deleted file mode 100644
index 572ef0b..0000000
--- a/.idea/.name
+++ /dev/null
@@ -1 +0,0 @@
-caesiumclt
\ No newline at end of file
diff --git a/.idea/caesium-clt.iml b/.idea/caesium-clt.iml
deleted file mode 100644
index f08604b..0000000
--- a/.idea/caesium-clt.iml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
\ No newline at end of file
diff --git a/.idea/cclt.iml b/.idea/cclt.iml
new file mode 100644
index 0000000..944a1fa
--- /dev/null
+++ b/.idea/cclt.iml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index ecc50f5..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,125 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index 81967a4..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
index 96d27b3..a51762a 100644
--- a/.idea/modules.xml
+++ b/.idea/modules.xml
@@ -2,7 +2,7 @@
-
+
\ No newline at end of file
diff --git a/.idea/scopes/Inspection.xml b/.idea/scopes/Inspection.xml
deleted file mode 100644
index 7eb5c79..0000000
--- a/.idea/scopes/Inspection.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
index 94a25f7..35eb1dd 100644
--- a/.idea/vcs.xml
+++ b/.idea/vcs.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100644
index ccd0de0..0000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-cmake_minimum_required(VERSION 3.16)
-project(caesiumclt)
-
-# The version number.
-set(VERSION_MAJOR 0)
-set(VERSION_MINOR 18)
-set(VERSION_PATCH 0)
-
-configure_file(
- "src/config.h.in"
- "${PROJECT_BINARY_DIR}/config.h"
-)
-if (NOT DEFINED LIBCAESIUM_PATH)
- message(FATAL_ERROR "LIBCAESIUM_PATH is not defined. Cannot find libcaesium.")
-endif ()
-
-link_directories(${LIBCAESIUM_PATH})
-
-include_directories("${PROJECT_BINARY_DIR}")
-
-add_subdirectory(src)
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..2f4f481
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,1174 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "adler"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
+
+[[package]]
+name = "adler32"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
+
+[[package]]
+name = "ahash"
+version = "0.7.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47"
+dependencies = [
+ "getrandom",
+ "once_cell",
+ "version_check",
+]
+
+[[package]]
+name = "ansi_term"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "arrayvec"
+version = "0.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6"
+
+[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi",
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
+
+[[package]]
+name = "bit-vec"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
+
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "bytemuck"
+version = "1.7.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72957246c41db82b8ef88a5486143830adeb8227ef9837740bdec67724cf2c5b"
+
+[[package]]
+name = "byteorder"
+version = "1.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
+
+[[package]]
+name = "bytes"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8"
+
+[[package]]
+name = "caesiumclt"
+version = "0.19.0"
+dependencies = [
+ "human_bytes",
+ "indicatif",
+ "infer 0.11.0",
+ "libcaesium",
+ "num_cpus",
+ "rand",
+ "rayon",
+ "structopt",
+ "walkdir",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.73"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
+dependencies = [
+ "jobserver",
+]
+
+[[package]]
+name = "cfb"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f"
+dependencies = [
+ "byteorder",
+ "fnv",
+ "uuid",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "clap"
+version = "2.33.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+dependencies = [
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim 0.8.0",
+ "textwrap 0.11.0",
+ "unicode-width",
+ "vec_map",
+]
+
+[[package]]
+name = "clap"
+version = "3.2.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "86447ad904c7fb335a790c9d7fe3d0d971dc523b8ccd1561a520de9a85302750"
+dependencies = [
+ "atty",
+ "bitflags",
+ "clap_lex",
+ "indexmap",
+ "strsim 0.10.0",
+ "termcolor",
+ "textwrap 0.15.1",
+]
+
+[[package]]
+name = "clap_lex"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
+dependencies = [
+ "os_str_bytes",
+]
+
+[[package]]
+name = "cloudflare-zlib"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cfcefb5df07f146eb15756342a135eb7d76b8bb609eff9c111f7539d060f94d"
+dependencies = [
+ "cloudflare-zlib-sys",
+]
+
+[[package]]
+name = "cloudflare-zlib-sys"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2040b6d1edfee6d75f172d81e2d2a7807534f3f294ce18184c70e7bb0105cd6f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "color_quant"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b"
+
+[[package]]
+name = "console"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89eab4d20ce20cea182308bca13088fecea9c05f6776cf287205d41a0ed3c847"
+dependencies = [
+ "encode_unicode",
+ "libc",
+ "once_cell",
+ "terminal_size",
+ "unicode-width",
+ "winapi",
+]
+
+[[package]]
+name = "crc"
+version = "3.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53757d12b596c16c78b83458d732a5d1a17ab3f53f2f7412f6fb57cc8a140ab3"
+dependencies = [
+ "crc-catalog",
+]
+
+[[package]]
+name = "crc-catalog"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d0165d2900ae6778e36e80bbc4da3b5eefccee9ba939761f9c2882a5d9af3ff"
+
+[[package]]
+name = "crc32fast"
+version = "1.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
+dependencies = [
+ "cfg-if",
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ec02e091aa634e2c3ada4a392989e7c3116673ef0ac5b72232439094d73b7fd"
+dependencies = [
+ "cfg-if",
+ "crossbeam-utils",
+ "lazy_static",
+ "memoffset",
+ "scopeguard",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d82cfc11ce7f2c3faef78d8a684447b40d503d9681acebed6cb728d45940c4db"
+dependencies = [
+ "cfg-if",
+ "lazy_static",
+]
+
+[[package]]
+name = "deflate"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c86f7e25f518f4b81808a2cf1c50996a61f5c2eb394b2393bd87f2a4780a432f"
+dependencies = [
+ "adler32",
+]
+
+[[package]]
+name = "dunce"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "453440c271cf5577fd2a40e4942540cb7d0d2f85e27c8d07dd0023c925a67541"
+
+[[package]]
+name = "either"
+version = "1.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
+
+[[package]]
+name = "encode_unicode"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
+
+[[package]]
+name = "fallible_collections"
+version = "0.4.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c195cf4b2285d3c993eb887b4dc56b0d5728bbe1d0f9a99c0ac6bec2da3e4d85"
+dependencies = [
+ "hashbrown",
+]
+
+[[package]]
+name = "filetime"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e94a7bbaa59354bc20dd75b67f23e2797b4490e9d6928203fb105c79e448c86c"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "windows-sys",
+]
+
+[[package]]
+name = "flate2"
+version = "1.0.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f82b0f4c27ad9f8bfd1f3208d882da2b09c301bc1c828fd3a00d0216d2fbbff6"
+dependencies = [
+ "crc32fast",
+ "miniz_oxide 0.5.1",
+]
+
+[[package]]
+name = "fnv"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
+
+[[package]]
+name = "getrandom"
+version = "0.2.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "gif"
+version = "0.11.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3a7187e78088aead22ceedeee99779455b23fc231fe13ec443f99bb71694e5b"
+dependencies = [
+ "color_quant",
+ "weezl",
+]
+
+[[package]]
+name = "gifsicle"
+version = "1.92.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "36998a2316aad26c8bfd74c82a8809eaf12e2216a2938bc4dca5b83c59fb3e1a"
+dependencies = [
+ "cc",
+ "libc",
+]
+
+[[package]]
+name = "glob"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
+
+[[package]]
+name = "hashbrown"
+version = "0.12.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+dependencies = [
+ "ahash",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "human_bytes"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39b528196c838e8b3da8b665e08c30958a6f2ede91d79f2ffcd0d4664b9c64eb"
+
+[[package]]
+name = "image"
+version = "0.24.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e30ca2ecf7666107ff827a8e481de6a132a9b687ed3bb20bb1c144a36c00964"
+dependencies = [
+ "bytemuck",
+ "byteorder",
+ "color_quant",
+ "gif",
+ "jpeg-decoder",
+ "num-rational",
+ "num-traits",
+ "png",
+]
+
+[[package]]
+name = "imagequant"
+version = "4.0.2"
+source = "git+https://github.com/Lymphatus/libimagequant?rev=67f1686#67f1686bfe55b9dda06760089f2f6720f3f7eeeb"
+dependencies = [
+ "arrayvec",
+ "noisy_float",
+ "once_cell",
+ "rayon",
+ "rgb",
+ "thread_local",
+]
+
+[[package]]
+name = "img-parts"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b19358258d99a5fc34466fed27a5318f92ae636c3e36165cf9b1e87b5b6701f0"
+dependencies = [
+ "bytes",
+ "crc32fast",
+ "miniz_oxide 0.5.1",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e"
+dependencies = [
+ "autocfg",
+ "hashbrown",
+ "rayon",
+]
+
+[[package]]
+name = "indicatif"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc42b206e70d86ec03285b123e65a5458c92027d1fb2ae3555878b8113b3ddf"
+dependencies = [
+ "console",
+ "number_prefix",
+ "unicode-width",
+]
+
+[[package]]
+name = "infer"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f178e61cdbfe084aa75a2f4f7a25a5bb09701a47ae1753608f194b15783c937a"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "infer"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0a6c16b11a665b26aeeb9b1d7f954cdeb034be38dd00adab4f2ae921a8fee804"
+dependencies = [
+ "cfb",
+]
+
+[[package]]
+name = "iter-read"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c397ca3ea05ad509c4ec451fea28b4771236a376ca1c69fd5143aae0cf8f93c4"
+
+[[package]]
+name = "itertools"
+version = "0.10.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "jobserver"
+version = "0.1.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "972f5ae5d1cb9c6ae417789196c803205313edde988685da5e3aae0827b9e7fd"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "jpeg-decoder"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "744c24117572563a98a7e9168a5ac1ee4a1ca7f702211258797bbe0ed0346c3c"
+
+[[package]]
+name = "lazy_static"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
+
+[[package]]
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "libcaesium"
+version = "0.10.0"
+source = "git+https://github.com/Lymphatus/libcaesium?rev=eca05e2#eca05e2eb8870c873118a9e43c2e9b60f83b12b8"
+dependencies = [
+ "bytes",
+ "gifsicle",
+ "image",
+ "imagequant",
+ "img-parts",
+ "infer 0.9.0",
+ "libc",
+ "lodepng",
+ "mozjpeg-sys",
+ "oxipng",
+ "webp",
+]
+
+[[package]]
+name = "libdeflate-sys"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "43afa5b192ff058426ba20a4f35c290ef402478d6045ac934ac15aa947a3898d"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "libdeflater"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e656b7960ec49e864badc7ad1b810427a7ac8b78511a699ce5cdc3ead0b32e5b"
+dependencies = [
+ "libdeflate-sys",
+]
+
+[[package]]
+name = "libwebp-sys"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "439fd1885aa28937e7edcd68d2e793cb4a22f8733460d2519fbafd2b215672bf"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "lodepng"
+version = "3.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ff45534ec797452c044fcd47861059eddb501e30a8fd9fdadea7957cdff3ebc7"
+dependencies = [
+ "crc32fast",
+ "fallible_collections",
+ "flate2",
+ "libc",
+ "rgb",
+]
+
+[[package]]
+name = "log"
+version = "0.4.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "memoffset"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "miniz_oxide"
+version = "0.6.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
+dependencies = [
+ "adler",
+]
+
+[[package]]
+name = "mozjpeg-sys"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac69196a2b59950122d25194985c8070f9633ac01414dce0a48925346c70c3de"
+dependencies = [
+ "cc",
+ "dunce",
+ "libc",
+ "nasm-rs",
+]
+
+[[package]]
+name = "nasm-rs"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce095842aee9aa3ecbda7a5d2a4df680375fd128a8596b6b56f8e497e231f483"
+dependencies = [
+ "rayon",
+]
+
+[[package]]
+name = "noisy_float"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "978fe6e6ebc0bf53de533cd456ca2d9de13de13856eda1518a285d7705a213af"
+dependencies = [
+ "num-traits",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-rational"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d41702bd167c2df5520b384281bc111a4b5efcf7fbc4c9c222c815b07e0a6a6a"
+dependencies = [
+ "autocfg",
+ "num-integer",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "num_cpus"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
+dependencies = [
+ "hermit-abi",
+ "libc",
+]
+
+[[package]]
+name = "number_prefix"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
+
+[[package]]
+name = "once_cell"
+version = "1.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
+
+[[package]]
+name = "os_str_bytes"
+version = "6.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ff7415e9ae3fff1225851df9e0d9e4e5479f947619774677a63572e55e80eff"
+
+[[package]]
+name = "oxipng"
+version = "6.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a40d437cd5308cba163907008d4c91a0280fc3b1ec1265dd20820e739002f4d9"
+dependencies = [
+ "bit-vec",
+ "clap 3.2.22",
+ "cloudflare-zlib",
+ "crc",
+ "crossbeam-channel",
+ "filetime",
+ "image",
+ "indexmap",
+ "itertools",
+ "libdeflater",
+ "log",
+ "miniz_oxide 0.6.2",
+ "rayon",
+ "rgb",
+ "rustc_version",
+ "stderrlog",
+ "wild",
+ "zopfli",
+]
+
+[[package]]
+name = "png"
+version = "0.17.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc38c0ad57efb786dd57b9864e5b18bae478c00c824dc55a38bbc9da95dde3ba"
+dependencies = [
+ "bitflags",
+ "crc32fast",
+ "deflate",
+ "miniz_oxide 0.5.1",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
+
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c7ed8b8c7b886ea3ed7dde405212185f423ab44682667c8c6dd14aa1d9f6612"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3d0b9745dc2debf507c8422de05d7226cc1f0644216dfdfead988f9b1ab32a7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
+dependencies = [
+ "libc",
+ "rand_chacha",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rayon"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d"
+dependencies = [
+ "autocfg",
+ "crossbeam-deque",
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f"
+dependencies = [
+ "crossbeam-channel",
+ "crossbeam-deque",
+ "crossbeam-utils",
+ "num_cpus",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
+dependencies = [
+ "bitflags",
+]
+
+[[package]]
+name = "rgb"
+version = "0.8.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3b221de559e4a29df3b957eec92bc0de6bc8eaf6ca9cfed43e5e1d67ff65a34"
+dependencies = [
+ "bytemuck",
+]
+
+[[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "scopeguard"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+
+[[package]]
+name = "semver"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f3aac57ee7f3272d8395c6e4f502f434f0e289fcd62876f70daa008c20dcabe"
+
+[[package]]
+name = "stderrlog"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "af95cb8a5f79db5b2af2a46f44da7594b5adbcbb65cbf87b8da0959bfdd82460"
+dependencies = [
+ "atty",
+ "log",
+ "termcolor",
+ "thread_local",
+]
+
+[[package]]
+name = "strsim"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "structopt"
+version = "0.3.26"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
+dependencies = [
+ "clap 2.33.3",
+ "lazy_static",
+ "structopt-derive",
+]
+
+[[package]]
+name = "structopt-derive"
+version = "0.4.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
+dependencies = [
+ "heck",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.74"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1873d832550d4588c3dbc20f01361ab00bfe741048f71e3fecf145a7cc18b29c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "terminal_size"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
+dependencies = [
+ "libc",
+ "winapi",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
+dependencies = [
+ "unicode-width",
+]
+
+[[package]]
+name = "textwrap"
+version = "0.15.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "949517c0cf1bf4ee812e2e07e08ab448e3ae0d23472aee8a06c985f0c8815b16"
+
+[[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
+name = "typed-arena"
+version = "2.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0685c84d5d54d1c26f7d3eb96cd41550adb97baed141a761cf335d3d33bcd0ae"
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
+
+[[package]]
+name = "uuid"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd6469f4314d5f1ffec476e05f17cc9a78bc7a27a6a857842170bdf8d6f98d2f"
+
+[[package]]
+name = "vec_map"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
+
+[[package]]
+name = "version_check"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
+
+[[package]]
+name = "walkdir"
+version = "2.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
+dependencies = [
+ "same-file",
+ "winapi",
+ "winapi-util",
+]
+
+[[package]]
+name = "wasi"
+version = "0.11.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
+
+[[package]]
+name = "webp"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf022f821f166079a407d000ab57e84de020e66ffbbf4edde999bc7d6e371cae"
+dependencies = [
+ "image",
+ "libwebp-sys",
+]
+
+[[package]]
+name = "weezl"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8b77fdfd5a253be4ab714e4ffa3c49caf146b4de743e97510c0656cf90f1e8e"
+
+[[package]]
+name = "wild"
+version = "2.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05b116685a6be0c52f5a103334cbff26db643826c7b3735fc0a3ba9871310a74"
+dependencies = [
+ "glob",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+
+[[package]]
+name = "windows-sys"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2"
+dependencies = [
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.36.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680"
+
+[[package]]
+name = "zopfli"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1e0d16c30236860686a8f03d36b384dc2fc0675a8916367d2f9a1ecd795eab6"
+dependencies = [
+ "adler32",
+ "byteorder",
+ "crc",
+ "iter-read",
+ "log",
+ "typed-arena",
+]
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..116ed62
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "caesiumclt"
+version = "0.19.0"
+authors = ["Matteo Paonessa "]
+edition = "2021"
+
+
+[dependencies]
+structopt = "0.3"
+indicatif = "0.17"
+walkdir = "2.3"
+num_cpus = "1.13"
+infer = "0.11"
+rayon = "1.5"
+rand = "0.8"
+human_bytes = { version = "0.4", default-features = false }
+libcaesium = { git = "https://github.com/Lymphatus/libcaesium", rev = "eca05e2" }
\ No newline at end of file
diff --git a/INSTALL.md b/INSTALL.md
deleted file mode 100644
index c5f2042..0000000
--- a/INSTALL.md
+++ /dev/null
@@ -1,22 +0,0 @@
-# Install
-
-## Requirements
-CaesiumCLT is based on [libcaesium](https://github.com/Lymphatus/libcaesium) and requires it to be compiled and installed.
-Please refer to its own documentation.
-You will also need cmake if you want to compile it from source.
-
-## Instructions
-
-Download the latest release package from [here](https://github.com/Lymphatus/caesium-clt/releases) or clone from git.
-Then run:
-
- $ cd caesium-clt
- $ mkdir build && cd build
- $ cmake .. -DLIBCAESIUM_PATH=/your/libcaesium/path/dir
- $ make
- $ sudo make install
-
-This will compile and install caesiumclt in your `PATH`.
-You can verify everything went ok by running `caesiumclt -v`.
-
-
\ No newline at end of file
diff --git a/README.md b/README.md
index 424d12a..a52ed1e 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,8 @@
## Caesium CommandLineTools
-###### caesium-clt - v0.18.0-beta (build 20221106)
+###### caesium-clt - v0.19.0 (build 20221114)
###### REQUIREMENTS
-* [libcaesium](https://github.com/Lymphatus/libcaesium) >= 0.9.3
-
-###### Included libraries
-* [optparse](https://github.com/skeeto/optparse)
-* [tinydir](https://github.com/cxong/tinydir)
-
+* [Rust](https://www.rust-lang.org/tools/install)
----------
###### TESTED PLATFORMS
@@ -18,7 +13,7 @@
----------
###### COMPILATION
-See INSTALL.md for more details.
+`cargo build --release`
----------
@@ -37,7 +32,7 @@ See INSTALL.md for more details.
- `-S, --keep-structure`
If the input is a folder, and the `-R` option is set, caesiumclt will compress all the files keeping the original folder structure.
- `-O, --overwrite`
- Sets the overwrite policy: `all` will overwrite any existing file, `prompt` will ask each time before overwriting, `bigger` will overwrite bigger files only, and `none` will silently skip existing files.
+ Sets overwrite policy: `all` will overwrite any existing file, `prompt` will ask each time before overwriting, `bigger` will overwrite bigger files only, and `none` will silently skip existing files.
- `-d, --dry-run`
If this option is set, no files will be compressed, but the entire process will just be simulated.
Useful for checking if all the files will be correctly handled.
@@ -81,19 +76,20 @@ $ caesiumclt -q 0 -RS -o ~/output/ ~/Pictures
----------
###### CHANGELOG
-* 0.18.0-beta - Fixed Windows build + libcaesium 0.9.3
-* 0.17.0-beta - libcaesium 0.9.2
-* 0.16.0-beta - Using libcaesium Rust library
-* 0.15.2-beta - Fixed Windows -RS bug
-* 0.15.1-beta - Fixed rename bug on Windows + "Compressing..." message
-* 0.15.0-beta - Support for libcaesium 0.5.0
-* 0.14.0-beta - Added --quiet option
-* 0.13.1-beta - Bugfix
-* 0.13.0-beta - Bugfix
-* 0.12.1-beta - Bugfix
-* 0.12.0-beta - Resizing (experimental)
-* 0.11.0-beta - Fixing paths issues and dry-run option
-* 0.10.2-beta - Bugfixes & full Windows support
-* 0.10.1-beta - All features are available
-* 0.10.0-beta - Switched to cmake build system and libcaesium
-* 0.9.1-beta - Initial development stage
+* 0.19.0 - Rust migration
+* 0.18.0 - Fixed Windows build + libcaesium 0.9.3
+* 0.17.0 - libcaesium 0.9.2
+* 0.16.0 - Using libcaesium Rust library
+* 0.15.2 - Fixed Windows -RS bug
+* 0.15.1 - Fixed rename bug on Windows + "Compressing..." message
+* 0.15.0 - Support for libcaesium 0.5.0
+* 0.14.0 - Added --quiet option
+* 0.13.1 - Bugfix
+* 0.13.0 - Bugfix
+* 0.12.1 - Bugfix
+* 0.12.0 - Resizing (experimental)
+* 0.11.0 - Fixing paths issues and dry-run option
+* 0.10.2 - Bugfixes & full Windows support
+* 0.10.1 - All features are available
+* 0.10.0 - Switched to cmake build system and libcaesium
+* 0.9.1 - Initial development stage
diff --git a/README.zh-CN.md b/README.zh-CN.md
index 1fc0eeb..be46c52 100644
--- a/README.zh-CN.md
+++ b/README.zh-CN.md
@@ -1,13 +1,8 @@
## Caesium 命令行工具
-###### caesium-clt - v0.18.0-beta (build 20221106)
+###### caesium-clt - v0.19.0 (build 20221114)
###### 依赖
-* [libcaesium](https://github.com/Lymphatus/libcaesium) >= 0.9.3
-
-###### 已包含的库
-* [optparse](https://github.com/skeeto/optparse)
-* [tinydir](https://github.com/cxong/tinydir)
-
+* [Rust](https://www.rust-lang.org/tools/install)
----------
###### 已通过测试的平台
@@ -18,7 +13,7 @@
----------
###### 编译
-详情见 INSTALL.md 。
+`cargo build --release`
----------
@@ -80,19 +75,20 @@ $ caesiumclt -q 0 -RS -o ~/output/ ~/Pictures
----------
###### 变更日志
-* 0.18.0-beta - Fixed Windows build + libcaesium 0.9.3
-* 0.17.0-beta - libcaesium 0.9.2
-* 0.16.0-beta - Using libcaesium Rust library
-* 0.15.2-beta - Fixed Windows -RS bug
-* 0.15.1-beta - Fixed rename bug on Windows + "Compressing..." message
-* 0.15.0-beta - Support for libcaesium 0.5.0
-* 0.14.0-beta - Added --quiet option
-* 0.13.1-beta - Bugfix
-* 0.13.0-beta - Bugfix
-* 0.12.1-beta - Bugfix
-* 0.12.0-beta - Resizing (experimental)
-* 0.11.0-beta - Fixing paths issues and dry-run option
-* 0.10.2-beta - Bugfixes & full Windows support
-* 0.10.1-beta - All features are available
-* 0.10.0-beta - Switched to cmake build system and libcaesium
-* 0.9.1-beta - Initial development stage
+* 0.19.0 - Rust migration
+* 0.18.0 - Fixed Windows build + libcaesium 0.9.3
+* 0.17.0 - libcaesium 0.9.2
+* 0.16.0 - Using libcaesium Rust library
+* 0.15.2 - Fixed Windows -RS bug
+* 0.15.1 - Fixed rename bug on Windows + "Compressing..." message
+* 0.15.0 - Support for libcaesium 0.5.0
+* 0.14.0 - Added --quiet option
+* 0.13.1 - Bugfix
+* 0.13.0 - Bugfix
+* 0.12.1 - Bugfix
+* 0.12.0 - Resizing (experimental)
+* 0.11.0 - Fixing paths issues and dry-run option
+* 0.10.2 - Bugfixes & full Windows support
+* 0.10.1 - All features are available
+* 0.10.0 - Switched to cmake build system and libcaesium
+* 0.9.1 - Initial development stage
diff --git a/install.sh b/install.sh
deleted file mode 100644
index 8649a7a..0000000
--- a/install.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/sh
-
-#libcaesium
-git clone https://github.com/Lymphatus/libcaesium.git
-cd libcaesium || exit
-cargo build --release
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
deleted file mode 100644
index 038ea09..0000000
--- a/src/CMakeLists.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-set(CMAKE_C_FLAGS "--std=gnu99 -Wno-nullability-completeness")
-
-FILE(GLOB CSources *.c)
-FILE(GLOB CVendorSources vendor/*.c)
-FILE(GLOB CHeaders *.h)
-FILE(GLOB CVendorHeaders vendor/*.h)
-
-add_executable(caesiumclt ${CVendorSources} ${CVendorHeaders} ${CSources} ${CHeaders})
-
-target_link_libraries(caesiumclt LINK_PUBLIC caesium m)
-
-install(TARGETS caesiumclt DESTINATION bin)
\ No newline at end of file
diff --git a/src/config.h.in b/src/config.h.in
deleted file mode 100644
index bc68323..0000000
--- a/src/config.h.in
+++ /dev/null
@@ -1,4 +0,0 @@
-#define VERSION_MAJOR @VERSION_MAJOR@
-#define VERSION_MINOR @VERSION_MINOR@
-#define VERSION_PATCH @VERSION_PATCH@
-
diff --git a/src/error.c b/src/error.c
deleted file mode 100644
index 8508a23..0000000
--- a/src/error.c
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#include
-#include
-
-#include "error.h"
-#include "utils.h"
-#include "shared.h"
-
-void display_error(error_level level, int code) {
- char *error_level = ((level) ? "[WARNING]" : "[ERROR]");
-
- print_to_console(stderr, verbose, "%s %s (code: %d)\n",
- error_level,
- get_error_message(code),
- code);
-
- if (level == CS_ERROR) {
- exit(-code);
- }
-}
-
-const char *get_error_message(int code) {
- switch (code) {
- //Generic errors
- case 1:
- return "Invalid quality value. Must be between [0-100].";
- case 2:
- return "Unrecognized option.";
- case 3:
- return "Empty input folder.";
- case 4:
- return "Cannot keep folder structure providing multiple input files.";
- case 5:
- return "Cannot create output folder.";
- case 6:
- return "Cannot check if is a directory.";
- case 7:
- return "Cannot calculate file size";
- case 8:
- return "Input folder provided. Skipping all other inputs.";
- case 9:
- return "Input files provided. Cannot mix them with a folder.";
- case 10:
- return "-R has no effects on files.";
- case 11:
- return "-S has no effect without -R.";
- case 12:
- return "Cannot set output folder inside the input one.";
- case 13:
- return "Scale factor must be between (0, 1.0]. Setting it to 1.0.";
- case 14:
- return "Scale factor parsing error.";
- case 15:
- return "Overwrite policy value is invalid. Using 'bigger'.";
- case 16:
- return "Cannot get the full output path.";
- case 17:
- return "Cannot create the output folder.";
-
- default:
- return "Unrecognized error.";
- }
-}
\ No newline at end of file
diff --git a/src/error.h b/src/error.h
deleted file mode 100644
index a058f19..0000000
--- a/src/error.h
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#ifndef CAESIUMCLT_ERROR_H
-#define CAESIUMCLT_ERROR_H
-
-typedef enum error_level
-{
- CS_ERROR = 0,
- CS_WARNING = 1
-} error_level;
-
-void display_error(error_level level, int code);
-
-const char *get_error_message(int code);
-
-#endif //CAESIUMCLT_ERROR_H
diff --git a/src/helper.c b/src/helper.c
deleted file mode 100644
index 4542f40..0000000
--- a/src/helper.c
+++ /dev/null
@@ -1,351 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#include
-#include
-#include
-#include
-#include "helper.h"
-#include "vendor/optparse.h"
-#include "utils.h"
-#include "config.h"
-#include "error.h"
-#include "shared.h"
-
-cclt_options parse_arguments(char **argv, C_CSParameters *options) {
- struct optparse opts;
- //Initialize application options
- cclt_options parameters = {NULL, "", "", false, false, 0, 0, 0, false, all};
-
- //Parse command line args
- struct optparse_long longopts[] = {
- {"quality", 'q', OPTPARSE_REQUIRED},
- {"exif", 'e', OPTPARSE_NONE},
- {"output", 'o', OPTPARSE_REQUIRED},
- {"scale", 's', OPTPARSE_REQUIRED},
- {"recursive", 'R', OPTPARSE_NONE},
- {"keep-structure", 'S', OPTPARSE_NONE},
- {"overwrite", 'O', OPTPARSE_REQUIRED},
- {"dry-run", 'd', OPTPARSE_NONE},
- {"quiet", 'Q', OPTPARSE_NONE},
- {"version", 'v', OPTPARSE_NONE},
- {"help", 'h', OPTPARSE_NONE},
- {0}
- };
- optparse_init(&opts, argv);
- int option;
- int quality = 0;
- while ((option = optparse_long(&opts, longopts, NULL)) != -1) {
- switch (option) {
- case 'q':
- quality = (int) strtol(opts.optarg, (char **) NULL, 10);
- if (quality < 0 || quality > 100) {
- display_error(CS_ERROR, 1);
- }
- if (quality == 0) {
- options->optimize = true;
- } else {
- options->jpeg_quality = quality;
- options->png_quality = quality;
- options->webp_quality = quality;
- options->gif_quality = quality;
- }
- break;
- case 'e':
- options->keep_metadata = true;
- break;
- case 'o':
- if (opts.optarg[0] == '~') {
- snprintf(parameters.output_folder, strlen(opts.optarg) + 1, "%s", opts.optarg);
- } else {
-#ifdef _WIN32
- _fullpath(parameters.output_folder, opts.optarg, MAX_PATH_SIZE);
-#else
- char *computedPath = realpath(opts.optarg, parameters.output_folder);
- if (computedPath == NULL) {
- //Folder does not exists and may just fail on some systems, like Docker Alpine
- if (errno == 2) {
- if (mkpath(opts.optarg) == 0) {
- computedPath = realpath(opts.optarg, parameters.output_folder);
- if (computedPath == NULL) {
- //Just throw an error here
- display_error(CS_ERROR, 16);
- }
- } else {
- display_error(CS_ERROR, 17);
- }
- } else {
- display_error(CS_ERROR, 16);
- }
- }
-#endif
- }
- int pathlen = (int) strlen(parameters.output_folder);
- if (parameters.output_folder[pathlen - 1] != '/' &&
- parameters.output_folder[pathlen - 1] != '\\') {
- // append the extra slash/backslash
-#ifdef _WIN32
- snprintf(parameters.output_folder + pathlen, 2, "\\");
-#else
- snprintf(parameters.output_folder + pathlen, 2, "/");
-#endif
- }
- break;
- case 'R':
- parameters.recursive = true;
- break;
- case 'S':
- parameters.keep_structure = true;
- break;
- case 'O':
- parameters.overwrite = parse_overwrite_policy(opts.optarg);
- break;
- case 'd':
- parameters.dry_run = true;
- break;
- case 'v':
- print_to_console(stdout, 1, "%d.%d.%d\n", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
- exit(EXIT_SUCCESS);
- case 'Q':
- verbose = 0;
- break;
- case 'h':
- print_help();
- break;
- case '?':
- default:
- print_to_console(stderr, verbose, "%s: %s\n", argv[0], opts.errmsg);
- display_error(CS_ERROR, 2);
- }
- }
- //Remaining arguments
- char *arg;
- bool files_flag = false, folders_flag = false;
- char resolved_path[MAX_PATH_SIZE];
-
- print_to_console(stdout, verbose, "%s\n", "Collecting files...");
-
- while ((arg = optparse_arg(&opts))) {
- if (folders_flag) {
- display_error(CS_WARNING, 8);
- break;
- }
-
- //Check if it's a directory and add its content
- if (arg[0] == '~' && is_directory(arg)) {
- if (arg[strlen(arg) - 1] == '/' || arg[strlen(arg) - 1] == '\\') {
- snprintf(resolved_path, strlen(arg), "%s", arg);
- } else {
-#ifdef _WIN32
- snprintf(resolved_path, strlen(arg) + 1, "%s\\", arg);
-#else
- snprintf(resolved_path, strlen(arg) + 1, "%s/", arg);
-#endif
- }
- } else {
-#ifdef _WIN32
- _fullpath(resolved_path, arg, MAX_PATH_SIZE);
-#else
- realpath(arg, resolved_path);
-#endif
- }
-
- if (is_directory(resolved_path)) {
- if (!files_flag) {
- folders_flag = true;
- size_t len = strlen(resolved_path);
- if (resolved_path[len - 1] != '/' && resolved_path[strlen(resolved_path) - 1] != '\\') {
-#ifdef _WIN32
- resolved_path[len] = '\\';
-#else
- resolved_path[len] = '/';
-#endif
- resolved_path[len + 1] = '\0';
- }
-
- snprintf(parameters.input_folder, strlen(resolved_path) + 1, "%s", resolved_path);
- scan_folder(resolved_path, ¶meters, parameters.recursive);
- if (parameters.files_count == 0) {
- display_error(CS_WARNING, 3);
- }
- } else {
- display_error(CS_WARNING, 9);
- }
- } else {
- files_flag = true;
- parameters.input_files = realloc(parameters.input_files, (parameters.files_count + 1) * sizeof(char *));
- parameters.input_files[parameters.files_count] = malloc((strlen(arg) + 1) * sizeof(char));
- snprintf(parameters.input_files[parameters.files_count], strlen(arg) + 1, "%s", arg);
- parameters.files_count++;
- }
- }
-
- //Check if the output folder is a subfolder of the input to avoid infinite loops
- //but just if the -R option is set
- //However, if the folders are the same, we can let it go as it will overwrite the files
- if (folders_flag) {
- if (strstr(parameters.output_folder, parameters.input_folder) != NULL
- && strcmp(parameters.output_folder, parameters.input_folder) != 0
- && parameters.recursive) {
- display_error(CS_ERROR, 12);
- }
- }
-
- //-R and -S set warnings
- if (parameters.recursive && !folders_flag) {
- display_error(CS_WARNING, 10);
- parameters.recursive = false;
- }
- if (!parameters.recursive && parameters.keep_structure) {
- display_error(CS_WARNING, 11);
- parameters.keep_structure = false;
- }
- //If there are files and folders, we cannot keep the structure
- if (parameters.keep_structure && (!folders_flag && parameters.files_count > 1)) {
- display_error(CS_WARNING, 4);
- parameters.keep_structure = false;
- }
- return parameters;
-}
-
-int start_compression(cclt_options *options, struct C_CSParameters parameters) {
- int compressed_files = 0;
- off_t input_file_size;
- off_t output_file_size;
- //Create the output folder if it does not exist
- if (mkpath(options->output_folder) == -1) {
- display_error(CS_ERROR, 5);
- }
-
- for (int i = 0; i < options->files_count; i++) {
- char *filename = get_filename(options->input_files[i]);
- char *output_full_path = NULL;
- char *original_output_full_path = NULL;
- bool overwriting = false;
- off_t file_size;
- //If we don't need to keep the structure, we put all the files in one folder by just the filename
- if (!options->keep_structure) {
- output_full_path = malloc((strlen(filename) + strlen(options->output_folder) + 1) * sizeof(char));
- snprintf(output_full_path, (strlen(filename) + strlen(options->output_folder) + 1), "%s%s",
- options->output_folder, filename);
- } else {
- /*
- * Otherwise, we need to compute the whole directory structure
- * We are sure we have a folder only as input, so that's the root
- * Just compute the subfolders without the filename, make them and append the filename
- * A piece of cake <3
- */
-
- size_t index = strspn(options->input_folder, options->input_files[i]);
- size_t size = strlen(options->input_files[i]) - index - strlen(filename);
- char output_full_folder[strlen(options->output_folder) + size + 1];
-
- snprintf(output_full_folder, strlen(options->output_folder) + size + 1, "%s%s",
- options->output_folder, &options->input_files[i][index]);
- output_full_path = malloc((strlen(output_full_folder) + strlen(filename) + 1) * sizeof(char));
- snprintf(output_full_path, strlen(output_full_folder) + strlen(filename) + 1, "%s%s",
- output_full_folder,
- filename);
-
- mkpath(output_full_folder);
- }
-
- //Calculating the total input file size, ignoring if we are going to skip them later
- file_size = get_file_size(options->input_files[i]);
- if (file_size == 0) {
- //We could not open the file
- continue;
- }
- input_file_size = file_size;
- options->input_total_size += input_file_size;
-
- //If the file already exist, create a temporary file
- bool f_exists = file_exists(output_full_path);
- if (f_exists) {
- if (options->overwrite == none) {
- print_to_console(stdout, verbose, "[SKIPPED] %s\n", output_full_path);
- options->output_total_size += get_file_size(output_full_path);
- goto free_and_go_on_with_next_file;
- } else if (options->overwrite == prompt) {
- print_to_console(stdout, verbose, "Overwrite %s? [y/n]\n", output_full_path);
- int prompt = getchar();
- if (prompt == '\n') {
- prompt = getchar();
- }
- if (prompt != 'y' && prompt != 'Y') {
- print_to_console(stdout, verbose, "[SKIPPED] %s\n", output_full_path);
- options->output_total_size += get_file_size(output_full_path);
- goto free_and_go_on_with_next_file;
- }
- }
-
- original_output_full_path = strdup(output_full_path);
- output_full_path = realloc(output_full_path, (strlen(output_full_path) + 4) * sizeof(char));
- snprintf(output_full_path, (strlen(original_output_full_path) + 4), "%s.cs", original_output_full_path);
- overwriting = true;
- }
-
- print_to_console(stdout, verbose, "(%d/%d) %s -> %s\nCompressing...",
- i + 1,
- options->files_count,
- filename,
- f_exists ? original_output_full_path : output_full_path);
- fflush(stdout);
- //Prevent compression if running in dry mode
- if (!options->dry_run) {
- C_CSResult compression_result = c_compress(options->input_files[i], output_full_path, parameters);
- if (compression_result.success) {
- compressed_files++;
- output_file_size = get_file_size(output_full_path);
-
- char *human_input_size = get_human_size(input_file_size);
- char *human_output_size = get_human_size(output_file_size);
-
- if (options->overwrite == bigger && get_file_size(original_output_full_path) <= output_file_size) {
- print_to_console(stdout, verbose, "Resulting file is bigger. Skipping.\n");
- remove(output_full_path);
- options->output_total_size += get_file_size(original_output_full_path);
- goto free_and_go_on_with_next_file;
- }
- options->output_total_size += output_file_size;
- print_to_console(stdout, verbose, "\r%s -> %s [%.2f%%]\n",
- human_input_size,
- human_output_size,
- ((float) output_file_size - (float) input_file_size) * 100 / (float) input_file_size);
- } else {
- print_to_console(stderr, verbose, "\nCompression failed: %s\n", compression_result.error_message);
- options->input_total_size -= get_file_size(options->input_files[i]);
- }
- }
-
- //Rename if we were overwriting
- if (overwriting && !options->dry_run) {
-#ifdef _WIN32
- remove(original_output_full_path);
-#endif
- rename(output_full_path, original_output_full_path);
- }
-
- free_and_go_on_with_next_file:
- free(original_output_full_path);
- free(output_full_path);
- }
-
- return compressed_files;
-}
-
-
diff --git a/src/helper.h b/src/helper.h
deleted file mode 100644
index c931e3a..0000000
--- a/src/helper.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#ifndef CAESIUM_CLT_HELPER_H
-#define CAESIUM_CLT_HELPER_H
-
-#include
-
-#ifdef _WIN32
-#define MAX_PATH_SIZE _MAX_PATH
-#else
-#include
-#include
-
-#define MAX_PATH_SIZE PATH_MAX
-#endif
-
-typedef enum overwrite_policy {
- none,
- prompt,
- bigger,
- all
-} overwrite_policy;
-
-typedef struct C_CSParameters {
- bool keep_metadata;
- unsigned int jpeg_quality;
- unsigned int png_quality;
- bool png_force_zopfli;
- unsigned int gif_quality;
- unsigned int webp_quality;
- bool optimize;
- int width;
- int height;
-} C_CSParameters;
-
-typedef struct C_CSResult {
- bool success;
- const char *error_message;
-} C_CSResult;
-
-extern C_CSResult c_compress(const char *i, const char *o, struct C_CSParameters params);
-
-typedef struct cclt_options
-{
- char **input_files;
- char input_folder[MAX_PATH_SIZE];
- char output_folder[MAX_PATH_SIZE];
- bool recursive;
- bool keep_structure;
- int files_count;
- off_t input_total_size;
- off_t output_total_size;
- bool dry_run;
- overwrite_policy overwrite;
-} cclt_options;
-
-
-
-cclt_options parse_arguments(char *argv[], C_CSParameters *options);
-
-int start_compression(cclt_options *options, struct C_CSParameters parameters);
-
-#endif //CAESIUM_CLT_HELPER_H
diff --git a/src/logger.rs b/src/logger.rs
new file mode 100644
index 0000000..d55fd37
--- /dev/null
+++ b/src/logger.rs
@@ -0,0 +1,16 @@
+pub enum ErrorLevel {
+ Log,
+ Warning,
+ Error,
+}
+
+pub fn log(message: &str, code: i32, level: ErrorLevel, verbose: u8) {
+ if verbose == 0 {
+ return;
+ }
+ match level {
+ ErrorLevel::Error => panic!("[ERROR] {} (Code: {})", message, code),
+ ErrorLevel::Warning => eprintln!("[WARNING] {} (Code: {})", message, code),
+ _ => println!("{}", message)
+ };
+}
\ No newline at end of file
diff --git a/src/main.c b/src/main.c
deleted file mode 100755
index 97228d7..0000000
--- a/src/main.c
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#include
-#include
-#include
-#include "utils.h"
-#include "shared.h"
-#include
-
-int main(int argc, char *argv[])
-{
- //Exit if less than 2 arguments
- if (argc < 2) {
- print_help();
- exit(EXIT_FAILURE);
- }
-
- long compression_time = 0;
- cclt_options options;
-
- //Initialize the default parameters
- C_CSParameters compress_options = {
- false,
- 80,
- 80,
- false,
- 20,
- 60,
- false,
- 0,
- 0
- };
- //Set them according to command line parameters
- options = parse_arguments(argv, &compress_options);
-
- //Start a timer before calling the compression
- clock_t start = clock(), diff;
-
- start_compression(&options, compress_options);
-
- //Cleanup the memory allocated objects
- free(options.input_files);
-
- //Get the difference
- diff = clock() - start;
- compression_time = diff * 1000 / CLOCKS_PER_SEC;
-
-
- //Output the compression results
- print_to_console(stdout, verbose, "-------------------------------\n");
- compression_time / 1000 % 60 >= 1 ?
- print_to_console(stdout, verbose, "Compression completed in %lum%lus\n",
- compression_time / 1000 / 60, compression_time / 1000 % 60) :
- print_to_console(stdout, verbose, "Compression completed in %lum%lus%lums\n",
- compression_time / 1000 / 60, compression_time / 1000 % 60, compression_time % 1000);
- print_to_console(stdout, verbose, "%s -> %s [%.2f%% | %s]\n",
- get_human_size(options.input_total_size), get_human_size(options.output_total_size),
- ((float) options.output_total_size - options.input_total_size) * 100 / options.input_total_size,
- get_human_size((options.output_total_size - options.input_total_size)));
-
-
- exit(EXIT_SUCCESS);
-}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..ec7dc5e
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,264 @@
+use std::fs;
+use std::path::Path;
+use std::sync::{Arc, Mutex};
+
+use human_bytes::human_bytes;
+use indicatif::ProgressBar;
+use indicatif::ProgressDrawTarget;
+use indicatif::ProgressStyle;
+use rand::{Rng, thread_rng};
+use rand::distributions::Alphanumeric;
+use rayon::prelude::*;
+
+use crate::logger::ErrorLevel::{Error, Log, Warning};
+use crate::logger::log;
+use crate::options::OverwritePolicy;
+
+mod scanfiles;
+mod options;
+mod logger;
+
+struct CompressionResult {
+ pub path: String,
+ pub output_path: String,
+ pub original_size: u64,
+ pub compressed_size: u64,
+ pub error: String,
+ pub result: bool,
+}
+
+fn main() {
+ let opt = options::get_opts();
+ let mut verbose = opt.verbose;
+ let args = opt.files;
+ let dry_run = opt.dry_run;
+ let output_dir = opt.output;
+ if opt.quiet {
+ verbose = 0;
+ }
+ let cpus = if opt.threads > 0 {
+ std::cmp::min(num_cpus::get(), opt.threads as usize)
+ } else {
+ num_cpus::get()
+ };
+ rayon::ThreadPoolBuilder::new().num_threads(cpus).build_global().unwrap_or_default();
+
+ match fs::create_dir_all(output_dir.clone()) {
+ Ok(_) => {}
+ Err(_) => log("Cannot create output path. Check your permissions.", 201, Error, verbose)
+ }
+
+ let (base_path, files) = scanfiles::scanfiles(args, opt.recursive);
+
+ let mut compression_parameters = caesium::initialize_parameters();
+ if opt.quality == 0 {
+ compression_parameters.optimize = true;
+ } else {
+ compression_parameters.jpeg.quality = opt.quality;
+ compression_parameters.png.quality = opt.quality;
+ compression_parameters.gif.quality = opt.quality;
+ compression_parameters.webp.quality = opt.quality;
+ }
+ compression_parameters.keep_metadata = opt.exif;
+
+ if opt.width > 0 {
+ compression_parameters.width = opt.width;
+ }
+
+ if opt.height > 0 {
+ compression_parameters.height = opt.height;
+ }
+
+ let overwrite_policy = opt.overwrite;
+ let keep_structure = opt.keep_structure;
+
+ let progress_bar = setup_progress_bar(files.len() as u64, verbose);
+ progress_bar.set_message("Compressing...");
+
+ let results = Arc::new(Mutex::new(Vec::new()));
+ files.par_iter().for_each(|input_file| {
+ let input_size = match fs::metadata(input_file) {
+ Ok(s) => s.len(),
+ Err(e) => {
+ let error_message = format!("Cannot get file size for {}, Error: {}", input_file.display(), e);
+ log(error_message.as_str(), 202, Warning, verbose);
+ 0
+ }
+ };
+
+ let mut compression_result = CompressionResult {
+ path: input_file.display().to_string(),
+ output_path: "".to_string(),
+ original_size: input_size,
+ compressed_size: 0,
+ error: "Unknown".to_string(),
+ result: false,
+ };
+
+ let filename = if keep_structure {
+ input_file.strip_prefix(base_path.clone()).unwrap_or_else(|_| Path::new("")).as_os_str()
+ } else {
+ input_file.file_name().unwrap_or_default()
+ };
+
+ if filename.is_empty() {
+ compression_result.error = "Cannot retrieve filename for {}. Skipping.".to_string();
+ results.lock().unwrap().push(compression_result);
+ return;
+ }
+ let filename_str = match filename.to_str() {
+ None => {
+ compression_result.error = "Cannot convert filename for {}. Skipping.".to_string();
+ results.lock().unwrap().push(compression_result);
+ return;
+ }
+ Some(fs) => fs
+ };
+
+
+ let random_suffix: String = (&mut thread_rng()).sample_iter(Alphanumeric)
+ .take(8)
+ .map(char::from)
+ .collect();
+ let random_suffixed_name = format!("{}.{}", filename_str, random_suffix);
+ let final_output_full_path = output_dir.clone().join(filename);
+ let output_full_path = output_dir.clone().join(random_suffixed_name);
+ let output_full_dir = output_full_path.parent().unwrap_or_else(|| Path::new("/"));
+ compression_result.output_path = final_output_full_path.display().to_string();
+ if !output_full_dir.exists() {
+ match fs::create_dir_all(output_full_dir) {
+ Ok(_) => {}
+ Err(e) => {
+ compression_result.error = format!("Cannot create output directory. Error: {}.", e);
+ results.lock().unwrap().push(compression_result);
+ return;
+ }
+ };
+ }
+ if !matches!(overwrite_policy, OverwritePolicy::All) && final_output_full_path.exists() {
+ if let OverwritePolicy::None = overwrite_policy { return; }
+ }
+ let input_full_path = input_file.to_str().unwrap();
+ let output_full_path_str = match output_full_path.to_str() {
+ None => {
+ compression_result.error = "Cannot convert output_full_path. Skipping.".to_string();
+ return;
+ }
+ Some(ofp) => ofp
+ };
+ if !dry_run {
+ match caesium::compress(input_full_path.to_string(), output_full_path_str.to_string(), &compression_parameters) {
+ Ok(_) => {
+ compression_result.result = true;
+ let output_metadata = fs::metadata(output_full_path.clone());
+ let output_size = if let Ok(..) = output_metadata {
+ output_metadata.unwrap().len()
+ } else {
+ 0
+ };
+ let mut final_output_size = output_size;
+ if matches!(overwrite_policy, OverwritePolicy::Bigger) && final_output_full_path.exists() {
+ let existing_file_metadata = fs::metadata(final_output_full_path.clone());
+ let existing_file_size = if let Ok(..) = existing_file_metadata {
+ existing_file_metadata.unwrap().len()
+ } else {
+ 0
+ };
+ if output_size >= existing_file_size {
+ match fs::remove_file(output_full_path) {
+ Ok(_) => {}
+ Err(e) => {
+ compression_result.error = format!("Cannot remove existing file. Error: {}.", e);
+ compression_result.result = false;
+ }
+ };
+ final_output_size = existing_file_size;
+ } else {
+ match fs::rename(output_full_path, final_output_full_path) {
+ Ok(_) => {}
+ Err(e) => {
+ compression_result.error = format!("Cannot rename existing file. Error: {}.", e);
+ compression_result.result = false;
+ }
+ };
+ }
+ } else {
+ match fs::rename(output_full_path, final_output_full_path) {
+ Ok(_) => {}
+ Err(e) => {
+ compression_result.error = format!("Cannot rename existing file. Error: {}.", e);
+ compression_result.result = false;
+ }
+ };
+ }
+ compression_result.compressed_size = final_output_size;
+ results.lock().unwrap().push(compression_result);
+ }
+ Err(e) => {
+ compression_result.error = e.to_string();
+ results.lock().unwrap().push(compression_result);
+ }
+ }
+ } else {
+ results.lock().unwrap().push(compression_result)
+ }
+ progress_bar.inc(1);
+ });
+
+
+ progress_bar.finish_with_message("Compression completed!");
+
+ let mut total_original_size = 0.0;
+ let mut total_compressed_size = 0.0;
+ let mut total_errors: u32 = 0;
+ let mut total_compressed_files = 0;
+
+ results.lock().unwrap().iter().for_each(|result| {
+ if result.result {
+ total_compressed_size += result.compressed_size as f64;
+ if verbose > 1 {
+ let message = format!("{} -> {}\n{} -> {} [{:.2}%]",
+ result.path,
+ result.output_path,
+ human_bytes(result.original_size as f64),
+ human_bytes(result.compressed_size as f64),
+ (result.compressed_size as f64 - result.original_size as f64) * 100.0 / result.original_size as f64
+ );
+ log(message.as_str(), 0, Log, verbose);
+ }
+ total_compressed_files += 1;
+ } else {
+ total_compressed_size += result.original_size as f64;
+ total_errors += 1;
+
+ log(format!("File {} was not compressed. Reason: {}", result.path, result.error).as_str(), 210, Warning, verbose);
+ }
+ total_original_size += result.original_size as f64;
+ });
+
+ let recap_message = format!("\nCompressed {} files ({} errors)\n{} -> {} [{:.2}% | -{}]",
+ total_compressed_files,
+ total_errors,
+ human_bytes(total_original_size),
+ human_bytes(total_compressed_size),
+ (total_compressed_size - total_original_size) * 100.0 / total_original_size,
+ human_bytes(total_original_size - total_compressed_size) //TODO can be positive
+ );
+
+ log(recap_message.as_str(), 0, Log, verbose);
+}
+
+fn setup_progress_bar(len: u64, verbose: u8) -> ProgressBar {
+ let progress_bar = ProgressBar::new(len);
+ progress_bar.set_draw_target(ProgressDrawTarget::stdout());
+ progress_bar.set_style(ProgressStyle::default_bar()
+ .template("[{elapsed_precise}] [{wide_bar:.cyan/blue}] {pos}/{len}\n{msg}")
+ .unwrap()
+ .progress_chars("#>-"));
+
+ if verbose == 0 {
+ progress_bar.set_draw_target(ProgressDrawTarget::hidden());
+ }
+
+ progress_bar
+}
diff --git a/src/options.rs b/src/options.rs
new file mode 100644
index 0000000..eec82c8
--- /dev/null
+++ b/src/options.rs
@@ -0,0 +1,93 @@
+use std::path::PathBuf;
+
+use structopt::clap::arg_enum;
+use structopt::StructOpt;
+
+use crate::logger::ErrorLevel::Error;
+use crate::logger::log;
+
+arg_enum! {
+ #[derive(Debug, Clone, Copy)]
+ pub enum OverwritePolicy {
+ All,
+ None,
+ Bigger
+ }
+}
+
+
+#[derive(StructOpt, Debug)]
+#[structopt(name = "", about = "CaesiumCLT - Command Line Tools for image compression")]
+pub struct Opt {
+ /// sets output file quality between [0-100], 0 for optimization
+ #[structopt(short = "q", long)]
+ pub quality: u32,
+
+ /// keeps EXIF info during compression
+ #[structopt(short = "e", long)]
+ pub exif: bool,
+
+ /// width of the output image, if height is not set will preserve aspect ratio
+ #[structopt(long, default_value = "0")]
+ pub width: u32,
+
+ /// height of the output image, if width is not set will preserve aspect ratio
+ #[structopt(long, default_value = "0")]
+ pub height: u32,
+
+ /// output folder
+ #[structopt(short = "o", long, parse(from_os_str))]
+ pub output: PathBuf,
+
+ /// if input is a folder, scan subfolders too
+ #[structopt(short = "R", long)]
+ pub recursive: bool,
+
+ /// keep the folder structure, can be used only with -R
+ #[structopt(short = "S", long)]
+ pub keep_structure: bool,
+
+ /// overwrite policy
+ #[structopt(short = "O", long, default_value = "all")]
+ pub overwrite: OverwritePolicy,
+
+ /// do not compress files but just show output paths
+ #[structopt(short = "d", long)]
+ pub dry_run: bool,
+
+ /// suppress all output
+ #[structopt(short = "Q", long)]
+ pub quiet: bool,
+
+ /// specify the number of parallel jobs (max is the number of processors available)
+ #[structopt(long, default_value = "0")]
+ pub threads: u32,
+
+ /// select how much output you want to see, 0 is equal to -Q, --quiet
+ #[structopt(long, default_value = "1")]
+ pub verbose: u8,
+
+ /// Files to process
+ #[structopt(name = "FILE", parse(from_os_str))]
+ pub files: Vec,
+}
+
+pub fn get_opts() -> Opt {
+ let opt = Opt::from_args();
+ validate_opts(&opt);
+
+ opt
+}
+
+fn validate_opts(opt: &Opt) {
+ let args = &opt.files;
+ let verbose = opt.verbose;
+
+ if args.is_empty() {
+ log("Please provide at least one file or folder.", 101, Error, verbose);
+ }
+
+ if !opt.output.is_dir() {
+ log("Please provide a folder as output path.", 102, Error, verbose);
+ }
+}
\ No newline at end of file
diff --git a/src/scanfiles.rs b/src/scanfiles.rs
new file mode 100644
index 0000000..4b2681f
--- /dev/null
+++ b/src/scanfiles.rs
@@ -0,0 +1,110 @@
+use std::path::PathBuf;
+use std::time::Duration;
+
+use indicatif::ProgressBar;
+use indicatif::ProgressStyle;
+use walkdir::WalkDir;
+
+pub fn is_filetype_supported(path: &PathBuf) -> bool {
+ let file_path = match path.to_str() {
+ None => return false,
+ Some(p) => p
+ };
+ match infer::get_from_path(file_path) {
+ Ok(v) => match v {
+ None => false,
+ Some(ft) => matches!(ft.mime_type(), "image/jpeg" | "image/png" | "image/gif" | "image/webp"),
+ },
+ Err(_) => false
+ }
+}
+
+
+fn is_valid(entry: &PathBuf) -> bool {
+ entry.exists() && entry.is_file() && is_filetype_supported(entry)
+}
+
+pub fn scanfiles(args: Vec, recursive: bool) -> (PathBuf, Vec) {
+ let mut files: Vec = vec![];
+ let mut base_path = PathBuf::new();
+
+ let progress_bar = init_progress_bar();
+
+ for input in args.into_iter() {
+ if input.exists() && input.is_dir() {
+ let mut walk_dir = WalkDir::new(input);
+ if !recursive {
+ walk_dir = walk_dir.max_depth(1);
+ }
+ for entry in walk_dir.into_iter().filter_map(|e| e.ok()) {
+ let path = entry.into_path();
+ if is_valid(&path) {
+ if let Ok(ap) = path.canonicalize() {
+ base_path = compute_base_folder(&base_path, &ap);
+ files.push(ap);
+ }
+ }
+ }
+ } else if is_valid(&input) {
+ if let Ok(ap) = input.canonicalize() {
+ base_path = compute_base_folder(&base_path, &ap);
+ files.push(ap);
+ }
+ }
+
+ progress_bar.tick();
+ }
+
+ progress_bar.finish_and_clear();
+
+ (base_path, files)
+}
+
+fn compute_base_folder(base_folder: &PathBuf, new_path: &PathBuf) -> PathBuf {
+ if base_folder.parent().is_none() {
+ return if new_path.is_dir() {
+ new_path.clone()
+ } else {
+ new_path.parent().unwrap_or(&*PathBuf::from("/")).to_path_buf()
+ };
+ }
+ let mut folder = PathBuf::new();
+ let mut new_path_folder = new_path.clone();
+ if new_path.is_file() {
+ new_path_folder = new_path.parent().unwrap_or(&*PathBuf::from("/")).to_path_buf();
+ }
+ for (i, component) in base_folder.iter().enumerate() {
+ if let Some(new_path_component) = new_path_folder.iter().nth(i) {
+ if new_path_component == component {
+ folder.push(component);
+ } else {
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ if folder.parent().is_none() {
+ return PathBuf::from("/");
+ }
+
+ folder
+}
+
+
+fn init_progress_bar() -> ProgressBar {
+ let progress_bar = ProgressBar::new_spinner();
+ let style = match ProgressStyle::default_spinner()
+ .tick_strings(&["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"])
+ .template("{spinner:.cyan} {msg}") {
+ Ok(s) => s,
+ Err(_) => ProgressStyle::default_spinner()
+ };
+
+ progress_bar.set_message("Collecting files...");
+ progress_bar.enable_steady_tick(Duration::from_millis(80));
+ progress_bar.set_style(style);
+
+ progress_bar
+}
\ No newline at end of file
diff --git a/src/shared.c b/src/shared.c
deleted file mode 100644
index c950335..0000000
--- a/src/shared.c
+++ /dev/null
@@ -1,19 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-
-int verbose = 1;
diff --git a/src/shared.h b/src/shared.h
deleted file mode 100644
index 145e22a..0000000
--- a/src/shared.h
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#ifndef CAESIUMCLT_SHARED_H
-#define CAESIUMCLT_SHARED_H
-
-extern int verbose;
-
-#endif //CAESIUMCLT_SHARED_H
diff --git a/src/utils.c b/src/utils.c
deleted file mode 100644
index 83d478b..0000000
--- a/src/utils.c
+++ /dev/null
@@ -1,354 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#ifdef _WIN32
-#include
-#include
-#endif
-
-#include
-#include
-
-#include "utils.h"
-#include "vendor/tinydir.h"
-#include "error.h"
-
-void print_help() {
- print_to_console(stdout, 1,
- "CaesiumCLT - Caesium Command Line Tools\n\n"
- "Usage: caesiumclt [OPTIONS] INPUT...\n"
- "Command line image compressor.\n\n"
-
- "Options:\n"
- "\t-q, --quality\t\tset output file quality between [0-100], 0 for optimization\n"
- "\t-e, --exif\t\tkeeps EXIF info during compression\n"
- "\t-o, --output\t\toutput folder\n"
- "\t-R, --recursive\t\tif input is a folder, scan subfolders too\n"
- "\t-S, --keep-structure\tkeep the folder structure, use with -R\n"
- "\t-O, --overwrite\t\tOverwrite policy: all, none, prompt, bigger. Default is bigger.\n"
- "\t-d, --dry-run\t\tdo not really compress files but just show output paths\n"
- "\t-Q, --quiet\t\tsuppress all output\n"
- "\t-h, --help\t\tdisplay this help and exit\n"
- "\t-v, --version\t\toutput version information and exit\n\n");
- exit(EXIT_SUCCESS);
-}
-
-bool is_directory(const char *path) {
-#ifdef _WIN32
- tinydir_dir dir;
-
- return tinydir_open(&dir, path) != -1;
-
-#else
- tinydir_file file;
-
- if (tinydir_file_open(&file, path) == -1) {
- display_error(CS_ERROR, 6);
- }
-
- return (bool) file.is_dir;
-#endif
-}
-
-void scan_folder(const char *directory, cclt_options *options, bool recursive) {
- tinydir_dir dir;
- tinydir_open(&dir, directory);
-
- while (dir.has_next) {
- tinydir_file file;
- tinydir_readfile(&dir, &file);
-
- if (file.is_dir) {
- if (strcmp(file.name, ".") != 0 && strcmp(file.name, "..") != 0 && recursive) {
- scan_folder(file.path, options, true);
- }
- } else {
- options->input_files = realloc(options->input_files, (options->files_count + 1) * sizeof(char *));
- options->input_files[options->files_count] = malloc((strlen(file.path) + 1) * sizeof(char));
- snprintf(options->input_files[options->files_count],
- strlen(file.path) + 1, "%s", file.path);
-#ifdef _WIN32
- options->input_files[options->files_count] = str_replace(options->input_files[options->files_count], "/", "\\");
-#endif
- options->files_count++;
- }
- tinydir_next(&dir);
- }
-
- tinydir_close(&dir);
-}
-
-#ifdef _WIN32
-int mkpath(const char *pathname)
-{
- const char *p;
- char *temp;
- bool ret = 0;
- const char SEP = '\\';
-
- temp = calloc(1, strlen(pathname) + 1);
- /* Skip Windows drive letter. */
-
- p = strchr(pathname, ':');
- if (p != NULL) {
- p++;
- }
- else {
-
- p = pathname;
- }
-
- while ((p = strchr(p, SEP)) != NULL)
- {
- /* Skip empty elements. Could be a Windows UNC path or
- just multiple separators which is okay. */
- if (p != pathname && *(p - 1) == SEP)
- {
- p++;
- continue;
- }
- /* Put the path up to this point into a temporary to
- pass to the make directory function. */
- memcpy(temp, pathname, p - pathname);
- temp[p - pathname] = '\0';
- p++;
- if (CreateDirectory(temp, NULL) == FALSE)
- {
- if (GetLastError() != ERROR_ALREADY_EXISTS)
- {
- ret = -1;
- break;
- }
- }
- }
- free(temp);
- return ret;
-}
-#endif
-
-#ifndef _WIN32
-
-int mkpath(const char *pathname) {
- char parent[PATH_MAX], *p;
- /* make a parent directory path */
- strncpy(parent, pathname, sizeof(parent));
- parent[sizeof(parent) - 1] = '\0';
- for (p = parent + strlen(parent); *p != '/' && p != parent; p--);
- *p = '\0';
- /* try to make parent directory */
- if (p != parent && mkpath(parent) != 0) {
- return -1;
- }
- /* make this one if parent has been made */
- if (mkdir(pathname, 0755) == 0) {
- return 0;
- }
- /* if it already exists that is fine */
- if (errno == EEXIST) {
- return 0;
- }
- return -1;
-}
-
-
-#endif
-
-char *get_filename(char *full_path) {
- char *token, *tofree;
-
- //Get just the filename
- tofree = strdup(full_path);
-#ifdef _WIN32
- while ((token = strsep(&tofree, "\\")) != NULL)
- {
-#else
- while ((token = strsep(&tofree, "/")) != NULL) {
-#endif
- if (tofree == NULL) {
- break;
- }
- }
-
- free(tofree);
-
- return token;
-}
-
-off_t get_file_size(const char *path) {
- FILE *f = fopen(path, "rb");
- if (f == NULL) {
- perror("Cannot open output file");
- display_error(CS_WARNING, 7);
- return 0;
- }
- fseek(f, 0, SEEK_END);
- off_t len = ftell(f);
- fclose(f);
-
- return len;
-}
-
-char *get_human_size(off_t size) {
- if (size == 0) {
- return "0.00 B";
- }
-
- //We should not get more than TB images
- char *unit[5] = {"B", "KB", "MB", "GB", "TB"};
- //Index of the array containing the correct unit
- double order = floor(log2(labs(size)) / 10);
- //Alloc enough size for the final string
- char *final = (char *) malloc(((int) (floor(log10(labs(size))) + 5)) * sizeof(char));
-
- //If the order exceeds 4, something is fishy
- if (order > 4) {
- order = 4;
- }
-
- //Copy the formatted string into the buffer
- sprintf(final, "%.2f %s", size / (pow(1024, order)), unit[(int) order]);
- //And return it
- return final;
-}
-
-bool file_exists(const char *file_path) {
- struct stat buffer;
- return (stat(file_path, &buffer) == 0);
-}
-
-overwrite_policy parse_overwrite_policy(const char *overwrite_string) {
- if (strcmp(overwrite_string, "none") == 0) {
- return none;
- } else if (strcmp(overwrite_string, "prompt") == 0) {
- return prompt;
- } else if (strcmp(overwrite_string, "bigger") == 0) {
- return bigger;
- } else if (strcmp(overwrite_string, "all") == 0) {
- return all;
- }
- display_error(CS_WARNING, 15);
- return bigger;
-}
-
-void print_to_console(FILE *buffer, int verbose, const char *format, ...) {
- if (!verbose) {
- return;
- }
-
- va_list args;
-
- va_start(args, format);
- vfprintf(buffer, format, args);
- va_end(args);
-}
-
-#ifdef _WIN32
-char *str_replace(char *orig, char *rep, char *with)
-{
- char *result; // the return string
- char *ins; // the next insert point
- char *tmp; // varies
- int len_rep; // length of rep (the string to remove)
- int len_with; // length of with (the string to replace rep with)
- int len_front; // distance between rep and end of last rep
- int count; // number of replacements
-
- if (!orig || !rep)
- return NULL;
- len_rep = strlen(rep);
- if (len_rep == 0)
- return NULL;
- if (!with)
- with = "";
- len_with = strlen(with);
-
- ins = orig;
- for (count = 0; tmp = strstr(ins, rep); ++count)
- {
- ins = tmp + len_rep;
- }
-
- tmp = result = malloc(strlen(orig) + (len_with - len_rep) * count + 1);
-
- if (!result)
- return NULL;
-
- while (count--)
- {
- ins = strstr(orig, rep);
- len_front = ins - orig;
- tmp = strncpy(tmp, orig, len_front) + len_front;
- tmp = strcpy(tmp, with) + len_with;
- orig += len_front + len_rep;
- }
- strcpy(tmp, orig);
- return result;
-}
-
-char *strsep(char **stringp, const char *delim)
-{
- char *begin, *end;
-
- begin = *stringp;
- if (begin == NULL)
- return NULL;
-
- /* A frequent case is when the delimiter string contains only one
- character. Here we don't need to call the expensive `strpbrk'
- function and instead work using `strchr'. */
- if (delim[0] == '\0' || delim[1] == '\0')
- {
- char ch = delim[0];
-
- if (ch == '\0')
- end = NULL;
- else
- {
- if (*begin == ch)
- end = begin;
- else if (*begin == '\0')
- end = NULL;
- else
- end = strchr(begin + 1, ch);
- }
- }
- else
- /* Find the end of the token. */
- end = strpbrk(begin, delim);
-
- if (end)
- {
- /* Terminate the token and set *STRINGP past NUL character. */
- *end++ = '\0';
- *stringp = end;
- }
- else
- /* No more delimiters; this is the last token. */
- *stringp = NULL;
-
- return begin;
-}
-
-#endif
\ No newline at end of file
diff --git a/src/utils.h b/src/utils.h
deleted file mode 100644
index 2afafe9..0000000
--- a/src/utils.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- *
- * Copyright 2019 Matteo Paonessa
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
-
-#ifndef CAESIUM_CLT_UTILS_H
-#define CAESIUM_CLT_UTILS_H
-
-#include "helper.h"
-
-void print_help();
-
-bool is_directory(const char *path);
-
-void scan_folder(const char *directory, cclt_options *options, bool recursive);
-
-char *get_filename(char * full_path);
-
-off_t get_file_size(const char *path);
-
-char* get_human_size(off_t size);
-
-int mkpath(const char *pathname);
-
-bool file_exists(const char* file_path);
-
-overwrite_policy parse_overwrite_policy(const char* overwrite_string);
-
-void print_to_console(FILE* buffer, int verbose, const char* format, ...);
-
-#ifdef _WIN32
-char *str_replace(char *orig, char *rep, char *with);
-char *strsep(char **stringp, const char *delim);
-#endif
-
-
-#endif //CAESIUM_CLT_UTILS_H
diff --git a/src/vendor/optparse.c b/src/vendor/optparse.c
deleted file mode 100644
index 78ecb8e..0000000
--- a/src/vendor/optparse.c
+++ /dev/null
@@ -1,264 +0,0 @@
-#include "optparse.h"
-
-#define MSG_INVALID "invalid option"
-#define MSG_MISSING "option requires an argument"
-#define MSG_TOOMANY "option takes no arguments"
-
-static int
-opterror(struct optparse *options, const char *message, const char *data)
-{
- unsigned p = 0;
- while (*message)
- options->errmsg[p++] = *message++;
- const char *sep = " -- '";
- while (*sep)
- options->errmsg[p++] = *sep++;
- while (p < sizeof(options->errmsg) - 2 && *data)
- options->errmsg[p++] = *data++;
- options->errmsg[p++] = '\'';
- options->errmsg[p++] = '\0';
- return '?';
-}
-
-void optparse_init(struct optparse *options, char **argv)
-{
- options->argv = argv;
- options->permute = 1;
- options->optind = 1;
- options->subopt = 0;
- options->optarg = 0;
- options->errmsg[0] = '\0';
-}
-
-static inline int
-is_dashdash(const char *arg)
-{
- return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0';
-}
-
-static inline int
-is_shortopt(const char *arg)
-{
- return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0';
-}
-
-static inline int
-is_longopt(const char *arg)
-{
- return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0';
-}
-
-static void
-permute(struct optparse *options, int index)
-{
- char *nonoption = options->argv[index];
- for (int i = index; i < options->optind - 1; i++)
- options->argv[i] = options->argv[i + 1];
- options->argv[options->optind - 1] = nonoption;
-}
-
-static int
-argtype(const char *optstring, char c)
-{
- if (c == ':')
- return -1;
- for (; *optstring && c != *optstring; optstring++);
- if (!*optstring)
- return -1;
- int count = OPTPARSE_NONE;
- if (optstring[1] == ':')
- count += optstring[2] == ':' ? 2 : 1;
- return count;
-}
-
-int optparse(struct optparse *options, const char *optstring)
-{
- options->errmsg[0] = '\0';
- options->optopt = 0;
- options->optarg = 0;
- char *option = options->argv[options->optind];
- if (option == 0) {
- return -1;
- } else if (is_dashdash(option)) {
- options->optind++; /* consume "--" */
- return -1;
- } else if (!is_shortopt(option)) {
- if (options->permute) {
- int index = options->optind;
- options->optind++;
- int r = optparse(options, optstring);
- permute(options, index);
- options->optind--;
- return r;
- } else {
- return -1;
- }
- }
- option += options->subopt + 1;
- options->optopt = option[0];
- int type = argtype(optstring, option[0]);
- char *next = options->argv[options->optind + 1];
- switch (type) {
- case OPTPARSE_NONE:
- if (option[1]) {
- options->subopt++;
- } else {
- options->subopt = 0;
- options->optind++;
- }
- return option[0];
- case OPTPARSE_REQUIRED:
- options->subopt = 0;
- options->optind++;
- if (option[1]) {
- options->optarg = option + 1;
- } else if (next != 0) {
- options->optarg = next;
- options->optind++;
- } else {
- options->optarg = 0;
- char str[2] = {option[0]};
- return opterror(options, MSG_MISSING, str);
- }
- return option[0];
- case OPTPARSE_OPTIONAL:
- options->subopt = 0;
- options->optind++;
- if (option[1])
- options->optarg = option + 1;
- else
- options->optarg = 0;
- return option[0];
- default:
- case -1: {
- options->optind++;
- char str[2] = {option[0]};
- return opterror(options, MSG_INVALID, str);
- }
- }
-}
-
-char *optparse_arg(struct optparse *options)
-{
- options->subopt = 0;
- char *option = options->argv[options->optind];
- if (option != 0)
- options->optind++;
- return option;
-}
-
-static inline int
-longopts_end(const struct optparse_long *longopts, int i)
-{
- return !longopts[i].longname && !longopts[i].shortname;
-}
-
-static void
-optstring_from_long(const struct optparse_long *longopts, char *optstring)
-{
- char *p = optstring;
- for (int i = 0; !longopts_end(longopts, i); i++) {
- if (longopts[i].shortname) {
- *p++ = longopts[i].shortname;
- for (int a = 0; a < (int)longopts[i].argtype; a++)
- *p++ = ':';
- }
- }
- *p = '\0';
-}
-
-/* Unlike strcmp(), handles options containing "=". */
-static int
-longopts_match(const char *longname, const char *option)
-{
- if (longname == 0)
- return 0;
- const char *a = option, *n = longname;
- for (; *a && *n && *a != '='; a++, n++)
- if (*a != *n)
- return 0;
- return *n == '\0' && (*a == '\0' || *a == '=');
-}
-
-/* Return the part after "=", or NULL. */
-static char *
-longopts_arg(char *option)
-{
- for (; *option && *option != '='; option++);
- if (*option == '=')
- return option + 1;
- else
- return 0;
-}
-
-static int
-long_fallback(struct optparse *options,
- const struct optparse_long *longopts,
- int *longindex)
-{
- char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */
- optstring_from_long(longopts, optstring);
- int result = optparse(options, optstring);
- if (longindex != 0) {
- *longindex = -1;
- if (result != -1)
- for (int i = 0; !longopts_end(longopts, i); i++)
- if (longopts[i].shortname == options->optopt)
- *longindex = i;
- }
- return result;
-}
-
-int
-optparse_long(struct optparse *options,
- const struct optparse_long *longopts,
- int *longindex)
-{
- char *option = options->argv[options->optind];
- if (option == 0) {
- return -1;
- } else if (is_dashdash(option)) {
- options->optind++; /* consume "--" */
- return -1;
- } else if (is_shortopt(option)) {
- return long_fallback(options, longopts, longindex);
- } else if (!is_longopt(option)) {
- if (options->permute) {
- int index = options->optind;
- options->optind++;
- int r = optparse_long(options, longopts, longindex);
- permute(options, index);
- options->optind--;
- return r;
- } else {
- return -1;
- }
- }
-
- /* Parse as long option. */
- options->errmsg[0] = '\0';
- options->optopt = 0;
- options->optarg = 0;
- option += 2; /* skip "--" */
- options->optind++;
- for (int i = 0; !longopts_end(longopts, i); i++) {
- const char *name = longopts[i].longname;
- if (longopts_match(name, option)) {
- if (longindex)
- *longindex = i;
- options->optopt = longopts[i].shortname;
- char *arg = longopts_arg(option);
- if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) {
- return opterror(options, MSG_TOOMANY, name);
- } if (arg != 0) {
- options->optarg = arg;
- } else if (longopts[i].argtype == OPTPARSE_REQUIRED) {
- options->optarg = options->argv[options->optind++];
- if (options->optarg == 0)
- return opterror(options, MSG_MISSING, name);
- }
- return options->optopt;
- }
- }
- return opterror(options, MSG_INVALID, option);
-}
\ No newline at end of file
diff --git a/src/vendor/optparse.h b/src/vendor/optparse.h
deleted file mode 100644
index c43a241..0000000
--- a/src/vendor/optparse.h
+++ /dev/null
@@ -1,102 +0,0 @@
-#ifndef OPTPARSE_H
-#define OPTPARSE_H
-
-/**
- * Optparse -- portable, reentrant, embeddable, getopt-like option parser
- *
- * The POSIX getopt() option parser has three fatal flaws. These flaws
- * are solved by Optparse.
- *
- * 1) Parser state is stored entirely in global variables, some of
- * which are static and inaccessible. This means only one thread can
- * use getopt(). It also means it's not possible to recursively parse
- * nested sub-arguments while in the middle of argument parsing.
- * Optparse fixes this by storing all state on a local struct.
- *
- * 2) The POSIX standard provides no way to properly reset the parser.
- * This means for portable code that getopt() is only good for one
- * run, over one argv with one optstring. It also means subcommand
- * options cannot be processed with getopt(). Most implementations
- * provide a method to reset the parser, but it's not portable.
- * Optparse provides an optparse_arg() function for stepping over
- * subcommands and continuing parsing of options with another
- * optstring. The Optparse struct itself can be passed around to
- * subcommand handlers for additional subcommand option parsing. A
- * full reset can be achieved by with an additional optparse_init().
- *
- * 3) Error messages are printed to stderr. This can be disabled with
- * opterr, but the messages themselves are still inaccessible.
- * Optparse solves this by writing an error message in its errmsg
- * field. The downside to Optparse is that this error message will
- * always be in English rather than the current locale.
- *
- * Optparse should be familiar with anyone accustomed to getopt(), and
- * it could be a nearly drop-in replacement. The optstring is the same
- * and the fields have the same names as the getopt() global variables
- * (optarg, optind, optopt).
- *
- * Optparse also supports GNU-style long options with optparse_long().
- * The interface is slightly different and simpler than getopt_long().
- *
- * By default, argv is permuted as it is parsed, moving non-option
- * arguments to the end. This can be disabled by setting the `permute`
- * field to 0 after initialization.
- */
-
-struct optparse {
- char **argv;
- int permute;
- int optind;
- int optopt;
- char *optarg;
- char errmsg[64];
- int subopt;
-};
-
-enum optparse_argtype { OPTPARSE_NONE, OPTPARSE_REQUIRED, OPTPARSE_OPTIONAL };
-
-struct optparse_long {
- const char *longname;
- int shortname;
- enum optparse_argtype argtype;
-};
-
-/**
- * Initializes the parser state.
- */
-void optparse_init(struct optparse *options, char **argv);
-
-/**
- * Read the next option in the argv array.
- * @param optstring a getopt()-formatted option string.
- * @return the next option character, -1 for done, or '?' for error
- *
- * Just like getopt(), a character followed by no colons means no
- * argument. One colon means the option has a required argument. Two
- * colons means the option takes an optional argument.
- */
-int optparse(struct optparse *options, const char *optstring);
-
-/**
- * Handles GNU-style long options in addition to getopt() options.
- * This works a lot like GNU's getopt_long(). The last option in
- * longopts must be all zeros, marking the end of the array. The
- * longindex argument may be NULL.
- */
-int
-optparse_long(struct optparse *options,
- const struct optparse_long *longopts,
- int *longindex);
-
-/**
- * Used for stepping over non-option arguments.
- * @return the next non-option argument, or -1 for no more arguments
- *
- * Argument parsing can continue with optparse() after using this
- * function. That would be used to parse the options for the
- * subcommand returned by optparse_arg(). This function allows you to
- * ignore the value of optind.
- */
-char *optparse_arg(struct optparse *options);
-
-#endif
\ No newline at end of file
diff --git a/src/vendor/tinydir.h b/src/vendor/tinydir.h
deleted file mode 100644
index 3bd0bd6..0000000
--- a/src/vendor/tinydir.h
+++ /dev/null
@@ -1,804 +0,0 @@
-/*
-Copyright (c) 2013-2016, tinydir authors:
-- Cong Xu
-- Lautis Sun
-- Baudouin Feildel
-- Andargor
-All rights reserved.
-
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are met:
-
-1. Redistributions of source code must retain the above copyright notice, this
- list of conditions and the following disclaimer.
-2. Redistributions in binary form must reproduce the above copyright notice,
- this list of conditions and the following disclaimer in the documentation
- and/or other materials provided with the distribution.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
-ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-*/
-#ifndef TINYDIR_H
-#define TINYDIR_H
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#if ((defined _UNICODE) && !(defined UNICODE))
-#define UNICODE
-#endif
-
-#if ((defined UNICODE) && !(defined _UNICODE))
-#define _UNICODE
-#endif
-
-#include
-#include
-#include
-#ifdef _MSC_VER
-# define WIN32_LEAN_AND_MEAN
-# include
-# include
-# pragma warning(push)
-# pragma warning (disable : 4996)
-#else
-# include
-# include
-# include
-# include
-#endif
-#ifdef __MINGW32__
-# include
-#endif
-
-
-/* types */
-
-/* Windows UNICODE wide character support */
-#if defined _MSC_VER || defined __MINGW32__
-#define _tinydir_char_t TCHAR
-#define TINYDIR_STRING(s) _TEXT(s)
-#define _tinydir_strlen _tcslen
-#define _tinydir_strcpy _tcscpy
-#define _tinydir_strcat _tcscat
-#define _tinydir_strcmp _tcscmp
-#define _tinydir_strrchr _tcsrchr
-#define _tinydir_strncmp _tcsncmp
-#else
-#define _tinydir_char_t char
-#define TINYDIR_STRING(s) s
-#define _tinydir_strlen strlen
-#define _tinydir_strcpy strcpy
-#define _tinydir_strcat strcat
-#define _tinydir_strcmp strcmp
-#define _tinydir_strrchr strrchr
-#define _tinydir_strncmp strncmp
-#endif
-
-#if (defined _MSC_VER || defined __MINGW32__)
-#include
-#define _TINYDIR_PATH_MAX MAX_PATH
-#elif defined __linux__
-#include
-#define _TINYDIR_PATH_MAX PATH_MAX
-#else
-#define _TINYDIR_PATH_MAX 4096
-#endif
-
-#ifdef _MSC_VER
-/* extra chars for the "\\*" mask */
-# define _TINYDIR_PATH_EXTRA 2
-#else
-# define _TINYDIR_PATH_EXTRA 0
-#endif
-
-#define _TINYDIR_FILENAME_MAX 256
-
-#if (defined _MSC_VER || defined __MINGW32__)
-#define _TINYDIR_DRIVE_MAX 3
-#endif
-
-#ifdef _MSC_VER
-# define _TINYDIR_FUNC static __inline
-#elif !defined __STDC_VERSION__ || __STDC_VERSION__ < 199901L
-# define _TINYDIR_FUNC static __inline__
-#else
-# define _TINYDIR_FUNC static inline
-#endif
-
-/* readdir_r usage; define TINYDIR_USE_READDIR_R to use it (if supported) */
-#ifdef TINYDIR_USE_READDIR_R
-
-/* readdir_r is a POSIX-only function, and may not be available under various
- * environments/settings, e.g. MinGW. Use readdir fallback */
-#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _BSD_SOURCE || _SVID_SOURCE ||\
- _POSIX_SOURCE
-# define _TINYDIR_HAS_READDIR_R
-#endif
-#if _POSIX_C_SOURCE >= 200112L
-# define _TINYDIR_HAS_FPATHCONF
-# include
-#endif
-#if _BSD_SOURCE || _SVID_SOURCE || \
- (_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
-# define _TINYDIR_HAS_DIRFD
-# include
-#endif
-#if defined _TINYDIR_HAS_FPATHCONF && defined _TINYDIR_HAS_DIRFD &&\
- defined _PC_NAME_MAX
-# define _TINYDIR_USE_FPATHCONF
-#endif
-#if defined __MINGW32__ || !defined _TINYDIR_HAS_READDIR_R ||\
- !(defined _TINYDIR_USE_FPATHCONF || defined NAME_MAX)
-# define _TINYDIR_USE_READDIR
-#endif
-
-/* Use readdir by default */
-#else
-# define _TINYDIR_USE_READDIR
-#endif
-
-/* MINGW32 has two versions of dirent, ASCII and UNICODE*/
-#ifndef _MSC_VER
-#if (defined __MINGW32__) && (defined _UNICODE)
-#define _TINYDIR_DIR _WDIR
-#define _tinydir_dirent _wdirent
-#define _tinydir_opendir _wopendir
-#define _tinydir_readdir _wreaddir
-#define _tinydir_closedir _wclosedir
-#else
-#define _TINYDIR_DIR DIR
-#define _tinydir_dirent dirent
-#define _tinydir_opendir opendir
-#define _tinydir_readdir readdir
-#define _tinydir_closedir closedir
-#endif
-#endif
-
-/* Allow user to use a custom allocator by defining _TINYDIR_MALLOC and _TINYDIR_FREE. */
-#if defined(_TINYDIR_MALLOC) && defined(_TINYDIR_FREE)
-#elif !defined(_TINYDIR_MALLOC) && !defined(_TINYDIR_FREE)
-#else
-#error "Either define both alloc and free or none of them!"
-#endif
-
-#if !defined(_TINYDIR_MALLOC)
-#define _TINYDIR_MALLOC(_size) malloc(_size)
-#define _TINYDIR_FREE(_ptr) free(_ptr)
-#endif /* !defined(_TINYDIR_MALLOC) */
-
-typedef struct tinydir_file
-{
- _tinydir_char_t path[_TINYDIR_PATH_MAX];
- _tinydir_char_t name[_TINYDIR_FILENAME_MAX];
- _tinydir_char_t *extension;
- int is_dir;
- int is_reg;
-
-#ifndef _MSC_VER
-#ifdef __MINGW32__
- struct _stat _s;
-#else
- struct stat _s;
-#endif
-#endif
-} tinydir_file;
-
-typedef struct tinydir_dir
-{
- _tinydir_char_t path[_TINYDIR_PATH_MAX];
- int has_next;
- size_t n_files;
-
- tinydir_file *_files;
-#ifdef _MSC_VER
- HANDLE _h;
- WIN32_FIND_DATA _f;
-#else
- _TINYDIR_DIR *_d;
- struct _tinydir_dirent *_e;
-#ifndef _TINYDIR_USE_READDIR
- struct _tinydir_dirent *_ep;
-#endif
-#endif
-} tinydir_dir;
-
-
-/* declarations */
-
-_TINYDIR_FUNC
-int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path);
-_TINYDIR_FUNC
-int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path);
-_TINYDIR_FUNC
-void tinydir_close(tinydir_dir *dir);
-
-_TINYDIR_FUNC
-int tinydir_next(tinydir_dir *dir);
-_TINYDIR_FUNC
-int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file);
-_TINYDIR_FUNC
-int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i);
-_TINYDIR_FUNC
-int tinydir_open_subdir_n(tinydir_dir *dir, size_t i);
-
-_TINYDIR_FUNC
-int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path);
-_TINYDIR_FUNC
-void _tinydir_get_ext(tinydir_file *file);
-_TINYDIR_FUNC
-int _tinydir_file_cmp(const void *a, const void *b);
-#ifndef _MSC_VER
-#ifndef _TINYDIR_USE_READDIR
-_TINYDIR_FUNC
-size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp);
-#endif
-#endif
-
-
-/* definitions*/
-
-_TINYDIR_FUNC
-int tinydir_open(tinydir_dir *dir, const _tinydir_char_t *path)
-{
-#ifndef _MSC_VER
-#ifndef _TINYDIR_USE_READDIR
- int error;
- int size; /* using int size */
-#endif
-#else
- _tinydir_char_t path_buf[_TINYDIR_PATH_MAX];
-#endif
- _tinydir_char_t *pathp;
-
- if (dir == NULL || path == NULL || _tinydir_strlen(path) == 0)
- {
- errno = EINVAL;
- return -1;
- }
- if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
- {
- errno = ENAMETOOLONG;
- return -1;
- }
-
- /* initialise dir */
- dir->_files = NULL;
-#ifdef _MSC_VER
- dir->_h = INVALID_HANDLE_VALUE;
-#else
- dir->_d = NULL;
-#ifndef _TINYDIR_USE_READDIR
- dir->_ep = NULL;
-#endif
-#endif
- tinydir_close(dir);
-
- _tinydir_strcpy(dir->path, path);
- /* Remove trailing slashes */
- pathp = &dir->path[_tinydir_strlen(dir->path) - 1];
- while (pathp != dir->path && (*pathp == TINYDIR_STRING('\\') || *pathp == TINYDIR_STRING('/')))
- {
- *pathp = TINYDIR_STRING('\0');
- pathp++;
- }
-#ifdef _MSC_VER
- _tinydir_strcpy(path_buf, dir->path);
- _tinydir_strcat(path_buf, TINYDIR_STRING("\\*"));
- dir->_h = FindFirstFile(path_buf, &dir->_f);
- if (dir->_h == INVALID_HANDLE_VALUE)
- {
- errno = ENOENT;
-#else
- dir->_d = _tinydir_opendir(path);
- if (dir->_d == NULL)
- {
-#endif
- goto bail;
- }
-
- /* read first file */
- dir->has_next = 1;
-#ifndef _MSC_VER
-#ifdef _TINYDIR_USE_READDIR
- dir->_e = _tinydir_readdir(dir->_d);
-#else
- /* allocate dirent buffer for readdir_r */
- size = _tinydir_dirent_buf_size(dir->_d); /* conversion to int */
- if (size == -1) return -1;
- dir->_ep = (struct _tinydir_dirent*)_TINYDIR_MALLOC(size);
- if (dir->_ep == NULL) return -1;
-
- error = readdir_r(dir->_d, dir->_ep, &dir->_e);
- if (error != 0) return -1;
-#endif
- if (dir->_e == NULL)
- {
- dir->has_next = 0;
- }
-#endif
-
- return 0;
-
- bail:
- tinydir_close(dir);
- return -1;
-}
-
-_TINYDIR_FUNC
-int tinydir_open_sorted(tinydir_dir *dir, const _tinydir_char_t *path)
-{
- /* Count the number of files first, to pre-allocate the files array */
- size_t n_files = 0;
- if (tinydir_open(dir, path) == -1)
- {
- return -1;
- }
- while (dir->has_next)
- {
- n_files++;
- if (tinydir_next(dir) == -1)
- {
- goto bail;
- }
- }
- tinydir_close(dir);
-
- if (tinydir_open(dir, path) == -1)
- {
- return -1;
- }
-
- dir->n_files = 0;
- dir->_files = (tinydir_file *)_TINYDIR_MALLOC(sizeof *dir->_files * n_files);
- if (dir->_files == NULL)
- {
- goto bail;
- }
- while (dir->has_next)
- {
- tinydir_file *p_file;
- dir->n_files++;
-
- p_file = &dir->_files[dir->n_files - 1];
- if (tinydir_readfile(dir, p_file) == -1)
- {
- goto bail;
- }
-
- if (tinydir_next(dir) == -1)
- {
- goto bail;
- }
-
- /* Just in case the number of files has changed between the first and
- second reads, terminate without writing into unallocated memory */
- if (dir->n_files == n_files)
- {
- break;
- }
- }
-
- qsort(dir->_files, dir->n_files, sizeof(tinydir_file), _tinydir_file_cmp);
-
- return 0;
-
- bail:
- tinydir_close(dir);
- return -1;
-}
-
-_TINYDIR_FUNC
-void tinydir_close(tinydir_dir *dir)
-{
- if (dir == NULL)
- {
- return;
- }
-
- memset(dir->path, 0, sizeof(dir->path));
- dir->has_next = 0;
- dir->n_files = 0;
- _TINYDIR_FREE(dir->_files);
- dir->_files = NULL;
-#ifdef _MSC_VER
- if (dir->_h != INVALID_HANDLE_VALUE)
- {
- FindClose(dir->_h);
- }
- dir->_h = INVALID_HANDLE_VALUE;
-#else
- if (dir->_d)
- {
- _tinydir_closedir(dir->_d);
- }
- dir->_d = NULL;
- dir->_e = NULL;
-#ifndef _TINYDIR_USE_READDIR
- _TINYDIR_FREE(dir->_ep);
- dir->_ep = NULL;
-#endif
-#endif
-}
-
-_TINYDIR_FUNC
-int tinydir_next(tinydir_dir *dir)
-{
- if (dir == NULL)
- {
- errno = EINVAL;
- return -1;
- }
- if (!dir->has_next)
- {
- errno = ENOENT;
- return -1;
- }
-
-#ifdef _MSC_VER
- if (FindNextFile(dir->_h, &dir->_f) == 0)
-#else
-#ifdef _TINYDIR_USE_READDIR
- dir->_e = _tinydir_readdir(dir->_d);
-#else
- if (dir->_ep == NULL)
- {
- return -1;
- }
- if (readdir_r(dir->_d, dir->_ep, &dir->_e) != 0)
- {
- return -1;
- }
-#endif
- if (dir->_e == NULL)
-#endif
- {
- dir->has_next = 0;
-#ifdef _MSC_VER
- if (GetLastError() != ERROR_SUCCESS &&
- GetLastError() != ERROR_NO_MORE_FILES)
- {
- tinydir_close(dir);
- errno = EIO;
- return -1;
- }
-#endif
- }
-
- return 0;
-}
-
-_TINYDIR_FUNC
-int tinydir_readfile(const tinydir_dir *dir, tinydir_file *file)
-{
- if (dir == NULL || file == NULL)
- {
- errno = EINVAL;
- return -1;
- }
-#ifdef _MSC_VER
- if (dir->_h == INVALID_HANDLE_VALUE)
-#else
- if (dir->_e == NULL)
-#endif
- {
- errno = ENOENT;
- return -1;
- }
- if (_tinydir_strlen(dir->path) +
- _tinydir_strlen(
-#ifdef _MSC_VER
- dir->_f.cFileName
-#else
- dir->_e->d_name
-#endif
- ) + 1 + _TINYDIR_PATH_EXTRA >=
- _TINYDIR_PATH_MAX)
- {
- /* the path for the file will be too long */
- errno = ENAMETOOLONG;
- return -1;
- }
- if (_tinydir_strlen(
-#ifdef _MSC_VER
- dir->_f.cFileName
-#else
- dir->_e->d_name
-#endif
- ) >= _TINYDIR_FILENAME_MAX)
- {
- errno = ENAMETOOLONG;
- return -1;
- }
-
- _tinydir_strcpy(file->path, dir->path);
- _tinydir_strcat(file->path, TINYDIR_STRING("/"));
- _tinydir_strcpy(file->name,
- #ifdef _MSC_VER
- dir->_f.cFileName
-#else
- dir->_e->d_name
-#endif
- );
- _tinydir_strcat(file->path, file->name);
-#ifndef _MSC_VER
-#ifdef __MINGW32__
- if (_tstat(
-#else
- if (stat(
-#endif
- file->path, &file->_s) == -1)
- {
- return -1;
- }
-#endif
- _tinydir_get_ext(file);
-
- file->is_dir =
-#ifdef _MSC_VER
- !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
-#else
- S_ISDIR(file->_s.st_mode);
-#endif
- file->is_reg =
-#ifdef _MSC_VER
- !!(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NORMAL) ||
- (
- !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DEVICE) &&
- !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
- !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_ENCRYPTED) &&
-#ifdef FILE_ATTRIBUTE_INTEGRITY_STREAM
- !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_INTEGRITY_STREAM) &&
-#endif
-#ifdef FILE_ATTRIBUTE_NO_SCRUB_DATA
- !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_NO_SCRUB_DATA) &&
-#endif
- !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_OFFLINE) &&
- !(dir->_f.dwFileAttributes & FILE_ATTRIBUTE_TEMPORARY));
-#else
- S_ISREG(file->_s.st_mode);
-#endif
-
- return 0;
-}
-
-_TINYDIR_FUNC
-int tinydir_readfile_n(const tinydir_dir *dir, tinydir_file *file, size_t i)
-{
- if (dir == NULL || file == NULL)
- {
- errno = EINVAL;
- return -1;
- }
- if (i >= dir->n_files)
- {
- errno = ENOENT;
- return -1;
- }
-
- memcpy(file, &dir->_files[i], sizeof(tinydir_file));
- _tinydir_get_ext(file);
-
- return 0;
-}
-
-_TINYDIR_FUNC
-int tinydir_open_subdir_n(tinydir_dir *dir, size_t i)
-{
- _tinydir_char_t path[_TINYDIR_PATH_MAX];
- if (dir == NULL)
- {
- errno = EINVAL;
- return -1;
- }
- if (i >= dir->n_files || !dir->_files[i].is_dir)
- {
- errno = ENOENT;
- return -1;
- }
-
- _tinydir_strcpy(path, dir->_files[i].path);
- tinydir_close(dir);
- if (tinydir_open_sorted(dir, path) == -1)
- {
- return -1;
- }
-
- return 0;
-}
-
-/* Open a single file given its path */
-_TINYDIR_FUNC
-int tinydir_file_open(tinydir_file *file, const _tinydir_char_t *path)
-{
- tinydir_dir dir;
- int result = 0;
- int found = 0;
- _tinydir_char_t dir_name_buf[_TINYDIR_PATH_MAX];
- _tinydir_char_t file_name_buf[_TINYDIR_FILENAME_MAX];
- _tinydir_char_t *dir_name;
- _tinydir_char_t *base_name;
-#if (defined _MSC_VER || defined __MINGW32__)
- _tinydir_char_t drive_buf[_TINYDIR_DRIVE_MAX];
- _tinydir_char_t ext_buf[_TINYDIR_FILENAME_MAX];
-#endif
-
- if (file == NULL || path == NULL || _tinydir_strlen(path) == 0)
- {
- errno = EINVAL;
- return -1;
- }
- if (_tinydir_strlen(path) + _TINYDIR_PATH_EXTRA >= _TINYDIR_PATH_MAX)
- {
- errno = ENAMETOOLONG;
- return -1;
- }
-
- /* Get the parent path */
-#if (defined _MSC_VER || defined __MINGW32__)
- #if ((defined _MSC_VER) && (_MSC_VER >= 1400))
- _tsplitpath_s(
- path,
- drive_buf, _TINYDIR_DRIVE_MAX,
- dir_name_buf, _TINYDIR_FILENAME_MAX,
- file_name_buf, _TINYDIR_FILENAME_MAX,
- ext_buf, _TINYDIR_FILENAME_MAX);
-#else
- _tsplitpath(
- path,
- drive_buf,
- dir_name_buf,
- file_name_buf,
- ext_buf);
-#endif
-
-/* _splitpath_s not work fine with only filename and widechar support */
-#ifdef _UNICODE
- if (drive_buf[0] == L'\xFEFE')
- drive_buf[0] = '\0';
- if (dir_name_buf[0] == L'\xFEFE')
- dir_name_buf[0] = '\0';
-#endif
-
- if (errno)
- {
- errno = EINVAL;
- return -1;
- }
- /* Emulate the behavior of dirname by returning "." for dir name if it's
- empty */
- if (drive_buf[0] == '\0' && dir_name_buf[0] == '\0')
- {
- _tinydir_strcpy(dir_name_buf, TINYDIR_STRING("."));
- }
- /* Concatenate the drive letter and dir name to form full dir name */
- _tinydir_strcat(drive_buf, dir_name_buf);
- dir_name = drive_buf;
- /* Concatenate the file name and extension to form base name */
- _tinydir_strcat(file_name_buf, ext_buf);
- base_name = file_name_buf;
-#else
- _tinydir_strcpy(dir_name_buf, path);
- dir_name = dirname(dir_name_buf);
- _tinydir_strcpy(file_name_buf, path);
- base_name =basename(file_name_buf);
-#endif
-
- /* Open the parent directory */
- if (tinydir_open(&dir, dir_name) == -1)
- {
- return -1;
- }
-
- /* Read through the parent directory and look for the file */
- while (dir.has_next)
- {
- if (tinydir_readfile(&dir, file) == -1)
- {
- result = -1;
- goto bail;
- }
- if (_tinydir_strcmp(file->name, base_name) == 0)
- {
- /* File found */
- found = 1;
- break;
- }
- tinydir_next(&dir);
- }
- if (!found)
- {
- result = -1;
- errno = ENOENT;
- }
-
- bail:
- tinydir_close(&dir);
- return result;
-}
-
-_TINYDIR_FUNC
-void _tinydir_get_ext(tinydir_file *file)
-{
- _tinydir_char_t *period = _tinydir_strrchr(file->name, TINYDIR_STRING('.'));
- if (period == NULL)
- {
- file->extension = &(file->name[_tinydir_strlen(file->name)]);
- }
- else
- {
- file->extension = period + 1;
- }
-}
-
-_TINYDIR_FUNC
-int _tinydir_file_cmp(const void *a, const void *b)
-{
- const tinydir_file *fa = (const tinydir_file *)a;
- const tinydir_file *fb = (const tinydir_file *)b;
- if (fa->is_dir != fb->is_dir)
- {
- return -(fa->is_dir - fb->is_dir);
- }
- return _tinydir_strncmp(fa->name, fb->name, _TINYDIR_FILENAME_MAX);
-}
-
-#ifndef _MSC_VER
-#ifndef _TINYDIR_USE_READDIR
-/*
-The following authored by Ben Hutchings
-from https://womble.decadent.org.uk/readdir_r-advisory.html
-*/
-/* Calculate the required buffer size (in bytes) for directory *
-* entries read from the given directory handle. Return -1 if this *
-* this cannot be done. *
-* *
-* This code does not trust values of NAME_MAX that are less than *
-* 255, since some systems (including at least HP-UX) incorrectly *
-* define it to be a smaller value. */
-_TINYDIR_FUNC
-size_t _tinydir_dirent_buf_size(_TINYDIR_DIR *dirp)
-{
- long name_max;
- size_t name_end;
- /* parameter may be unused */
- (void)dirp;
-
-#if defined _TINYDIR_USE_FPATHCONF
- name_max = fpathconf(dirfd(dirp), _PC_NAME_MAX);
- if (name_max == -1)
-#if defined(NAME_MAX)
- name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
-#else
- return (size_t)(-1);
-#endif
-#elif defined(NAME_MAX)
- name_max = (NAME_MAX > 255) ? NAME_MAX : 255;
-#else
-#error "buffer size for readdir_r cannot be determined"
-#endif
- name_end = (size_t)offsetof(struct _tinydir_dirent, d_name) + name_max + 1;
- return (name_end > sizeof(struct _tinydir_dirent) ?
- name_end : sizeof(struct _tinydir_dirent));
-}
-#endif
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-
-# if defined (_MSC_VER)
-# pragma warning(pop)
-# endif
-
-#endif
\ No newline at end of file