Rust switch
This commit is contained in:
parent
aacb482ad8
commit
7bdf972c00
|
@ -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}}
|
||||
|
|
@ -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
|
||||
/target
|
||||
**/*.rs.bk
|
||||
test_output
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
# Default ignored files
|
||||
/workspace.xml
|
|
@ -1 +0,0 @@
|
|||
caesiumclt
|
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module classpath="CMake" type="CPP_MODULE" version="4" />
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="CPP_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/examples" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/benches" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/target" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/cmake-build-debug/CMakeFiles" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,125 +0,0 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ClangTidyInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DeprecatedAPI" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="DuplicateSwitchCase" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EndlessLoop" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="EqualityInConditionalOperator" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FormatSpecifiers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="FunctionImplicitDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HidesUpperScope" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="HidingNonVirtualFunction" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ImplicitIntegerAndEnumConversion" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ImplicitPointerAndIntegerConversion" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IncompatibleEnums" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IncompatibleInitializers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="IncompatiblePointers" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="InfiniteRecursion" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="KRUnspecifiedParameters" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="LocalValueEscapesScope" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MissingReturn" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="MissingSwitchCase" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NotImplementedFunctions" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NotInitializedVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="NotSuperclass" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OCDFAInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OCLoopDoesntUseConditionVariableInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OCSimplifyInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OCUnusedGlobalDeclarationInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OCUnusedMacroInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OCUnusedStructInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="OCUnusedTemplateParameterInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="RedundantCast" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ResourceNotFoundInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="SignednessMismatch" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnreachableCode" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedExpressionResult" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedImportStatement" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedLocalVariable" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedLocalization" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedParameter" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="UnusedValue" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ValueMayNotFitIntoReceiver" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<scope name="Inspection" level="WARNING" enabled="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
|
@ -1,12 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
<component name="CidrRootsConfiguration">
|
||||
<sourceRoots>
|
||||
<file path="$PROJECT_DIR$/src" />
|
||||
</sourceRoots>
|
||||
<excludeRoots>
|
||||
<file path="$PROJECT_DIR$/cmake-build-debug/CMakeFiles" />
|
||||
</excludeRoots>
|
||||
</component>
|
||||
</project>
|
|
@ -2,7 +2,7 @@
|
|||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/caesium-clt.iml" filepath="$PROJECT_DIR$/.idea/caesium-clt.iml" />
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/cclt.iml" filepath="$PROJECT_DIR$/.idea/cclt.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -1,3 +0,0 @@
|
|||
<component name="DependencyValidationManager">
|
||||
<scope name="Inspection" pattern="!file:src/optparse.c&&!file:src/optparse.h&&!file:src/tinydir.h&&!file:.gitignore" />
|
||||
</component>
|
|
@ -1,6 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -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)
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "caesiumclt"
|
||||
version = "0.19.0"
|
||||
authors = ["Matteo Paonessa <matteo.paonessa@gmail.com>"]
|
||||
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" }
|
22
INSTALL.md
22
INSTALL.md
|
@ -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`.
|
||||
|
||||
|
46
README.md
46
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
#libcaesium
|
||||
git clone https://github.com/Lymphatus/libcaesium.git
|
||||
cd libcaesium || exit
|
||||
cargo build --release
|
|
@ -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)
|
|
@ -1,4 +0,0 @@
|
|||
#define VERSION_MAJOR @VERSION_MAJOR@
|
||||
#define VERSION_MINOR @VERSION_MINOR@
|
||||
#define VERSION_PATCH @VERSION_PATCH@
|
||||
|
79
src/error.c
79
src/error.c
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#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.";
|
||||
}
|
||||
}
|
31
src/error.h
31
src/error.h
|
@ -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
|
351
src/helper.c
351
src/helper.c
|
@ -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 <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#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;
|
||||
}
|
||||
|
||||
|
78
src/helper.h
78
src/helper.h
|
@ -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 <stdbool.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#define MAX_PATH_SIZE _MAX_PATH
|
||||
#else
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#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
|
|
@ -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)
|
||||
};
|
||||
}
|
78
src/main.c
78
src/main.c
|
@ -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 <stdlib.h>
|
||||
#include <time.h>
|
||||
#include <stdio.h>
|
||||
#include "utils.h"
|
||||
#include "shared.h"
|
||||
#include <stdbool.h>
|
||||
|
||||
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);
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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<PathBuf>,
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<PathBuf>, recursive: bool) -> (PathBuf, Vec<PathBuf>) {
|
||||
let mut files: Vec<PathBuf> = 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
|
||||
}
|
19
src/shared.c
19
src/shared.c
|
@ -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;
|
23
src/shared.h
23
src/shared.h
|
@ -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
|
354
src/utils.c
354
src/utils.c
|
@ -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 <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <limits.h>
|
||||
#include <math.h>
|
||||
#include <errno.h>
|
||||
#include <stdarg.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <stdint.h>
|
||||
#include <Windows.h>
|
||||
#endif
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
#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
|
49
src/utils.h
49
src/utils.h
|
@ -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
|
|
@ -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);
|
||||
}
|
|
@ -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
|
|
@ -1,804 +0,0 @@
|
|||
/*
|
||||
Copyright (c) 2013-2016, tinydir authors:
|
||||
- Cong Xu
|
||||
- Lautis Sun
|
||||
- Baudouin Feildel
|
||||
- Andargor <andargor@yahoo.com>
|
||||
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 <errno.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef _MSC_VER
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# include <windows.h>
|
||||
# include <tchar.h>
|
||||
# pragma warning(push)
|
||||
# pragma warning (disable : 4996)
|
||||
#else
|
||||
# include <dirent.h>
|
||||
# include <libgen.h>
|
||||
# include <sys/stat.h>
|
||||
# include <stddef.h>
|
||||
#endif
|
||||
#ifdef __MINGW32__
|
||||
# include <tchar.h>
|
||||
#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 <windows.h>
|
||||
#define _TINYDIR_PATH_MAX MAX_PATH
|
||||
#elif defined __linux__
|
||||
#include <linux/limits.h>
|
||||
#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 <unistd.h>
|
||||
#endif
|
||||
#if _BSD_SOURCE || _SVID_SOURCE || \
|
||||
(_POSIX_C_SOURCE >= 200809L || _XOPEN_SOURCE >= 700)
|
||||
# define _TINYDIR_HAS_DIRFD
|
||||
# include <sys/types.h>
|
||||
#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 <ben@decadent.org.uk>
|
||||
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
|
Loading…
Reference in New Issue