Rust switch

This commit is contained in:
Matteo Paonessa 2022-12-20 13:38:05 +01:00
parent aacb482ad8
commit 7bdf972c00
36 changed files with 1737 additions and 2711 deletions

View File

@ -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}}

188
.gitignore vendored
View File

@ -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

2
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Default ignored files
/workspace.xml

View File

@ -1 +0,0 @@
caesiumclt

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<module classpath="CMake" type="CPP_MODULE" version="4" />

15
.idea/cclt.iml Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -1,3 +0,0 @@
<component name="DependencyValidationManager">
<scope name="Inspection" pattern="!file:src/optparse.c&amp;&amp;!file:src/optparse.h&amp;&amp;!file:src/tinydir.h&amp;&amp;!file:.gitignore" />
</component>

View File

@ -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>

View File

@ -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)

1174
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

17
Cargo.toml Normal file
View File

@ -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" }

View File

@ -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`.

View File

@ -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

View File

@ -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

View File

@ -1,6 +0,0 @@
#!/bin/sh
#libcaesium
git clone https://github.com/Lymphatus/libcaesium.git
cd libcaesium || exit
cargo build --release

View File

@ -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)

View File

@ -1,4 +0,0 @@
#define VERSION_MAJOR @VERSION_MAJOR@
#define VERSION_MINOR @VERSION_MINOR@
#define VERSION_PATCH @VERSION_PATCH@

View File

@ -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.";
}
}

View File

@ -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

View File

@ -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, &parameters, 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;
}

View File

@ -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

16
src/logger.rs Normal file
View File

@ -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)
};
}

View File

@ -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);
}

264
src/main.rs Normal file
View File

@ -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
}

93
src/options.rs Normal file
View File

@ -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);
}
}

110
src/scanfiles.rs Normal file
View File

@ -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
}

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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

264
src/vendor/optparse.c vendored
View File

@ -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);
}

102
src/vendor/optparse.h vendored
View File

@ -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

804
src/vendor/tinydir.h vendored
View File

@ -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