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
|
/target
|
||||||
.DS_Store
|
**/*.rs.bk
|
||||||
Desktop.ini
|
test_output
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
|
@ -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">
|
<project version="4">
|
||||||
<component name="ProjectModuleManager">
|
<component name="ProjectModuleManager">
|
||||||
<modules>
|
<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>
|
</modules>
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</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"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</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 CommandLineTools
|
||||||
###### caesium-clt - v0.18.0-beta (build 20221106)
|
###### caesium-clt - v0.19.0 (build 20221114)
|
||||||
|
|
||||||
###### REQUIREMENTS
|
###### REQUIREMENTS
|
||||||
* [libcaesium](https://github.com/Lymphatus/libcaesium) >= 0.9.3
|
* [Rust](https://www.rust-lang.org/tools/install)
|
||||||
|
|
||||||
###### Included libraries
|
|
||||||
* [optparse](https://github.com/skeeto/optparse)
|
|
||||||
* [tinydir](https://github.com/cxong/tinydir)
|
|
||||||
|
|
||||||
----------
|
----------
|
||||||
|
|
||||||
###### TESTED PLATFORMS
|
###### TESTED PLATFORMS
|
||||||
|
@ -18,7 +13,7 @@
|
||||||
----------
|
----------
|
||||||
|
|
||||||
###### COMPILATION
|
###### COMPILATION
|
||||||
See INSTALL.md for more details.
|
`cargo build --release`
|
||||||
|
|
||||||
----------
|
----------
|
||||||
|
|
||||||
|
@ -37,7 +32,7 @@ See INSTALL.md for more details.
|
||||||
- `-S, --keep-structure`
|
- `-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.
|
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`
|
- `-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`
|
- `-d, --dry-run`
|
||||||
If this option is set, no files will be compressed, but the entire process will just be simulated.
|
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.
|
Useful for checking if all the files will be correctly handled.
|
||||||
|
@ -81,19 +76,20 @@ $ caesiumclt -q 0 -RS -o ~/output/ ~/Pictures
|
||||||
----------
|
----------
|
||||||
|
|
||||||
###### CHANGELOG
|
###### CHANGELOG
|
||||||
* 0.18.0-beta - Fixed Windows build + libcaesium 0.9.3
|
* 0.19.0 - Rust migration
|
||||||
* 0.17.0-beta - libcaesium 0.9.2
|
* 0.18.0 - Fixed Windows build + libcaesium 0.9.3
|
||||||
* 0.16.0-beta - Using libcaesium Rust library
|
* 0.17.0 - libcaesium 0.9.2
|
||||||
* 0.15.2-beta - Fixed Windows -RS bug
|
* 0.16.0 - Using libcaesium Rust library
|
||||||
* 0.15.1-beta - Fixed rename bug on Windows + "Compressing..." message
|
* 0.15.2 - Fixed Windows -RS bug
|
||||||
* 0.15.0-beta - Support for libcaesium 0.5.0
|
* 0.15.1 - Fixed rename bug on Windows + "Compressing..." message
|
||||||
* 0.14.0-beta - Added --quiet option
|
* 0.15.0 - Support for libcaesium 0.5.0
|
||||||
* 0.13.1-beta - Bugfix
|
* 0.14.0 - Added --quiet option
|
||||||
* 0.13.0-beta - Bugfix
|
* 0.13.1 - Bugfix
|
||||||
* 0.12.1-beta - Bugfix
|
* 0.13.0 - Bugfix
|
||||||
* 0.12.0-beta - Resizing (experimental)
|
* 0.12.1 - Bugfix
|
||||||
* 0.11.0-beta - Fixing paths issues and dry-run option
|
* 0.12.0 - Resizing (experimental)
|
||||||
* 0.10.2-beta - Bugfixes & full Windows support
|
* 0.11.0 - Fixing paths issues and dry-run option
|
||||||
* 0.10.1-beta - All features are available
|
* 0.10.2 - Bugfixes & full Windows support
|
||||||
* 0.10.0-beta - Switched to cmake build system and libcaesium
|
* 0.10.1 - All features are available
|
||||||
* 0.9.1-beta - Initial development stage
|
* 0.10.0 - Switched to cmake build system and libcaesium
|
||||||
|
* 0.9.1 - Initial development stage
|
||||||
|
|
|
@ -1,13 +1,8 @@
|
||||||
## Caesium 命令行工具
|
## 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
|
* [Rust](https://www.rust-lang.org/tools/install)
|
||||||
|
|
||||||
###### 已包含的库
|
|
||||||
* [optparse](https://github.com/skeeto/optparse)
|
|
||||||
* [tinydir](https://github.com/cxong/tinydir)
|
|
||||||
|
|
||||||
----------
|
----------
|
||||||
|
|
||||||
###### 已通过测试的平台
|
###### 已通过测试的平台
|
||||||
|
@ -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.19.0 - Rust migration
|
||||||
* 0.17.0-beta - libcaesium 0.9.2
|
* 0.18.0 - Fixed Windows build + libcaesium 0.9.3
|
||||||
* 0.16.0-beta - Using libcaesium Rust library
|
* 0.17.0 - libcaesium 0.9.2
|
||||||
* 0.15.2-beta - Fixed Windows -RS bug
|
* 0.16.0 - Using libcaesium Rust library
|
||||||
* 0.15.1-beta - Fixed rename bug on Windows + "Compressing..." message
|
* 0.15.2 - Fixed Windows -RS bug
|
||||||
* 0.15.0-beta - Support for libcaesium 0.5.0
|
* 0.15.1 - Fixed rename bug on Windows + "Compressing..." message
|
||||||
* 0.14.0-beta - Added --quiet option
|
* 0.15.0 - Support for libcaesium 0.5.0
|
||||||
* 0.13.1-beta - Bugfix
|
* 0.14.0 - Added --quiet option
|
||||||
* 0.13.0-beta - Bugfix
|
* 0.13.1 - Bugfix
|
||||||
* 0.12.1-beta - Bugfix
|
* 0.13.0 - Bugfix
|
||||||
* 0.12.0-beta - Resizing (experimental)
|
* 0.12.1 - Bugfix
|
||||||
* 0.11.0-beta - Fixing paths issues and dry-run option
|
* 0.12.0 - Resizing (experimental)
|
||||||
* 0.10.2-beta - Bugfixes & full Windows support
|
* 0.11.0 - Fixing paths issues and dry-run option
|
||||||
* 0.10.1-beta - All features are available
|
* 0.10.2 - Bugfixes & full Windows support
|
||||||
* 0.10.0-beta - Switched to cmake build system and libcaesium
|
* 0.10.1 - All features are available
|
||||||
* 0.9.1-beta - Initial development stage
|
* 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