chore: baseline usable version

This commit is contained in:
dela
2026-06-10 11:17:51 +08:00
commit a2f82a86b6
932 changed files with 172551 additions and 0 deletions

View File

@@ -0,0 +1,130 @@
# References:
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
# https://code.qt.io/cgit/qt/qt5.git/tree/_clang-format
BasedOnStyle: LLVM
Standard: c++17
# 指针和引用的对齐方式。
# 可能的值有:
# PAS_Left (在配置中: Left) 指针左对齐。
# PAS_Right (在配置中: Right) 指针右对齐。
# PAS_Middle (在配置中: Middle) 指针中间对齐。
PointerAlignment: Right
# public/protected/private 等访问修饰符偏移量
AccessModifierOffset: -4
# 缩进长度
IndentWidth: 4
# 连续空行的最大数
MaxEmptyLinesToKeep: 999
# 在OC中的@property后面添加一个空格。例如使用“@property (readonly)”而不是“@property(readonly)”
ObjCSpaceAfterProperty: true
# OC块中所拍的字符数
ObjCBlockIndentWidth: 4
# 取决于值, 语句“int f() { return 0; }”可以被放到一个单行。
# 可能的值有:
# SFS_None (在配置中: None) 从不合并方法或函数到单独的一行。
# SFS_Empty (在配置中: Empty) 仅合并空的函数。
# SFS_Inline (在配置中: Inline) 仅合并类中定义的方法或函数. 意味着 “empty”.
# SFS_All (在配置中: All) 合并所有的方法适应单行.
AllowShortFunctionsOnASingleLine: None
# 如果为真true, 语句“if (a) return;” 能被放到单行。
AllowShortIfStatementsOnASingleLine: false
# 如果为真true, 对齐注释。
AlignTrailingComments: true
# 如果为真,对齐连续的宏定义
AlignConsecutiveMacros: true
# 如果为真true,将会在“[”之后和“]”之前插入空格。
SpacesInSquareBrackets: false
# 如果为真true, 将会在“(”之后和“)”之前插入空格。
SpacesInParentheses : false
# 如果为真true, 校准连续的声明。
# 这将会校准连续多行的声明的名字。这将会导致像下面这样的格式:
# int aaaa = 12;
# float b = 23;
# std::string ccc = 23;
AlignConsecutiveDeclarations: false
# 如果为真true连续调整多行
# 这将会调整连续行中的分配操作符。这将会导致像下面这样的格式:
# int aaaa = 12;
# int b = 23;
# int ccc = 23;
AlignConsecutiveAssignments: false
# 如果为假false移除分配操作符=)前空格。
SpaceBeforeAssignmentOperators: true
# 如果为真true, 将会在字面量容器中插入空格(例如 OC和Javascript的数组和字典字面量)。
SpacesInContainerLiterals: false
# 缩进case标签
IndentCaseLabels: true
# 如果表达式中包含函数调用,并且函数调用因为表达式太长被放到了下一行,是否缩进
IndentWrappedFunctionNames: true
# 如果为真true, 保持块的起始空行。
# true: false:
# if (foo) { vs. if (foo) {
# bar();
# bar(); }
# }
KeepEmptyLinesAtTheStartOfBlocks: true
# 允许所有参数都被放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 使用C风格强制类型转换后是否在中间添加一个空格
SpaceAfterCStyleCast: true
# 在模板定义后换行
AlwaysBreakTemplateDeclarations: Yes
# Tab长度
TabWidth: 4
# 是否使用Tab
UseTab: Never
# 在括号后对齐参数
# someLongFunction(argument1,
# argument2);
AlignAfterOpenBracket: Align
# 名字空间内部缩进
NamespaceIndentation: All
# 一行最长列数
ColumnLimit: 100
# 按层次缩进宏定义
IndentPPDirectives: AfterHash
# 预处理语句缩进为 2
PPIndentWidth: 2
# 数组元素对齐
AlignArrayOfStructures: Left
# 不对头文件排序
SortIncludes: Never
FixNamespaceComments: false
StatementMacros: ['__qas_attr__', '__qas_exclude__', '__qas_include__']
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ]

98
libs/qwindowkit/qmsetup/.gitignore vendored Normal file
View File

@@ -0,0 +1,98 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.log
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
# *.res
# *.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
__pycache__
*.pyc
# Binaries
# --------
*.dll
*.exe
.DS_Store
*/.DS_Store
build-src*
build/
cmake-build*
*.user
*.lnk
_workingDir*
.vscode
.idea
.cache
cache
.vs
out/
CMakeSettings.json
# /vcpkg
/data
/*.natvis
*.sublime-*
setup-vcpkg.json
setup-vcpkg-temp*

4
libs/qwindowkit/qmsetup/.gitmodules vendored Normal file
View File

@@ -0,0 +1,4 @@
[submodule "syscmdline"]
path = src/syscmdline
url = ../../SineStriker/syscmdline.git
branch = main

View File

@@ -0,0 +1,46 @@
cmake_minimum_required(VERSION 3.19)
project(qmsetup VERSION 1.0.0.0)
# ----------------------------------
# Configure Options
# ----------------------------------
option(QMSETUP_VCPKG_TOOLS_HINT "Install executables to tools directory" OFF)
option(QMSETUP_STATIC_RUNTIME "Static link runtime libraries on Windows" ON)
# ----------------------------------
# CMake Settings
# ----------------------------------
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
endif()
if(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /manifest:no")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /manifest:no")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /manifest:no")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
set(CMAKE_DEBUG_POSTFIX "d")
endif()
endif()
# ----------------------------------
# Project Variables
# ----------------------------------
set(QMSETUP_VERSION ${PROJECT_VERSION})
set(QMSETUP_INSTALL_NAME ${PROJECT_NAME})
set(QMSETUP_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR})
include(cmake/QMSetupAPI.cmake)
add_subdirectory(src)

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) Stdware Collections
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,252 @@
# QMSetup
**QMSetup** is a set of CMake Modules and Basic Libraries for C/C++ projects.
**This project is independent from Qt and other 3rdparty libraries.** Due to the fact that it encompasses some tools that need to be compiled, it's strongly not suggested to be included as a subproject.
## Features
### Helpful Functions
+ Path, Version functions
+ Target configuration functions
+ Qt related functions
### Generators
+ Generate Windows RC files, manifest files
+ Generate MacOS Bundle info files
+ Generate configuration header files
+ Generate Git information header files
### Filesystem Utilities
+ Reorganize header files
+ Copy files and directories after build
### Install Utilities
+ Deploy project dependencies and fix rpaths
### Extended Build Rules
+ Create translations with **Qt Linguist** tools
+ Create source files with **Protobuf** compiler
+ Create documentations with **Doxygen**
## Support Platforms
+ Microsoft Windows
+ Apple Macintosh
+ GNU/Linux
## Dependencies
### Required Packages
#### Windows
Windows deploy command acquires the shared library paths by reading the PE files and searching the specified paths so that it doesn't depend on `dumpbin` tool.
#### Unix
Unix deploy command acquires the shared library paths by running `ldd`/`otool` command and fixes the *rpath*s by runing the `patchelf`/`install_name_tool` command, make sure you have installed them.
```sh
sudo apt install patchelf
```
### Build System
+ C++ 17
+ CMake 3.19
### Open-Source Libraries
+ https://github.com/SineStriker/syscmdline
+ https://github.com/jothepro/doxygen-awesome-css
## Integrate
### Clone
Via Https
```sh
git clone --recursive https://github.com/stdware/qmsetup.git
```
Via SSH
```sh
git clone --recursive git@github.com:stdware/qmsetup.git
```
### Preinstall (Suggested)
#### Build & Install
```sh
cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DCMAKE_INSTALL_PREFIX=/path/to
cmake --build build --target all
cmake --build build --target install
```
#### Import
```sh
cmake -Dqmsetup_DIR=/path/to/lib/cmake/qmsetup ...
```
```cmake
find_package(qmsetup REQUIRED)
```
### Sub-project
It still needs to be installed, but the installation occurs during the CMake Configure phase and is executed only once.
```cmake
find_package(qmsetup QUIET)
if (NOT TARGET qmsetup::library)
# Modify this variable according to your project structure
set(_source_dir ${CMAKE_CURRENT_SOURCE_DIR}/qmsetup)
# Import install function
include("${_source_dir}/cmake/modules/private/InstallPackage.cmake")
# Install package in place
set(_package_path)
qm_install_package(qmsetup
SOURCE_DIR ${_source_dir}
BUILD_TYPE Release
RESULT_PATH _package_path
)
# Find package again
find_package(qmsetup REQUIRED PATHS ${_package_path})
# Update import path
set(qmsetup_DIR ${_package_path} CACHE PATH "" FORCE)
endif()
```
## Quick Start
### Examples
You can use the functions in this library to greatly simplify several kinds of common build rules.
#### Generate Configuration Header
```cmake
qm_import(Preprocess)
qm_add_definition(FOO false)
qm_add_definition(BAR 114514)
qm_add_definition(BAZ "ABC" STRING_LITERAL)
qm_generate_config(${CMAKE_BINARY_DIR}/conf.h)
```
#### Reorganize Include Directory
```cmake
qm_import(Preprocess)
qm_sync_include(src/core ${CMAKE_BINARY_DIR}/include/MyCore
INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR}/MyCore
)
```
#### Sync Resource Files After Build
```cmake
qm_import(Filesystem)
qm_add_copy_command(${PROJECT_NAME}
SOURCES
file.txt
dir_to_copy
dir_contents_to_copy/
DESTINATION .
)
```
#### Deploy Project And All Dependencies
```cmake
qm_import(Deploy)
qm_deploy_directory("${CMAKE_INSTALL_PREFIX}"
COMMENT "Deploy project spectacularly"
PLUGINS "iconengines/qsvgicon" "bearer/qgenericbearer"
QML Qt QtQml
PLUGIN_DIR share/plugins
QML_DIR share/qml
)
```
#### Add Qt Translations
```cmake
qm_import(Translate)
qm_find_qt(LinguistTools)
qm_add_translation(${PROJECT_NAME}_translations
LOCALES ja_JP zh_CN zh_TW
PREFIX ${PROJECT_NAME}
TARGETS ${PROJECT_NAME}
TS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/translations
QM_DIR ${CMAKE_CURRENT_BINARY_DIR}/translations
)
```
#### Generate Protubuf Source Files
```cmake
qm_import(Protobuf)
find_package(Protobuf REQUIRED)
qm_create_protobuf(_proto_src
INPUT a.proto b.proto
INCLUDE_DIRECTORIES src/proto
OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/proto
)
target_sources(${PROJECT_NAME} PUBLIC ${_proto_src})
```
#### Generate Doxygen HTML Documentations
```cmake
qm_import(Doxygen)
find_package(Doxygen REQUIRED)
qm_setup_doxygen(${PROJECT_NAME}_RunDoxygen
NAME ${PROJECT_NAME}
DESCRIPTION "my project"
MDFILE "${CMAKE_SOURCE_DIR}/README.md"
OUTPUT_DIR "${CMAK_BINARY_DIR}/doc"
INPUT src
TARGETS ${PROJECT_NAME}
DEPENDS ${PROJECT_NAME}
NO_EXPAND_MACROS
Q_OBJECT
Q_GADGET
Q_DECLARE_TR_FUNCTIONS
COMPILE_DEFINITIONS
Q_SIGNALS=Q_SIGNALS
Q_SLOTS=Q_SLOTS
GENERATE_TAGFILE "${PROJECT_NAME}_tagfile.xml"
INSTALL_DIR "doc"
)
```
### Find Modules
Use `qm_find_package` to find supported third-party packages.
+ YY-Thunks: https://github.com/Chuyu-Team/YY-Thunks
+ VC-LTL5: https://github.com/Chuyu-Team/VC-LTL5
### Detailed Documentations
+ [Core Command](docs/core-command.md)
The CMake Modules documentations is provided in the comments.
See `examples` to get detailed use cases.
## Contributors
+ [SineStriker](https://github.com/SineStriker)
+ [wangwenx190](https://github.com/wangwenx190)
+ [RigoLigoRLC](https://github.com/RigoLigoRLC)
+ [CrSjimo](https://github.com/CrSjimo)
## License
QMSetup is licensed under the MIT License.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,157 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 - 2023 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
class DoxygenAwesomeDarkModeToggle extends HTMLElement {
// SVG icons from https://fonts.google.com/icons
// Licensed under the Apache 2.0 license:
// https://www.apache.org/licenses/LICENSE-2.0.html
static lightModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FCBF00"><rect fill="none" height="24" width="24"/><circle cx="12" cy="12" opacity=".3" r="3"/><path d="M12,9c1.65,0,3,1.35,3,3s-1.35,3-3,3s-3-1.35-3-3S10.35,9,12,9 M12,7c-2.76,0-5,2.24-5,5s2.24,5,5,5s5-2.24,5-5 S14.76,7,12,7L12,7z M2,13l2,0c0.55,0,1-0.45,1-1s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S1.45,13,2,13z M20,13l2,0c0.55,0,1-0.45,1-1 s-0.45-1-1-1l-2,0c-0.55,0-1,0.45-1,1S19.45,13,20,13z M11,2v2c0,0.55,0.45,1,1,1s1-0.45,1-1V2c0-0.55-0.45-1-1-1S11,1.45,11,2z M11,20v2c0,0.55,0.45,1,1,1s1-0.45,1-1v-2c0-0.55-0.45-1-1-1C11.45,19,11,19.45,11,20z M5.99,4.58c-0.39-0.39-1.03-0.39-1.41,0 c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0s0.39-1.03,0-1.41L5.99,4.58z M18.36,16.95 c-0.39-0.39-1.03-0.39-1.41,0c-0.39,0.39-0.39,1.03,0,1.41l1.06,1.06c0.39,0.39,1.03,0.39,1.41,0c0.39-0.39,0.39-1.03,0-1.41 L18.36,16.95z M19.42,5.99c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06c-0.39,0.39-0.39,1.03,0,1.41 s1.03,0.39,1.41,0L19.42,5.99z M7.05,18.36c0.39-0.39,0.39-1.03,0-1.41c-0.39-0.39-1.03-0.39-1.41,0l-1.06,1.06 c-0.39,0.39-0.39,1.03,0,1.41s1.03,0.39,1.41,0L7.05,18.36z"/></svg>`
static darkModeIcon = `<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="24px" viewBox="0 0 24 24" width="24px" fill="#FE9700"><rect fill="none" height="24" width="24"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27 C17.45,17.19,14.93,19,12,19c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z" opacity=".3"/><path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"/></svg>`
static title = "Toggle Light/Dark Mode"
static prefersLightModeInDarkModeKey = "prefers-light-mode-in-dark-mode"
static prefersDarkModeInLightModeKey = "prefers-dark-mode-in-light-mode"
static _staticConstructor = function() {
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.userPreference)
// Update the color scheme when the browsers preference changes
// without user interaction on the website.
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
})
// Update the color scheme when the tab is made visible again.
// It is possible that the appearance was changed in another tab
// while this tab was in the background.
document.addEventListener("visibilitychange", visibilityState => {
if (document.visibilityState === 'visible') {
DoxygenAwesomeDarkModeToggle.onSystemPreferenceChanged()
}
});
}()
static init() {
$(function() {
$(document).ready(function() {
const toggleButton = document.createElement('doxygen-awesome-dark-mode-toggle')
toggleButton.title = DoxygenAwesomeDarkModeToggle.title
toggleButton.updateIcon()
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => {
toggleButton.updateIcon()
})
document.addEventListener("visibilitychange", visibilityState => {
if (document.visibilityState === 'visible') {
toggleButton.updateIcon()
}
});
$(document).ready(function(){
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
})
$(window).resize(function(){
document.getElementById("MSearchBox").parentNode.appendChild(toggleButton)
})
})
})
}
constructor() {
super();
this.onclick=this.toggleDarkMode
}
/**
* @returns `true` for dark-mode, `false` for light-mode system preference
*/
static get systemPreference() {
return window.matchMedia('(prefers-color-scheme: dark)').matches
}
/**
* @returns `true` for dark-mode, `false` for light-mode user preference
*/
static get userPreference() {
return (!DoxygenAwesomeDarkModeToggle.systemPreference && localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)) ||
(DoxygenAwesomeDarkModeToggle.systemPreference && !localStorage.getItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey))
}
static set userPreference(userPreference) {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = userPreference
if(!userPreference) {
if(DoxygenAwesomeDarkModeToggle.systemPreference) {
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey, true)
} else {
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey)
}
} else {
if(!DoxygenAwesomeDarkModeToggle.systemPreference) {
localStorage.setItem(DoxygenAwesomeDarkModeToggle.prefersDarkModeInLightModeKey, true)
} else {
localStorage.removeItem(DoxygenAwesomeDarkModeToggle.prefersLightModeInDarkModeKey)
}
}
DoxygenAwesomeDarkModeToggle.onUserPreferenceChanged()
}
static enableDarkMode(enable) {
if(enable) {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = true
document.documentElement.classList.add("dark-mode")
document.documentElement.classList.remove("light-mode")
} else {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = false
document.documentElement.classList.remove("dark-mode")
document.documentElement.classList.add("light-mode")
}
}
static onSystemPreferenceChanged() {
DoxygenAwesomeDarkModeToggle.darkModeEnabled = DoxygenAwesomeDarkModeToggle.userPreference
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
}
static onUserPreferenceChanged() {
DoxygenAwesomeDarkModeToggle.enableDarkMode(DoxygenAwesomeDarkModeToggle.darkModeEnabled)
}
toggleDarkMode() {
DoxygenAwesomeDarkModeToggle.userPreference = !DoxygenAwesomeDarkModeToggle.userPreference
this.updateIcon()
}
updateIcon() {
if(DoxygenAwesomeDarkModeToggle.darkModeEnabled) {
this.innerHTML = DoxygenAwesomeDarkModeToggle.darkModeIcon
} else {
this.innerHTML = DoxygenAwesomeDarkModeToggle.lightModeIcon
}
}
}
customElements.define("doxygen-awesome-dark-mode-toggle", DoxygenAwesomeDarkModeToggle);

View File

@@ -0,0 +1,40 @@
/**
Doxygen Awesome
https://github.com/jothepro/doxygen-awesome-css
MIT License
Copyright (c) 2021 - 2023 jothepro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
@media screen and (min-width: 768px) {
#MSearchBox {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - var(--searchbar-height) - 1px);
}
#MSearchField {
width: calc(var(--side-nav-fixed-width) - calc(2 * var(--spacing-medium)) - 66px - var(--searchbar-height));
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
table.memname tr {
float: left;
}
.memname td {
padding: 0;
}
.memname tr:last-child:not(:first-child) {
margin-left: -0.75em
}
html {
--external-link-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%231779c4" d="M10.12,7.5H9.38A.38.38,0,0,0,9,7.88V10.5H1.5V3H4.88a.38.38,0,0,0,.37-.38V1.88a.38.38,0,0,0-.37-.38H1.12A1.12,1.12,0,0,0,0,2.62v8.26A1.12,1.12,0,0,0,1.12,12H9.38a1.12,1.12,0,0,0,1.12-1.12h0v-3A.38.38,0,0,0,10.12,7.5ZM11.44,0h-3A.56.56,0,0,0,8,1l.84.84L3.16,7.51a.56.56,0,0,0,0,.79h0l.54.53a.56.56,0,0,0,.79,0h0L10.2,3.12,11,4a.56.56,0,0,0,1-.4v-3A.56.56,0,0,0,11.44,0Z"/></svg>')
}
@media (prefers-color-scheme: dark) {
html:not(.light-mode) {
--external-link-icon: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 12 12"><path fill="%231982d2" d="M10.12,7.5H9.38A.38.38,0,0,0,9,7.88V10.5H1.5V3H4.88a.38.38,0,0,0,.37-.38V1.88a.38.38,0,0,0-.37-.38H1.12A1.12,1.12,0,0,0,0,2.62v8.26A1.12,1.12,0,0,0,1.12,12H9.38a1.12,1.12,0,0,0,1.12-1.12h0v-3A.38.38,0,0,0,10.12,7.5ZM11.44,0h-3A.56.56,0,0,0,8,1l.84.84L3.16,7.51a.56.56,0,0,0,0,.79h0l.54.53a.56.56,0,0,0,.79,0h0L10.2,3.12,11,4a.56.56,0,0,0,1-.4v-3A.56.56,0,0,0,11.44,0Z"/></svg>')
}
}
a[href^='http://'], a[href^='https://'] {
background-image: var(--external-link-icon);
background-repeat: no-repeat;
background-position: center right;
background-size: 0.75em;
padding-right: 0.875em;
}

View File

@@ -0,0 +1,90 @@
<!-- HTML header for doxygen 1.9.8-->
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "https://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="$langISO">
<head>
<meta http-equiv="Content-Type" content="text/xhtml;charset=UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=11" />
<meta name="generator" content="Doxygen $doxygenversion" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!--BEGIN PROJECT_NAME-->
<title>$projectname: $title</title>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<title>$title</title>
<!--END !PROJECT_NAME-->
<link href="$relpath^tabs.css" rel="stylesheet" type="text/css" />
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN FULL_SIDEBAR-->
<script type="text/javascript">
var page_layout = 1;
</script>
<!--END FULL_SIDEBAR-->
<!--END DISABLE_INDEX-->
<script type="text/javascript" src="$relpath^jquery.js"></script>
<script type="text/javascript" src="$relpath^dynsections.js"></script>
$treeview $search $mathjax $darkmode
<link href="$relpath^$stylesheet" rel="stylesheet" type="text/css" /> $extrastylesheet
<script type="text/javascript" src="$relpath^doxygen-awesome-darkmode-toggle.js"></script>
<script type="text/javascript">
DoxygenAwesomeDarkModeToggle.init()
</script>
</head>
<body>
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN FULL_SIDEBAR-->
<div id="side-nav" class="ui-resizable side-nav-resizable">
<!-- do not remove this div, it is closed by doxygen! -->
<!--END FULL_SIDEBAR-->
<!--END DISABLE_INDEX-->
<div id="top">
<!-- do not remove this div, it is closed by doxygen! -->
<!--BEGIN TITLEAREA-->
<div id="titlearea">
<table cellspacing="0" cellpadding="0">
<tbody>
<tr id="projectrow">
<!--BEGIN PROJECT_LOGO-->
<td id="projectlogo"><img alt="Logo" src="$relpath^$projectlogo" /></td>
<!--END PROJECT_LOGO-->
<!--BEGIN PROJECT_NAME-->
<td id="projectalign">
<div id="projectname">$projectname
<!--BEGIN PROJECT_NUMBER--><span id="projectnumber">&#160;$projectnumber</span>
<!--END PROJECT_NUMBER-->
</div>
<!--BEGIN PROJECT_BRIEF-->
<div id="projectbrief">$projectbrief</div>
<!--END PROJECT_BRIEF-->
</td>
<!--END PROJECT_NAME-->
<!--BEGIN !PROJECT_NAME-->
<!--BEGIN PROJECT_BRIEF-->
<td>
<div id="projectbrief">$projectbrief</div>
</td>
<!--END PROJECT_BRIEF-->
<!--END !PROJECT_NAME-->
<!--BEGIN DISABLE_INDEX-->
<!--BEGIN SEARCHENGINE-->
<!--BEGIN !FULL_SIDEBAR-->
<td>$searchbox</td>
<!--END !FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
<!--END DISABLE_INDEX-->
</tr>
<!--BEGIN SEARCHENGINE-->
<!--BEGIN FULL_SIDEBAR-->
<tr>
<td colspan="2">$searchbox</td>
</tr>
<!--END FULL_SIDEBAR-->
<!--END SEARCHENGINE-->
</tbody>
</table>
</div>
<!--END TITLEAREA-->
<!-- end header part -->

View File

@@ -0,0 +1,102 @@
# https://github.com/Chuyu-Team/VC-LTL5
cmake_minimum_required(VERSION 3.13)
if(NOT MSVC OR DEFINED __VC_LTL_CMAKE_INCLUDE_GUARD)
return()
endif()
set(__VC_LTL_CMAKE_INCLUDE_GUARD 1)
#
# VC-LTL自动化加载配置建议你将此文件单独复制到你的工程再使用该文件能自动识别当前环境是否存在VC-LTL并且自动应用。
#
# 使用方法:
# 1. 在“CMakeLists.txt” 添加 “include("VC-LTL helper for cmake.cmake")”。
#
# VC-LTL默认搜索顺序
# 1. “VC-LTL helper for cmake.cmake”所在根目录即 ${CMAKE_CURRENT_LIST_DIR}
# 2. 当前CMake根目录即 ${CMAKE_CURRENT_SOURCE_DIR}/VC-LTL
# 3. 当前项目根目录,即 ${PROJECT_SOURCE_DIR}/VC-LTL
# 4. 当前CMake父目录即 ${CMAKE_CURRENT_SOURCE_DIR}/../VC-LTL
# 5. 当前项目根目录,即 ${PROJECT_SOURCE_DIR}/../VC-LTL
# 6. 注册表HKEY_CURRENT_USER\Code\VC-LTL@Root
#
# 把VC-LTL放在其中一个位置即可VC-LTL就能被自动引用。
#
# 如果你对默认搜索顺序不满,你可以修改此文件。你也可以直接指定${VC_LTL_Root}宏更加任性的去加载VC-LTL。
#
#####################################################################VC-LTL设置#####################################################################
#控制最小兼容系统版本目前可用版本为5.1.2600.0 6.0.6000.0(默认) 6.2.9200.0 10.0.10240.0 10.0.19041.0
#注意VC-LTL依赖YY-Thunks否则可能无法兼容早期系统。如果需要支持Windows XP该值必须为5.1.2600.0。
if(NOT DEFINED WindowsTargetPlatformMinVersion)
set(WindowsTargetPlatformMinVersion "10.0.19041.0" CACHE STRING "" FORCE)
endif()
#VC-LTL使用的CRT模式SupportLTL可能值为
# * false禁用VC_LTL
# * true默认值让VC-LTL自动适应。当最小兼容版本>=10.0时使用ucrt模式其他系统使用msvcrt模式。
# * msvcrt使用msvcrt.dll作为CRT。注意msvcrt模式可能不完全支持所有ucrt的新功能。比如setloacl不支持UTF8。
# * ucrt使用ucrtbase.dll作为CRT。注意早期系统可能需要下载VC-LTL.Redist.Dlls.zip感谢msvcr14x项目提供兼容XP系统的ucrtbase.dll。
#如果需要兼容XP时也使用ucrt请指定SupportLTL=ucrt。
#set(SupportLTL "ucrt")
#(PR#70 引入)默认关开启后将使用cmake `INTERFACE`能力,然后单独`target_link_directories(工程名称 VC_LTL)` 即可引用
#option(VC_LTL_EnableCMakeInterface "VC_LTL_EnableCMakeInterface" on)
####################################################################################################################################################
if(NOT VC_LTL_Root)
if(EXISTS ${CMAKE_CURRENT_LIST_DIR}/_msvcrt.h)
set(VC_LTL_Root ${CMAKE_CURRENT_LIST_DIR})
endif()
endif()
if(NOT VC_LTL_Root)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/VC-LTL/_msvcrt.h)
set(VC_LTL_Root ${CMAKE_CURRENT_SOURCE_DIR}/VC-LTL)
endif()
endif()
if(NOT VC_LTL_Root)
if(EXISTS ${PROJECT_SOURCE_DIR}/VC-LTL/_msvcrt.h)
set(VC_LTL_Root ${PROJECT_SOURCE_DIR}/VC-LTL)
endif()
endif()
if(NOT VC_LTL_Root)
if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../VC-LTL/_msvcrt.h)
set(VC_LTL_Root ${CMAKE_CURRENT_SOURCE_DIR}/../VC-LTL)
endif()
endif()
if(NOT VC_LTL_Root)
if(EXISTS ${PROJECT_SOURCE_DIR}/../VC-LTL/_msvcrt.h)
set(VC_LTL_Root ${PROJECT_SOURCE_DIR}/../VC-LTL)
endif()
endif()
if(NOT VC_LTL_Root)
EXECUTE_PROCESS(COMMAND reg query "HKEY_CURRENT_USER\\Code\\VC-LTL" -v "Root"
OUTPUT_VARIABLE FOUND_FILE
ERROR_VARIABLE ERROR_INFO
)
string(REGEX MATCH "[a-zA-Z]:\\\\.+\\\\"
FOUND_LTL
${FOUND_FILE})
if (NOT ${FOUND_LTL} STREQUAL "")
set(VC_LTL_Root ${FOUND_LTL})
endif()
if(NOT DEFINED VC_LTL_Root)
string(REGEX MATCH "\\\\\\\\.+\\\\" FOUND_LTL ${FOUND_FILE})
if (NOT ${FOUND_LTL} STREQUAL "")
set(VC_LTL_Root ${FOUND_LTL})
endif()
endif()
endif()
if(VC_LTL_Root)
include("${VC_LTL_Root}\\config\\config.cmake")
endif()

View File

@@ -0,0 +1,102 @@
#[[
MIT License
Copyright (C) 2023 by wangwenx190 (Yuhang Zhao)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]
# https://github.com/Chuyu-Team/YY-Thunks
if(NOT MSVC OR DEFINED __YY_THUNKS_CMAKE_INCLUDE_GUARD)
return()
endif()
set(__YY_THUNKS_CMAKE_INCLUDE_GUARD 1)
if(NOT DEFINED YYTHUNKS_TARGET_OS)
set(YYTHUNKS_TARGET_OS "Vista" CACHE STRING "Supported values: Vista, WinXP" FORCE)
endif()
set(__yy_thunks_dir "")
if(DEFINED ENV{YYTHUNKS_INSTALL_DIR})
set(__env "$ENV{YYTHUNKS_INSTALL_DIR}")
if(NOT "x${__env}" STREQUAL "x" AND EXISTS "${__env}")
set(__yy_thunks_dir "${__env}")
endif()
endif()
if("x${__yy_thunks_dir}" STREQUAL "x")
set(__yy_thunks_reg "")
cmake_host_system_information(RESULT __yy_thunks_reg
QUERY WINDOWS_REGISTRY "HKCU/Code/YY-Thunks"
VALUE "Root")
if(NOT "x${__yy_thunks_reg}" STREQUAL "x" AND EXISTS "${__yy_thunks_reg}")
set(__yy_thunks_dir "${__yy_thunks_reg}")
elseif(EXISTS "${CMAKE_CURRENT_LIST_DIR}/YY-Thunks")
set(__yy_thunks_dir "${CMAKE_CURRENT_LIST_DIR}/YY-Thunks")
elseif(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/YY-Thunks")
set(__yy_thunks_dir "${CMAKE_CURRENT_SOURCE_DIR}/YY-Thunks")
elseif(EXISTS "${PROJECT_SOURCE_DIR}/YY-Thunks")
set(__yy_thunks_dir "${PROJECT_SOURCE_DIR}/YY-Thunks")
elseif(EXISTS "${CMAKE_SOURCE_DIR}/YY-Thunks")
set(__yy_thunks_dir "${CMAKE_SOURCE_DIR}/YY-Thunks")
elseif(EXISTS "${CMAKE_SOURCE_DIR}/../YY-Thunks")
set(__yy_thunks_dir "${CMAKE_SOURCE_DIR}/../YY-Thunks")
endif()
endif()
if(NOT "x${__yy_thunks_dir}" STREQUAL "x")
cmake_path(NORMAL_PATH __yy_thunks_dir OUTPUT_VARIABLE __yy_thunks_dir)
endif()
if(NOT "x${__yy_thunks_dir}" STREQUAL "x" AND EXISTS "${__yy_thunks_dir}")
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
set(__yy_thunks_arch x64)
else()
set(__yy_thunks_arch x86)
endif()
set(__yy_thunks_obj "${__yy_thunks_dir}/objs/${__yy_thunks_arch}/YY_Thunks_for_${YYTHUNKS_TARGET_OS}.obj")
cmake_path(NORMAL_PATH __yy_thunks_obj OUTPUT_VARIABLE __yy_thunks_obj)
if(EXISTS "${__yy_thunks_obj}")
set(YYTHUNKS_FOUND TRUE CACHE BOOL "" FORCE)
set(YYTHUNKS_INSTALL_DIR "${__yy_thunks_dir}" CACHE STRING "" FORCE)
set(YYTHUNKS_ARCH "${__yy_thunks_arch}" CACHE STRING "" FORCE)
set(YYTHUNKS_OBJ_FILE "YY_Thunks_for_${YYTHUNKS_TARGET_OS}.obj" CACHE STRING "" FORCE)
set(YYTHUNKS_OBJ_PATH "${__yy_thunks_obj}" CACHE STRING "" FORCE)
add_link_options("${__yy_thunks_obj}")
message("###################################################################################################")
message("# #")
message("# ██ ██ ██ ██ ████████ ██ ██ ██ ██ ███ ██ ██ ██ ███████ #")
message("# ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ██ ██ ██ ██ #")
message("# ████ ████ █████ ██ ███████ ██ ██ ██ ██ ██ █████ ███████ #")
message("# ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ #")
message("# ██ ██ ██ ██ ██ ██████ ██ ████ ██ ██ ███████ #")
message("# #")
message("###################################################################################################")
message("")
message(" YY-Thunks install dir :" ${__yy_thunks_dir})
message(" YY-Thunks target platform :" ${YYTHUNKS_TARGET_OS})
message(" YY-Thunks obj file path :" ${__yy_thunks_obj})
message("")
else()
message(WARNING "YY-Thunks's obj file is missing!")
endif()
else()
message(WARNING "Can't locate the YY-Thunks installation directory!")
endif()

View File

@@ -0,0 +1,238 @@
include_guard(DIRECTORY)
#[[
Disable all possible warnings from the compiler.
qm_compiler_no_warnings()
]] #
macro(qm_compiler_no_warnings)
foreach(__lang C CXX)
if(NOT "x${CMAKE_${__lang}_FLAGS}" STREQUAL "x")
if(MSVC)
string(REGEX REPLACE " [/-]W[01234] " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
else()
string(REGEX REPLACE " -W(all)?(extra)? " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
string(REGEX REPLACE " -[W]?pedantic " " " CMAKE_${__lang}_FLAGS ${CMAKE_${__lang}_FLAGS})
endif()
endif()
string(APPEND CMAKE_${__lang}_FLAGS " -w ")
endforeach()
if(MSVC)
add_compile_definitions(-D_CRT_NON_CONFORMING_SWPRINTFS)
add_compile_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE)
add_compile_definitions(-D_CRT_NONSTDC_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE)
add_compile_definitions(-D_SCL_SECURE_NO_WARNINGS -D_SCL_SECURE_NO_DEPRECATE)
add_compile_definitions(-D_SILENCE_ALL_MS_EXT_DEPRECATION_WARNINGS)
else()
foreach(__lang C CXX)
string(APPEND CMAKE_${__lang}_FLAGS " -fpermissive ")
endforeach()
endif()
endmacro()
#[[
Enable all possible warnings from the compiler.
qm_compiler_max_warnings()
]] #
function(qm_compiler_max_warnings)
if(MSVC)
add_compile_options(-W4)
elseif("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
add_compile_options(-Weverything)
else()
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
endfunction()
#[[
Treat all warnings as errors.
qm_compiler_warnings_are_errors()
]] #
function(qm_compiler_warnings_are_errors)
if(MSVC)
add_compile_options(-WX)
else()
add_compile_options(-Werror)
endif()
endfunction()
#[[
Prevent the compiler from receiving unknown parameters.
qm_compiler_no_unknown_options()
]] #
function(qm_compiler_no_unknown_options)
if(MSVC)
if(MSVC_VERSION GREATER_EQUAL 1930) # Visual Studio 2022 version 17.0
add_compile_options(-options:strict)
endif()
add_link_options(-WX)
endif()
endfunction()
#[[
Remove all unused code from the final binary.
qm_compiler_eliminate_dead_code()
]] #
function(qm_compiler_eliminate_dead_code)
if(MSVC)
add_compile_options(-Gw -Gy -Zc:inline)
add_link_options(-OPT:REF -OPT:ICF -OPT:LBR)
else()
add_compile_options(-ffunction-sections -fdata-sections)
if(APPLE)
add_link_options(-Wl,-dead_strip)
else()
add_link_options(-Wl,--as-needed -Wl,--strip-all -Wl,--gc-sections)
endif()
if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
add_link_options(-Wl,--icf=all)
endif()
endif()
endfunction()
#[[
Only export symbols which are marked to be exported, just like MSVC.
qm_compiler_dont_export_by_default()
]] #
macro(qm_compiler_dont_export_by_default)
set(CMAKE_C_VISIBILITY_PRESET "hidden")
set(CMAKE_CXX_VISIBILITY_PRESET "hidden")
set(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
endmacro()
#[[
Enable all possible security issue mitigations from your compiler.
qm_compiler_enable_secure_code()
]] #
macro(qm_compiler_enable_secure_code)
if(MSVC)
add_compile_options(-GS -sdl -guard:cf)
add_link_options(-DYNAMICBASE -FIXED:NO -NXCOMPAT -GUARD:CF)
if(CMAKE_SIZEOF_VOID_P EQUAL 4)
add_link_options(-SAFESEH)
endif()
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
add_link_options(-HIGHENTROPYVA)
endif()
if(MSVC_VERSION GREATER_EQUAL 1920) # Visual Studio 2019 version 16.0
add_link_options(-CETCOMPAT)
endif()
if(MSVC_VERSION GREATER_EQUAL 1925) # Visual Studio 2019 version 16.5
add_compile_options(-Qspectre-load)
elseif(MSVC_VERSION GREATER_EQUAL 1912) # Visual Studio 2017 version 15.5
add_compile_options(-Qspectre)
endif()
if(MSVC_VERSION GREATER_EQUAL 1927) # Visual Studio 2019 version 16.7
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
add_compile_options(-guard:ehcont)
add_link_options(-guard:ehcont)
endif()
endif()
if(MSVC_VERSION GREATER_EQUAL 1930) # Visual Studio 2022 version 17.0
add_compile_options(-Qspectre-jmp)
endif()
elseif(MINGW)
if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
add_compile_options(-mguard=cf)
add_link_options(-mguard=cf)
else()
endif()
else()
add_compile_options(-mshstk -ftrivial-auto-var-init=pattern
-fstack-protector-strong -fstack-clash-protection
-fcf-protection=full)
add_link_options(-Wl,-z,relro,-z,now)
if("x${CMAKE_CXX_COMPILER_ID}" STREQUAL "xClang")
add_compile_options(-mretpoline -mspeculative-load-hardening)
if(NOT APPLE)
add_compile_options(-fsanitize=cfi -fsanitize-cfi-cross-dso)
endif()
endif()
endif()
endmacro()
#[[
Enable all possible Qt coding style policies.
qm_compiler_enable_strict_qt(
TARGETS <target1> <target2> ... <targetN>
[NO_DEPRECATED_API]
[ALLOW_KEYWORD]
[ALLOW_UNSAFE_FLAGS]
)
NO_DEPRECATED_API: Disable the use of any deprecated Qt APIs. Only has effect since Qt6.
ALLOW_KEYWORD: Allow the use of the traditional Qt keywords such as "signal" "slot" "emit".
ALLOW_UNSAFE_FLAGS: Allow the use of the unsafe QFlags (unsafe: can be implicitly cast to and from "int").
]] #
function(qm_compiler_enable_strict_qt)
cmake_parse_arguments(arg "NO_DEPRECATED_API;ALLOW_KEYWORD;ALLOW_UNSAFE_FLAGS" "" "TARGETS" ${ARGN})
if(NOT arg_TARGETS)
message(AUTHOR_WARNING "qm_compiler_enable_strict_qt: you need to specify at least one target!")
return()
endif()
if(arg_UNPARSED_ARGUMENTS)
message(AUTHOR_WARNING "qm_compiler_enable_strict_qt: Unrecognized arguments: ${arg_UNPARSED_ARGUMENTS}")
endif()
foreach(_target IN LISTS arg_TARGETS)
if(NOT TARGET ${_target})
message(AUTHOR_WARNING "qm_compiler_enable_strict_qt: ${_target} is not a valid CMake target!")
continue()
endif()
target_compile_definitions(${_target} PRIVATE
QT_NO_CAST_TO_ASCII
QT_NO_CAST_FROM_ASCII
QT_NO_CAST_FROM_BYTEARRAY
QT_NO_URL_CAST_FROM_STRING
QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
QT_NO_JAVA_STYLE_ITERATORS
QT_NO_FOREACH QT_NO_QFOREACH
QT_NO_AS_CONST QT_NO_QASCONST
QT_NO_EXCHANGE QT_NO_QEXCHANGE
QT_NO_QPAIR
QT_NO_INTEGRAL_STRINGS
QT_NO_USING_NAMESPACE
QT_NO_CONTEXTLESS_CONNECT
QT_EXPLICIT_QFILE_CONSTRUCTION_FROM_PATH
QT_USE_NODISCARD_FILE_OPEN
QT_USE_QSTRINGBUILDER
QT_USE_FAST_OPERATOR_PLUS
QT_DEPRECATED_WARNINGS # Have no effect since 5.13
QT_DEPRECATED_WARNINGS_SINCE=0x0A0000 # Deprecated since 6.5
QT_WARN_DEPRECATED_UP_TO=0x0A0000 # Available since 6.5
)
if(arg_NO_DEPRECATED_API)
target_compile_definitions(${_target} PRIVATE
QT_DISABLE_DEPRECATED_BEFORE=0x0A0000 # Deprecated since 6.5
QT_DISABLE_DEPRECATED_UP_TO=0x0A0000 # Available since 6.5
)
endif()
# On Windows enabling this flag requires us re-compile Qt with this flag enabled,
# so only enable it on non-Windows platforms.
if(NOT WIN32)
target_compile_definitions(${_target} PRIVATE
QT_STRICT_ITERATORS
)
endif()
# We handle this flag specially because some Qt headers may still use the
# traditional keywords (especially some private headers).
if(NOT arg_ALLOW_KEYWORD)
target_compile_definitions(${_target} PRIVATE
QT_NO_KEYWORDS
)
endif()
# We handle this flag specially because some Qt headers may still use the
# unsafe flags (especially some QtQuick headers).
if(NOT arg_ALLOW_UNSAFE_FLAGS)
target_compile_definitions(${_target} PRIVATE
QT_TYPESAFE_FLAGS
)
endif()
endforeach()
endfunction()

View File

@@ -0,0 +1,421 @@
#[[
Warning: This module depends on `qmcorecmd` after installation.
]] #
if(NOT QMSETUP_CORECMD_EXECUTABLE)
message(FATAL_ERROR "QMSETUP_CORECMD_EXECUTABLE not defined. Add find_package(qmsetup) to CMake first.")
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS)
set(QMSETUP_APPLOCAL_DEPS_PATHS)
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS_DEBUG)
set(QMSETUP_APPLOCAL_DEPS_PATHS_DEBUG ${QMSETUP_APPLOCAL_DEPS_PATHS})
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS_RELEASE)
set(QMSETUP_APPLOCAL_DEPS_PATHS_RELEASE ${QMSETUP_APPLOCAL_DEPS_PATHS})
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS_RELWITHDEBINFO)
set(QMSETUP_APPLOCAL_DEPS_PATHS_RELWITHDEBINFO ${QMSETUP_APPLOCAL_DEPS_PATHS_RELEASE})
endif()
if(NOT DEFINED QMSETUP_APPLOCAL_DEPS_PATHS_MINSIZEREL)
set(QMSETUP_APPLOCAL_DEPS_PATHS_MINSIZEREL ${QMSETUP_APPLOCAL_DEPS_PATHS_RELEASE})
endif()
include_guard(DIRECTORY)
#[[
Record searching paths for Windows Executables, must be called before calling `qm_win_applocal_deps`
or `qm_deploy_directory` if your project supports Windows.
WARNING: This function is deprecated.
qm_win_record_deps(<target>)
]] #
function(qm_win_record_deps _target)
if(NOT WIN32)
return()
endif()
set(_paths)
get_target_property(_link_libraries ${_target} LINK_LIBRARIES)
foreach(_item IN LISTS _link_libraries)
if(NOT TARGET ${_item})
continue()
endif()
get_target_property(_imported ${_item} IMPORTED)
if(_imported)
get_target_property(_path ${_item} LOCATION)
if(NOT _path OR NOT ${_path} MATCHES "\\.dll$")
continue()
endif()
set(_path "$<TARGET_PROPERTY:${_item},LOCATION_$<CONFIG>>")
else()
get_target_property(_type ${_item} TYPE)
if(NOT ${_type} MATCHES "SHARED_LIBRARY")
continue()
endif()
set(_path "$<TARGET_FILE:${_item}>")
endif()
list(APPEND _paths ${_path})
endforeach()
if(NOT _paths)
return()
endif()
set(_deps_file "${CMAKE_CURRENT_BINARY_DIR}/${_target}_deps_$<CONFIG>.txt")
file(GENERATE OUTPUT ${_deps_file} CONTENT "$<JOIN:${_paths},\n>")
set_target_properties(${_target} PROPERTIES QMSETUP_DEPENDENCIES_FILE ${_deps_file})
endfunction()
#[[
Automatically copy dependencies for Windows Executables after build.
qm_win_applocal_deps(<target>
[CUSTOM_TARGET <target>]
[FORCE] [VERBOSE]
[EXTRA_SEARCHING_PATHS <path...>]
[EXTRA_TARGETS <target...>]
[OUTPUT_DIR <dir>]
[EXCLUDE <pattern...>]
)
]] #
function(qm_win_applocal_deps _target)
if(NOT WIN32)
return()
endif()
set(options FORCE VERBOSE)
set(oneValueArgs TARGET CUSTOM_TARGET OUTPUT_DIR)
set(multiValueArgs EXTRA_SEARCHING_PATHS EXTRA_TARGETS EXCLUDE)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Get output directory and deploy target
set(_out_dir)
set(_deploy_target)
if(FUNC_CUSTOM_TARGET)
set(_deploy_target ${FUNC_CUSTOM_TARGET})
if(NOT TARGET ${_deploy_target})
add_custom_target(${_deploy_target})
endif()
else()
set(_deploy_target ${_target})
endif()
if(FUNC_OUTPUT_DIR)
set(_out_dir ${FUNC_OUTPUT_DIR})
else()
set(_out_dir "$<TARGET_FILE_DIR:${_target}>")
endif()
if(NOT _out_dir)
message(FATAL_ERROR "qm_win_applocal_deps: cannot determine output directory.")
endif()
# Get dep files
set(_dep_files)
_qm_win_get_all_dep_files(_dep_files ${_target})
foreach(_item IN LISTS FUNC_EXTRA_TARGETS)
_qm_win_get_all_dep_files(_dep_files ${_item})
endforeach()
# Prepare command
set(_args)
if(FUNC_FORCE)
list(APPEND _args -f)
endif()
if(FUNC_VERBOSE)
list(APPEND _args -V)
endif()
# Add extra searching paths
foreach(_item IN LISTS FUNC_EXTRA_SEARCHING_PATHS)
list(APPEND _args "-L${_item}")
endforeach()
# Add global extra searching paths
if(CMAKE_BUILD_TYPE)
string(TOUPPER ${CMAKE_BUILD_TYPE} _build_type_upper)
if(QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _args "-L${_item}")
endforeach()
elseif(QMSETUP_APPLOCAL_DEPS_PATHS)
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _args "-L${_item}")
endforeach()
endif()
else()
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _args "-L${_item}")
endforeach()
endif()
foreach(_item IN LISTS _dep_files)
list(APPEND _args "--linkdirs-file" "${_item}")
endforeach()
foreach(_item IN LISTS FUNC_EXCLUDE)
list(APPEND _args -e ${_item})
endforeach()
list(APPEND _args "$<TARGET_FILE:${_target}>")
add_custom_command(TARGET ${_deploy_target} POST_BUILD
COMMAND ${QMSETUP_CORECMD_EXECUTABLE} deploy ${_args}
WORKING_DIRECTORY ${_out_dir}
VERBATIM
)
endfunction()
#[[
Add deploy command when install project, not available in debug mode.
qm_deploy_directory(<install_dir>
[FORCE] [STANDARD] [VERBOSE]
[LIBRARY_DIR <dir>]
[EXTRA_LIBRARIES <path>...]
[EXTRA_PLUGIN_PATHS <path>...]
[EXTRA_SEARCHING_PATHS <path>...]
[PLUGINS <plugin>...]
[PLUGIN_DIR <dir>]
[QML <qml>...]
[QML_DIR <dir>]
[WIN_TARGETS <target>...]
[COMMENT <comment>]
)
PLUGINS: Qt plugins, in format of `<category>/<name>`
PLUGIN_DIR: Qt plugins destination
EXTRA_PLUGIN_PATHS: Extra Qt plugins searching paths
QML: Qt qml directories
QML_DIR: Qt qml destination
LIBRARY_DIR: Library destination
EXTRA_LIBRARIES Extra library names list to deploy
EXTRA_SEARCHING_PATHS: Extra library searching paths
]] #
function(qm_deploy_directory _install_dir)
set(options FORCE STANDARD VERBOSE)
set(oneValueArgs LIBRARY_DIR PLUGIN_DIR QML_DIR COMMENT)
set(multiValueArgs EXTRA_PLUGIN_PATHS PLUGINS QML WIN_TARGETS EXTRA_SEARCHING_PATHS EXTRA_LIBRARIES)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Get qmake
if((FUNC_PLUGINS OR FUNC_QML) AND NOT DEFINED QT_QMAKE_EXECUTABLE)
if(TARGET Qt${QT_VERSION_MAJOR}::qmake)
qm_get_executable_location(Qt${QT_VERSION_MAJOR}::qmake _qmake_path)
set(QT_QMAKE_EXECUTABLE "${_qmake_path}" CACHE FILEPATH "Path to qmake executable" FORCE)
elseif((FUNC_PLUGINS AND NOT FUNC_EXTRA_PLUGIN_PATHS) OR FUNC_QML)
message(FATAL_ERROR "qm_deploy_directory: qmake not defined. Add find_package(Qt5 COMPONENTS Core) to CMake to enable.")
endif()
endif()
# Set values
qm_set_value(_lib_dir FUNC_LIBRARY_DIR "${_install_dir}/${QMSETUP_SHARED_LIBRARY_CATEGORY}")
qm_set_value(_plugin_dir FUNC_PLUGIN_DIR "${_install_dir}/plugins")
qm_set_value(_qml_dir FUNC_QML_DIR "${_install_dir}/qml")
# Prepare commands
set(_args
-i "${_install_dir}"
-m "${QMSETUP_CORECMD_EXECUTABLE}"
--plugindir "${_plugin_dir}"
--libdir "${_lib_dir}"
--qmldir "${_qml_dir}"
)
set(_searching_paths)
if(QT_QMAKE_EXECUTABLE)
list(APPEND _args --qmake "${QT_QMAKE_EXECUTABLE}")
endif()
# Add Qt plugins
foreach(_item IN LISTS FUNC_PLUGINS)
list(APPEND _args --plugin "${_item}")
endforeach()
# Add QML modules
foreach(_item IN LISTS FUNC_QML)
list(APPEND _args --qml "${_item}")
endforeach()
# Add extra plugin paths
foreach(_item IN LISTS FUNC_EXTRA_PLUGIN_PATHS)
list(APPEND _args --extra "${_item}")
endforeach()
# Add extra searching paths
foreach(_item IN LISTS FUNC_EXTRA_SEARCHING_PATHS)
get_filename_component(_item ${_item} ABSOLUTE)
list(APPEND _searching_paths ${_item})
endforeach()
# Add global extra searching paths
if(CMAKE_BUILD_TYPE)
string(TOUPPER ${CMAKE_BUILD_TYPE} _build_type_upper)
if(QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS_${_build_type_upper})
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _searching_paths ${_item})
endforeach()
elseif(QMSETUP_APPLOCAL_DEPS_PATHS)
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _searching_paths ${_item})
endforeach()
endif()
else()
foreach(_item IN LISTS QMSETUP_APPLOCAL_DEPS_PATHS)
get_filename_component(_item ${_item} ABSOLUTE BASE_DIR ${CMAKE_SOURCE_DIR})
list(APPEND _searching_paths ${_item})
endforeach()
endif()
foreach(_item IN LISTS _searching_paths)
list(APPEND _args -L "${_item}")
endforeach()
if(WIN32)
set(_dep_files)
if(FUNC_WIN_TARGETS)
_qm_win_get_all_dep_files(_dep_files ${FUNC_WIN_TARGETS})
endif()
foreach(_item IN LISTS _dep_files)
list(APPEND _args "--linkdirs-file" "${_item}")
endforeach()
set(_script_quoted "cmd /c \"${QMSETUP_MODULES_DIR}/scripts/windeps.bat\"")
else()
set(_script_quoted "bash \"${QMSETUP_MODULES_DIR}/scripts/unixdeps.sh\"")
endif()
# Add extra libraries
foreach(_item IN LISTS _searching_paths)
foreach(_lib IN LISTS FUNC_EXTRA_LIBRARIES)
set(_path "${_item}/${_lib}")
if((EXISTS ${_path}) AND(NOT IS_DIRECTORY ${_path}))
list(APPEND _args --copy ${_path} ${_lib_dir})
endif()
endforeach()
endforeach()
# Add options
if(FUNC_FORCE)
list(APPEND _args "-f")
endif()
if(FUNC_STANDARD)
list(APPEND _args "-s")
endif()
if(FUNC_VERBOSE)
list(APPEND _args "-V")
endif()
set(_args_quoted)
foreach(_item IN LISTS _args)
set(_args_quoted "${_args_quoted}\"${_item}\" ")
endforeach()
set(_comment_code)
if(FUNC_COMMENT)
set(_comment_code "message(STATUS \"${FUNC_COMMENT}\")")
endif()
# Add install command
install(CODE "
${_comment_code}
execute_process(
COMMAND ${_script_quoted} ${_args_quoted}
WORKING_DIRECTORY \"${_install_dir}\"
COMMAND_ERROR_IS_FATAL ANY
)
")
endfunction()
# ----------------------------------
# Private functions
# ----------------------------------
function(_qm_win_get_all_dep_files _out)
# Get searching paths
macro(get_recursive_dynamic_dependencies _current_target _result)
get_target_property(_deps ${_current_target} LINK_LIBRARIES)
if(_deps)
foreach(_dep IN LISTS _deps)
if(NOT TARGET ${_dep})
continue()
endif()
get_target_property(_type ${_dep} TYPE)
if(_type STREQUAL "SHARED_LIBRARY")
list(APPEND ${_result} ${_dep})
endif()
get_recursive_dynamic_dependencies(${_dep} ${_result})
endforeach()
endif()
endmacro()
set(_visited_targets ${ARGN})
foreach(_target ${ARGN})
set(_all_deps)
get_recursive_dynamic_dependencies(${_target} _all_deps)
foreach(_cur_dep IN LISTS _all_deps)
if(${_cur_dep} IN_LIST _visited_targets)
continue()
endif()
list(APPEND _visited_targets ${_cur_dep})
endforeach()
endforeach()
set(_dep_files)
foreach(_target IN LISTS _visited_targets)
# Add file
get_target_property(_file ${_target} QMSETUP_DEPENDENCIES_FILE)
if(NOT _file)
continue()
endif()
list(APPEND _dep_files ${_file})
endforeach()
set(${_out} ${_dep_files} PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,235 @@
include_guard(DIRECTORY)
#[[
Add Doxygen documentation generating target.
qm_setup_doxygen(<target>
[NAME <name>]
[VERSION <version>]
[DESCRIPTION <desc>]
[LOGO <file>]
[MDFILE <file>]
[OUTPUT_DIR <dir>]
[INSTALL_DIR <dir>]
[TAGFILES <file> ...]
[GENERATE_TAGFILE <file>]
[INPUT <file> ...]
[INCLUDE_DIRECTORIES <dir> ...]
[COMPILE_DEFINITIONS <NAME=VALUE> ...]
[TARGETS <target> ...]
[ENVIRONMENT_EXPORTS <key> ...]
[NO_EXPAND_MACROS <macro> ...]
[DEPENDS <dependency> ...]
)
]] #
function(qm_setup_doxygen _target)
set(options)
set(oneValueArgs NAME VERSION DESCRIPTION LOGO MDFILE OUTPUT_DIR INSTALL_DIR GENERATE_TAGFILE)
set(multiValueArgs INPUT TAGFILES INCLUDE_DIRECTORIES COMPILE_DEFINITIONS TARGETS ENVIRONMENT_EXPORTS
NO_EXPAND_MACROS DEPENDS
)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT DOXYGEN_EXECUTABLE)
message(FATAL_ERROR "qm_setup_doxygen: DOXYGEN_EXECUTABLE not defined. Add find_package(Doxygen) to CMake to enable.")
endif()
set(DOXYGEN_FILE_DIR ${QMSETUP_MODULES_DIR}/doxygen)
if(FUNC_NAME)
set(_name ${FUNC_NAME})
else()
set(_name ${PROJECT_NAME})
endif()
if(FUNC_VERSION)
set(_version ${FUNC_VERSION})
else()
set(_version ${PROJECT_VERSION})
endif()
if(FUNC_DESCRIPTION)
set(_desc ${FUNC_DESCRIPTION})
elseif(PROJECT_DESCRIPTION)
set(_desc ${PROJECT_DESCRIPTION})
else()
set(_desc ${_name})
endif()
if(FUNC_LOGO)
set(_logo ${FUNC_LOGO})
else()
set(_logo)
endif()
if(FUNC_MDFILE)
set(_mdfile ${FUNC_MDFILE})
else()
set(_mdfile)
endif()
if(FUNC_GENERATE_TAGFILE)
set(_tagfile ${FUNC_GENERATE_TAGFILE})
else()
set(_tagfile)
endif()
set(_sep " \\\n ")
# Generate include file
set(_doxy_includes "${CMAKE_CURRENT_BINARY_DIR}/cmake/doxygen_${_target}_$<CONFIG>.inc")
set(_doxy_output_dir "${CMAKE_CURRENT_BINARY_DIR}/doxygen_${_target}")
set(_input "")
set(_tagfiles "")
set(_includes "")
set(_defines "")
set(_no_expand "")
if(FUNC_INPUT)
set(_input "INPUT = $<JOIN:${FUNC_INPUT},${_sep}>\n\n")
else()
set(_input "INPUT = \n\n")
endif()
if(FUNC_TAGFILES)
set(_tagfiles "TAGFILES = $<JOIN:${FUNC_TAGFILES},${_sep}>\n\n")
else()
set(_tagfiles "TAGFILES = \n\n")
endif()
if(FUNC_INCLUDE_DIRECTORIES)
set(_includes "INCLUDE_PATH = $<JOIN:${FUNC_INCLUDE_DIRECTORIES},${_sep}>\n\n")
else()
set(_includes "INCLUDE_PATH = \n\n")
endif()
if(FUNC_COMPILE_DEFINITIONS)
set(_defines "PREDEFINED = $<JOIN:${FUNC_COMPILE_DEFINITIONS},${_sep}>\n\n")
else()
set(_defines "PREDEFINED = \n\n")
endif()
if(FUNC_NO_EXPAND_MACROS)
set(_temp_list)
foreach(_item IN LISTS FUNC_NO_EXPAND_MACROS)
list(APPEND _temp_list "${_item}=")
endforeach()
set(_no_expand "PREDEFINED += $<JOIN:${_temp_list},${_sep}>\n\n")
unset(_temp_list)
endif()
# Extra
set(_extra_arguments)
if(FUNC_TARGETS)
foreach(item IN LISTS FUNC_TARGETS)
set(_extra_arguments
"${_extra_arguments}INCLUDE_PATH += $<JOIN:$<TARGET_PROPERTY:${item},INCLUDE_DIRECTORIES>,${_sep}>\n\n")
set(_extra_arguments
"${_extra_arguments}PREDEFINED += $<JOIN:$<TARGET_PROPERTY:${item},COMPILE_DEFINITIONS>,${_sep}>\n\n")
endforeach()
endif()
if(FUNC_OUTPUT_DIR)
set(_doxy_output_dir ${FUNC_OUTPUT_DIR})
endif()
if(_mdfile)
set(_extra_arguments "${_extra_arguments}INPUT += ${_mdfile}\n\n")
endif()
file(GENERATE
OUTPUT "${_doxy_includes}"
CONTENT "${_input}${_tagfiles}${_includes}${_defines}${_extra_arguments}${_no_expand}"
)
set(_env)
foreach(_export IN LISTS FUNC_ENVIRONMENT_EXPORTS)
if(NOT DEFINED "${_export}")
message(FATAL_ERROR "qm_setup_doxygen: ${_export} is not known when trying to export it.")
endif()
list(APPEND _env "${_export}=${${_export}}")
endforeach()
list(APPEND _env "DOXY_FILE_DIR=${DOXYGEN_FILE_DIR}")
list(APPEND _env "DOXY_INCLUDE_FILE=${_doxy_includes}")
list(APPEND _env "DOXY_PROJECT_NAME=${_name}")
list(APPEND _env "DOXY_PROJECT_VERSION=${_version}")
list(APPEND _env "DOXY_PROJECT_BRIEF=${_desc}")
list(APPEND _env "DOXY_PROJECT_LOGO=${_logo}")
list(APPEND _env "DOXY_MAINPAGE_MD_FILE=${_mdfile}")
set(_build_command "${CMAKE_COMMAND}" "-E" "env"
${_env}
"DOXY_OUTPUT_DIR=${_doxy_output_dir}"
"DOXY_GENERATE_TAGFILE=${_tagfile}"
"${DOXYGEN_EXECUTABLE}"
"${DOXYGEN_FILE_DIR}/Doxyfile"
)
if(FUNC_DEPENDS)
set(_dependencies DEPENDS ${FUNC_DEPENDS})
endif()
if(_tagfile)
get_filename_component(_tagfile_dir ${_tagfile} ABSOLUTE)
get_filename_component(_tagfile_dir ${_tagfile_dir} DIRECTORY)
set(_make_tagfile_dir_cmd COMMAND ${CMAKE_COMMAND} -E make_directory ${_tagfile_dir})
else()
set(_make_tagfile_dir_cmd)
endif()
add_custom_target(${_target}
COMMAND ${CMAKE_COMMAND} -E make_directory ${_doxy_output_dir}
${_make_tagfile_dir_cmd}
COMMAND ${_build_command}
COMMENT "Building HTML documentation"
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
VERBATIM
${_dependencies}
)
if(FUNC_INSTALL_DIR)
set(_install_dir ${FUNC_INSTALL_DIR})
if(_tagfile)
get_filename_component(_name ${_tagfile} NAME)
set(_install_tagfile ${_name})
else()
set(_install_tagfile)
endif()
set(_install_command "${CMAKE_COMMAND}" "-E" "env"
${_env}
"DOXY_OUTPUT_DIR=\${_install_dir}"
"DOXY_GENERATE_TAGFILE=\${_install_dir}/${_install_tagfile}"
"${DOXYGEN_EXECUTABLE}"
"${DOXYGEN_FILE_DIR}/Doxyfile"
)
set(_install_command_quoted)
foreach(_item IN LISTS _install_command)
set(_install_command_quoted "${_install_command_quoted}\"${_item}\" ")
endforeach()
install(CODE "
message(STATUS \"Installing HTML documentation\")
get_filename_component(_install_dir \"${_install_dir}\" ABSOLUTE BASE_DIR \${CMAKE_INSTALL_PREFIX})
file(MAKE_DIRECTORY \${_install_dir})
execute_process(
COMMAND ${_install_command_quoted}
WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"
OUTPUT_QUIET
)
")
endif()
endfunction()

View File

@@ -0,0 +1,199 @@
include_guard(DIRECTORY)
if(NOT QMSETUP_MODULES_DIR)
get_filename_component(QMSETUP_MODULES_DIR ${CMAKE_CURRENT_LIST_DIR} DIRECTORY)
endif()
#[[
Initialize the build output directories of targets and resources.
qm_init_directories()
]] #
macro(qm_init_directories)
if(NOT DEFINED QMSETUP_BUILD_DIR)
set(QMSETUP_BUILD_DIR "${CMAKE_BINARY_DIR}/out-$<LOWER_CASE:${CMAKE_SYSTEM_PROCESSOR}>-$<CONFIG>")
endif()
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${QMSETUP_BUILD_DIR}/bin)
endif()
if(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${QMSETUP_BUILD_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${QMSETUP_BUILD_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_BUILD_SHARE_DIR)
set(CMAKE_BUILD_SHARE_DIR ${QMSETUP_BUILD_DIR}/share)
endif()
endmacro()
#[[
Add a resources copying command for whole project.
qm_add_copy_command(<target>
[CUSTOM_TARGET <target>]
[EXTRA_ARGS <args...>]
[DEPENDS <targets...>]
[VERBOSE] [SKIP_BUILD] [SKIP_INSTALL]
SOURCES <file/dir...> [DESTINATION <dir>] [INSTALL_DIR <dir>]
)
CUSTOM_TARGET: Use a custom target to control the copy command
EXTRA_ARGS: Extra arguments to pass to file(INSTALL) statement
DEPENDS: Targets that the copy command depends
SOURCES: Source files or directories, directories ending with "/" will have their contents copied
DESTINATION: Copy the source file to the destination path. If the given value is a relative path,
the base directory depends on the type of the target
- `$<TARGET_FILE_DIR>`: real target
- `QMSETUP_BUILD_DIR`: custom target
INSTALL_DIR: Install the source files into a subdirectory in the given path. The subdirectory is the
relative path from the `QMSETUP_BUILD_DIR` to `DESTINATION`.
]] #
function(qm_add_copy_command _target)
set(options VERBOSE SKIP_BUILD SKIP_INSTALL)
set(oneValueArgs CUSTOM_TARGET DESTINATION INSTALL_DIR)
set(multiValueArgs SOURCES EXTRA_ARGS DEPENDS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT FUNC_SOURCES)
message(FATAL_ERROR "qm_add_copy_command: SOURCES not specified.")
endif()
if(NOT TARGET ${_target})
add_custom_target(${_target})
endif()
# Determine destination
set(_dest .)
if(FUNC_DESTINATION)
set(_dest ${FUNC_DESTINATION})
endif()
# Determine destination base directory
get_target_property(_type ${_target} TYPE)
if(_type STREQUAL "UTILITY")
if(QMSETUP_BUILD_DIR)
set(_dest_base ${QMSETUP_BUILD_DIR})
else()
set(_dest_base ${CMAKE_CURRENT_SOURCE_DIR})
endif()
else()
set(_dest_base "$<TARGET_FILE_DIR:${_target}>")
endif()
# Set deploy target
if(FUNC_CUSTOM_TARGET)
set(_deploy_target ${FUNC_CUSTOM_TARGET})
if(NOT TARGET ${_deploy_target})
add_custom_target(${_deploy_target})
endif()
else()
set(_deploy_target ${_target})
endif()
if(FUNC_DEPENDS)
add_dependencies(${_deploy_target} ${FUNC_DEPENDS})
endif()
# Prepare arguments
set(_extra_args)
set(_ignore_stdout)
if(FUNC_EXTRA_ARGS)
list(APPEND _extra_args -D "args=${FUNC_EXTRA_ARGS}")
endif()
if(NOT FUNC_VERBOSE)
set(_ignore_stdout ${QMSETUP_IGNORE_STDOUT})
endif()
if(NOT FUNC_SKIP_BUILD)
# Build phase
add_custom_command(TARGET ${_deploy_target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
-D "src=${FUNC_SOURCES}"
-D "dest=${_dest}"
-D "dest_base=${_dest_base}"
${_extra_args}
-P "${QMSETUP_MODULES_DIR}/scripts/copy.cmake" ${_ignore_stdout}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
endif()
if(FUNC_INSTALL_DIR AND NOT FUNC_SKIP_INSTALL)
if(NOT QMSETUP_BUILD_DIR)
message(FATAL_ERROR "qm_add_copy_command: `QMSETUP_BUILD_DIR` not defined, the install directory cannot be determined.")
endif()
# Install phase
install(CODE "
set(_src \"${FUNC_SOURCES}\")
set(_extra_args \"${FUNC_EXTRA_ARGS}\")
# Calculate the relative path from build phase destination to build directory
get_filename_component(_build_dir \"${_dest}\" ABSOLUTE BASE_DIR \"${_dest_base}\")
file(RELATIVE_PATH _rel_path \"${QMSETUP_BUILD_DIR}\" \${_build_dir})
# Calculate real install directory
get_filename_component(_dest \"${FUNC_INSTALL_DIR}/\${_rel_path}\" ABSOLUTE BASE_DIR \${CMAKE_INSTALL_PREFIX})
execute_process(
COMMAND \${CMAKE_COMMAND}
-D \"src=\${_src}\"
-D \"dest=\${_dest}\"
\${_extra_args}
-P \"${QMSETUP_MODULES_DIR}/scripts/copy.cmake\"
WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"
)
")
endif()
endfunction()
#[[
Add a custom command to run `configure_file`.
qm_future_configure_file(<_input> <output>
[FORCE]
[EXTRA_ARGS <args...>]
[DEPENDS <deps...>]
[VARIABLES <var...>]
)
]] #
function(qm_future_configure_file _input _output)
set(options FORCE)
set(oneValueArgs)
set(multiValueArgs VARIABLES EXTRA_ARGS DEPENDS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_options)
foreach(_item IN LISTS FUNC_VARIABLES)
list(APPEND _options -D "${_item}=${${_item}}")
endforeach()
if(FUNC_FORCE)
list(APPLE _options -D "force=TRUE")
endif()
add_custom_command(OUTPUT ${_output}
COMMAND ${CMAKE_COMMAND}
-D "input=${_input}"
-D "output=${_output}"
-D "args=${FUNC_EXTRA_ARGS}"
${_options}
-P "${QMSETUP_MODULES_DIR}/scripts/configure_file.cmake"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${FUNC_DEPENDS}
VERBATIM
)
endfunction()

View File

@@ -0,0 +1,538 @@
#[[
Warning: This module depends on `qmcorecmd` after installation.
]] #
if(NOT QMSETUP_CORECMD_EXECUTABLE)
message(FATAL_ERROR "QMSETUP_CORECMD_EXECUTABLE not defined. Add find_package(qmsetup) to CMake first.")
endif()
if(NOT DEFINED QMSETUP_DEFINITION_NUMERICAL)
set(QMSETUP_DEFINITION_NUMERICAL off)
endif()
if(NOT DEFINED QMSETUP_DEFINITION_SCOPE)
set(QMSETUP_DEFINITION_SCOPE)
endif()
if(NOT DEFINED QMSETUP_DEFINITION_PROPERTY)
set(QMSETUP_DEFINITION_PROPERTY)
endif()
if(NOT DEFINED QMSETUP_SYNC_INCLUDE_STANDARD)
set(QMSETUP_SYNC_INCLUDE_STANDARD on)
endif()
include_guard(DIRECTORY)
#[[
Generate indirect reference files for header files to make the include statements more orderly.
The generated file has the same timestamp as the source file.
qm_sync_include(<src> <dest>
[STANDARD] [NO_STANDARD] [NO_ALL]
[INCLUDE <expr> <sub> ...]
[EXCLUDE <expr...>]
[INSTALL_DIR <dir>]
[FORCE] [VERBOSE]
)
STANDARD: Enable standard public-private pattern, can be forced to enable by enabling `QMSETUP_SYNC_INCLUDE_STANDARD`
NO_STANDARD: Disable standard public-private pattern, enable it to override `QMSETUP_SYNC_INCLUDE_STANDARD`
#]]
function(qm_sync_include _src_dir _dest_dir)
set(options FORCE VERBOSE NO_STANDARD NO_ALL)
set(oneValueArgs INSTALL_DIR)
set(multiValueArgs INCLUDE EXCLUDE)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(NOT IS_ABSOLUTE ${_src_dir})
get_filename_component(_src_dir ${_src_dir} ABSOLUTE)
else()
string(REPLACE "\\" "/" _src_dir ${_src_dir})
endif()
if(NOT IS_ABSOLUTE ${_dest_dir})
get_filename_component(_dest_dir ${_dest_dir} ABSOLUTE)
else()
string(REPLACE "\\" "/" _dest_dir ${_dest_dir})
endif()
if(IS_DIRECTORY ${_src_dir})
file(GLOB_RECURSE header_files ${_src_dir}/*.h ${_src_dir}/*.hpp)
set(_args)
if(FUNC_STANDARD OR(QMSETUP_SYNC_INCLUDE_STANDARD AND NOT FUNC_NO_STANDARD))
list(APPEND _args -s)
endif()
if(FUNC_NO_ALL)
list(APPEND _args -n)
endif()
set(_even off)
foreach(_item IN LISTS FUNC_INCLUDE)
if(_even)
set(_even off)
list(APPEND _args ${_item})
else()
set(_even on)
list(APPEND _args -i ${_item})
endif()
endforeach()
foreach(_item IN LISTS FUNC_EXCLUDE)
list(APPEND _args -e ${_item})
endforeach()
if(FUNC_VERBOSE)
list(APPEND _args -V)
endif()
if(FUNC_FORCE OR NOT EXISTS ${_dest_dir})
if(EXISTS ${_dest_dir})
file(REMOVE_RECURSE ${_dest_dir})
endif()
execute_process(
COMMAND ${QMSETUP_CORECMD_EXECUTABLE} incsync ${_args} ${_src_dir} ${_dest_dir}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endif()
if(FUNC_INSTALL_DIR)
set(_install_dir ${FUNC_INSTALL_DIR})
set(_args_quoted)
foreach(_item IN LISTS _args)
set(_args_quoted "${_args_quoted}\"${_item}\" ")
endforeach()
# Get command output only and use file(INSTALL) to install files
install(CODE "
get_filename_component(_install_dir \"${_install_dir}\" ABSOLUTE BASE_DIR \${CMAKE_INSTALL_PREFIX})
execute_process(
COMMAND \"${QMSETUP_CORECMD_EXECUTABLE}\" incsync -d
${_args_quoted} \"${_src_dir}\" \${_install_dir}
WORKING_DIRECTORY \"${CMAKE_CURRENT_SOURCE_DIR}\"
OUTPUT_VARIABLE _output_contents
OUTPUT_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
string(REPLACE \"\\n\" \";\" _lines \"\${_output_contents}\")
foreach(_line IN LISTS _lines)
string(REGEX MATCH \"from \\\"([^\\\"]*)\\\" to \\\"([^\\\"]*)\\\"\" _ \${_line})
get_filename_component(_target_path \${CMAKE_MATCH_2} DIRECTORY)
file(INSTALL \${CMAKE_MATCH_1} DESTINATION \${_target_path})
endforeach()
")
endif()
else()
message(FATAL_ERROR "qm_sync_include: source directory doesn't exist.")
endif()
endfunction()
#[[
Add a definition to a property scope.
qm_add_definition( <key | key=value> | <key> <value>
[GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
[PROPERTY <prop>]
[CONDITION <cond...>]
[STRING_LITERAL] [NO_KEYWORD]
[NUMERICAL] [CLASSICAL]
)
STRING_LITERAL: Force quotes on values
NO_KEYWORD: Treat any keyword as string
NUMERICAL: Use 1/-1 as defined/undefined, can be forced to enable by enabling `QMSETUP_DEFINITION_NUMERICAL`
CLASSICAL: Use classical definition, enable it to override `QMSETUP_DEFINITION_NUMERICAL`
]] #
function(qm_add_definition _first)
set(options STRING_LITERAL NO_KEYWORD NUMERICAL CLASSICAL)
set(oneValueArgs TARGET SOURCE DIRECTORY PROPERTY)
set(multiValueArgs CONDITION)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_result)
set(_is_pair off)
set(_defined off)
set(_list ${_first} ${FUNC_UNPARSED_ARGUMENTS})
list(LENGTH _list _len)
set(_cond on)
set(_numerical off)
if(NOT FUNC_CLASSICAL AND(QMSETUP_DEFINITION_NUMERICAL OR FUNC_NUMERICAL))
set(_numerical on)
endif()
if(FUNC_CONDITION)
if(NOT(${FUNC_CONDITION}))
set(_cond off)
endif()
elseif(DEFINED FUNC_CONDITION)
set(_cond off)
endif()
if(_len EQUAL 1)
set(_result ${_list})
set(_defined on)
if(NOT _cond)
set(_defined off)
endif()
elseif(_len EQUAL 2)
# Get key
list(POP_FRONT _list _key)
list(POP_FRONT _list _val)
if(FUNC_STRING_LITERAL AND NOT ${_val} MATCHES "\".+\"")
set(_val "\"${_val}\"")
endif()
# Boolean
string(TOLOWER ${_val} _val_lower)
if(NOT FUNC_NO_KEYWORD AND(${_val_lower} STREQUAL "off" OR ${_val_lower} STREQUAL "false"))
set(_result ${_key})
set(_defined off)
if(NOT _cond)
set(_defined on)
endif()
elseif(NOT FUNC_NO_KEYWORD AND(${_val_lower} STREQUAL "on" OR ${_val_lower} STREQUAL "true"))
set(_result ${_key})
set(_defined on)
if(NOT _cond)
set(_defined off)
endif()
else()
set(_result "${_key}=${_val}")
set(_is_pair on)
set(_defined on)
if(NOT _cond)
set(_defined off)
endif()
endif()
else()
message(FATAL_ERROR "qm_add_definition: called with incorrect number of arguments")
endif()
if(_numerical AND NOT _is_pair)
if(_defined)
set(_result "${_result}=1")
else()
set(_result "${_result}=-1")
endif()
elseif(NOT _defined)
return()
endif()
_qm_calc_property_scope_helper(_scope _prop)
set_property(${_scope} APPEND PROPERTY ${_prop} "${_result}")
endfunction()
#[[
Remove a definition from a property scope.
qm_remove_definition(<key>
[GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
[PROPERTY <prop>]
)
]] #
function(qm_remove_definition _key)
set(options)
set(oneValueArgs TARGET PROPERTY)
set(multiValueArgs)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(FUNC_TARGET)
get_target_property(_definitions ${FUNC_TARGET} ${_prop})
else()
get_property(_definitions GLOBAL PROPERTY ${_prop})
endif()
# Filter
list(FILTER _definitions EXCLUDE REGEX "^${_key}(=.*)?$")
_qm_calc_property_scope_helper(_scope _prop)
set_property(${_scope} PROPERTY ${_prop} "${_definitions}")
endfunction()
#[[
Generate a configuration header of a property scope. If the configuration has not changed,
the generated file's timestemp will not be updated when you reconfigure it.
qm_generate_config(<file>
[GLOBAL | TARGET <target> | SOURCE <file> | DIRECTORY <dir>]
[PROPERTY <prop>]
[PROJECT_NAME <name>]
[WARNING_FILE <file>]
[NO_WARNING]
[NO_HASH]
)
]] #
function(qm_generate_config _file)
set(options NO_WARNING NO_HASH)
set(oneValueArgs TARGET PROPERTY PROJECT_NAME WARNING_FILE)
set(multiValueArgs)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
_qm_calc_property_scope_helper(_scope _prop)
get_property(_definitions ${_scope} PROPERTY ${_prop})
if(NOT _definitions)
set(_definitions) # May be _-NOTFOUND
endif()
_qm_generate_config_helper()
endfunction()
#[[
Generate build info information header.
qm_generate_build_info(<file>
[REQUIRED]
[ROOT_DIRECTORY <dir>]
[PREFIX <prefix>]
[YEAR] [TIME]
[PROJECT_NAME <name>]
[WARNING_FILE <file>]
[NO_WARNING]
[NO_HASH]
)
file: Output file
REQUIRED: Abort if there's any error with git
ROOT_DIRECTORY: Repository root directory (CMake will try to run `git` at this directory)
PREFIX: Macros prefix, default to the upper case of `PROJECT_NAME`
]] #
function(qm_generate_build_info _file)
set(options NO_WARNING NO_HASH YEAR TIME REQUIRED)
set(oneValueArgs ROOT_DIRECTORY PREFIX PROJECT_NAME WARNING_FILE)
set(multiValueArgs)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
if(FUNC_PREFIX)
set(_prefix ${FUNC_PREFIX})
else()
string(TOUPPER "${PROJECT_NAME}" _prefix)
string(MAKE_C_IDENTIFIER ${_prefix} _prefix)
endif()
set(_dir)
qm_set_value(_dir FUNC_ROOT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})
set(_git_branch "unknown")
set(_git_hash "unknown")
set(_git_commit_time "unknown")
set(_git_commit_author "unknown")
set(_git_commit_email "unknown")
set(_git_revision_id "0")
# Check `git` command
if(NOT GIT_EXECUTABLE)
find_package(Git)
if(NOT Git_FOUND)
if(FUNC_REQUIRED)
message(FATAL_ERROR "Git not found")
endif()
endif()
endif()
if(GIT_EXECUTABLE)
# Branch
execute_process(
COMMAND ${GIT_EXECUTABLE} symbolic-ref --short -q HEAD
OUTPUT_VARIABLE _temp
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
WORKING_DIRECTORY ${_dir}
RESULT_VARIABLE _code
)
if(_code EQUAL 0)
set(_git_branch ${_temp})
elseif(FUNC_REQUIRED)
message(FATAL_ERROR "Error running git symbolic-ref: ${_err}")
endif()
# Hash
execute_process(
COMMAND ${GIT_EXECUTABLE} log -1 "--pretty=format:%H;%aI;%aN;%aE" # Use `;` as separator
OUTPUT_VARIABLE _temp
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
WORKING_DIRECTORY ${_dir}
RESULT_VARIABLE _code
)
if(_code EQUAL 0)
list(GET _temp 0 _git_hash)
list(GET _temp 1 _git_commit_time)
list(GET _temp 2 _git_commit_author)
list(GET _temp 3 _git_commit_email)
elseif(FUNC_REQUIRED)
message(FATAL_ERROR "Error running git log: ${_err}")
endif()
# Revision ID
execute_process(
COMMAND ${GIT_EXECUTABLE} rev-list --count HEAD
OUTPUT_VARIABLE _temp
ERROR_VARIABLE _err
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
WORKING_DIRECTORY ${_dir}
RESULT_VARIABLE _code
)
if(_code EQUAL 0)
set(_git_revision_id ${_temp})
elseif(FUNC_REQUIRED)
message(FATAL_ERROR "Error running git rev-list: ${_err}")
endif()
endif()
qm_set_value(_system_name CMAKE_SYSTEM_NAME unknown)
qm_set_value(_system_version CMAKE_SYSTEM_VERSION unknown)
qm_set_value(_system_processor CMAKE_SYSTEM_PROCESSOR unknown)
qm_set_value(_host_system_name CMAKE_HOST_SYSTEM_NAME unknown)
qm_set_value(_host_system_version CMAKE_HOST_SYSTEM_VERSION unknown)
qm_set_value(_host_system_processor CMAKE_HOST_SYSTEM_PROCESSOR unknown)
qm_set_value(_compiler_name CMAKE_CXX_COMPILER_ID unknown)
qm_set_value(_compiler_version CMAKE_CXX_COMPILER_VERSION unknown)
qm_set_value(_compiler_arch CMAKE_CXX_COMPILER_ARCHITECTURE_ID unknown)
qm_set_value(_compiler_abi CMAKE_CXX_COMPILER_ABI unknown)
set(_definitions)
set(_has_time off)
# year
if(FUNC_YEAR)
string(TIMESTAMP _build_year "%Y")
list(APPEND _definitions ${_prefix}_BUILD_YEAR=\"${_build_year}\")
set(_has_time on)
endif()
# time
if(FUNC_TIME)
string(TIMESTAMP _build_time UTC)
list(APPEND _definitions ${_prefix}_BUILD_TIME=\"${_build_time}\")
set(_has_time on)
endif()
if(_has_time)
list(APPEND _definitions "%")
endif()
# system
list(APPEND _definitions ${_prefix}_SYSTEM_NAME=\"${_system_name}\")
list(APPEND _definitions ${_prefix}_SYSTEM_VERSION=\"${_system_version}\")
list(APPEND _definitions ${_prefix}_SYSTEM_PROCESSOR=\"${_system_processor}\")
list(APPEND _definitions ${_prefix}_HOST_SYSTEM_NAME=\"${_host_system_name}\")
list(APPEND _definitions ${_prefix}_HOST_SYSTEM_VERSION=\"${_host_system_version}\")
list(APPEND _definitions ${_prefix}_HOST_SYSTEM_PROCESSOR=\"${_host_system_processor}\")
list(APPEND _definitions "%")
# compiler
list(APPEND _definitions ${_prefix}_COMPILER_ID=\"${_compiler_name}\")
list(APPEND _definitions ${_prefix}_COMPILER_VERSION=\"${_compiler_version}\")
list(APPEND _definitions ${_prefix}_COMPILER_ARCH=\"${_compiler_arch}\")
list(APPEND _definitions ${_prefix}_COMPILER_ABI=\"${_compiler_abi}\")
list(APPEND _definitions "%")
# build time (deprecated)
# list(APPEND _definitions ${_prefix}_BUILD_DATE_TIME=\"${_build_time}\")
# list(APPEND _definitions ${_prefix}_BUILD_YEAR=\"${_build_year}\")
# list(APPEND _definitions "%")
# git info
list(APPEND _definitions ${_prefix}_GIT_BRANCH=\"${_git_branch}\")
list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_HASH=\"${_git_hash}\")
list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_TIME=\"${_git_commit_time}\")
list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_AUTHOR=\"${_git_commit_author}\")
list(APPEND _definitions ${_prefix}_GIT_LAST_COMMIT_EMAIL=\"${_git_commit_email}\")
list(APPEND _definitions ${_prefix}_GIT_REVISION_ID=\"${_git_revision_id}\")
_qm_generate_config_helper()
endfunction()
# ----------------------------------
# Private functions
# ----------------------------------
function(_qm_calc_property_scope_helper _scope _prop)
if(FUNC_TARGET)
set(_scope TARGET ${FUNC_TARGET})
elseif(FUNC_SOURCE)
set(_scope SOURCE ${FUNC__SOURCE})
elseif(FUNC_DIRECTORY)
set(_scope DIRECTORY ${FUNC_DIRECTORY})
elseif(FUNC_GLOBAL)
set(_scope GLOBAL)
elseif(QMSETUP_DEFINITION_SCOPE)
set(_scope ${QMSETUP_DEFINITION_SCOPE})
else()
set(_scope GLOBAL)
endif()
qm_set_value(_prop FUNC_PROPERTY QMSETUP_DEFINITION_PROPERTY "CONFIG_DEFINITIONS")
set(_scope ${_scope} PARENT_SCOPE)
set(_prop ${_prop} PARENT_SCOPE)
endfunction()
function(_qm_generate_config_helper)
set(_args)
foreach(_item IN LISTS _definitions)
list(APPEND _args "-D${_item}")
endforeach()
if(FUNC_PROJECT_NAME)
list(APPEND _args "-p" ${FUNC_PROJECT_NAME})
endif()
if(FUNC_NO_HASH)
list(APPEND _args "-f")
endif()
list(APPEND _args ${_file})
if(NOT FUNC_NO_WARNING)
list(APPEND _args "-w")
if(FUNC_WARNING_FILE)
list(APPEND _args ${FUNC_WARNING_FILE})
endif()
endif()
execute_process(COMMAND ${QMSETUP_CORECMD_EXECUTABLE} configure ${_args}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND_ERROR_IS_FATAL ANY
)
endfunction()

View File

@@ -0,0 +1,141 @@
include_guard(DIRECTORY)
#[[
Add rules for creating Google Protobuf source files.
qm_create_protobuf(<OUT>
INPUT <files...>
[OUTPUT_DIR <dir>]
[TARGET <target>]
[INCLUDE_DIRECTORIES <dirs...>]
[OPTIONS <options...>]
[DEPENDS <deps...>]
[CREATE_ONCE]
)
INPUT: source files
OUTPUT_DIR: output directory
TARGET: add a custom target to run the generating command
INCLUDE_DIRECTORIES: extra include directories
OPTIONS: extra options passed to protobuf compiler
DEPENDS: dependencies
CREATE_ONCE: create proto code files at configure phase if not exist
OUT: output source file paths
#]]
function(qm_create_protobuf _out)
set(options CREATE_ONCE)
set(oneValueArgs OUTPUT_DIR TARGET)
set(multiValueArgs INPUT INCLUDE_DIRECTORIES OPTIONS DEPENDS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Find `protoc`
if(NOT PROTOC_EXECUTABLE)
if(NOT TARGET protobuf::protoc)
message(FATAL_ERROR "qm_create_protobuf: protobuf compiler not found. Add find_package(Protobuf) to CMake to enable.")
endif()
get_target_property(PROTOC_EXECUTABLE protobuf::protoc LOCATION)
if(NOT PROTOC_EXECUTABLE)
message(FATAL_ERROR "qm_create_protobuf: failed to get the location of `protoc`, you could set PROTOC_EXECUTABLE manually.")
endif()
# Cache value
set(PROTOC_EXECUTABLE ${PROTOC_EXECUTABLE} PARENT_SCOPE)
endif()
if(NOT FUNC_INPUT)
message(FATAL_ERROR "qm_create_protobuf: INPUT not specified.")
endif()
if(FUNC_OUTPUT_DIR)
get_filename_component(_out_dir ${FUNC_OUTPUT_DIR} ABSOLUTE)
file(MAKE_DIRECTORY ${_out_dir})
else()
set(_out_dir ${CMAKE_CURRENT_BINARY_DIR})
endif()
# Collect include paths and out files
set(_include_options)
set(_out_files)
set(_input_names)
foreach(_item IN LISTS FUNC_INCLUDE_DIRECTORIES)
get_filename_component(_abs_path ${_item} ABSOLUTE)
list(APPEND _include_options -I${_abs_path})
endforeach()
foreach(_item IN LISTS FUNC_INPUT)
get_filename_component(_item ${_item} ABSOLUTE)
get_filename_component(_abs_path ${_item} DIRECTORY)
list(APPEND _include_options -I${_abs_path})
endforeach()
list(REMOVE_DUPLICATES _include_options)
set(_create_once_warning)
set(_create_once_warning_printed off)
# Prepare for create once
if(FUNC_CREATE_ONCE)
# Check if options contain generator expressions
foreach(_opt IN LISTS _include_options LISTS FUNC_OPTIONS)
string(GENEX_STRIP "${_opt}" _no_genex)
if(NOT _no_genex STREQUAL _opt)
set(_create_once_warning "options contain generator expressions, skip generating source file now")
break()
endif()
endforeach()
endif()
foreach(_item IN LISTS FUNC_INPUT)
get_filename_component(_basename ${_item} NAME_WLE)
list(APPEND _out_files ${_out_dir}/${_basename}.pb.h ${_out_dir}/${_basename}.pb.cc)
get_filename_component(_name ${_item} NAME)
list(APPEND _input_names ${_name})
if(FUNC_CREATE_ONCE AND(NOT EXISTS ${_out_dir}/${_basename}.pb.h OR NOT EXISTS ${_out_dir}/${_basename}.pb.cc))
if(_create_once_warning)
if(NOT _create_once_warning_printed)
message(WARNING "qm_create_protobuf: ${_create_once_warning}")
set(_create_once_warning_printed on)
endif()
else()
get_filename_component(_abs_file ${_item} ABSOLUTE)
if(NOT EXISTS ${_abs_file})
message(WARNING "qm_create_protobuf: input file \"${_name}\" is not available, skip generating source file now")
else()
message(STATUS "Protoc: Generating ${_basename}.pb.h, ${_basename}.pb.cc")
execute_process(COMMAND
${PROTOC_EXECUTABLE} --cpp_out=${_out_dir} ${_include_options} ${FUNC_OPTIONS} ${_name}
)
endif()
endif()
endif()
add_custom_command(
OUTPUT ${_out_dir}/${_basename}.pb.h ${_out_dir}/${_basename}.pb.cc
COMMAND ${PROTOC_EXECUTABLE} --cpp_out=${_out_dir} ${_include_options} ${FUNC_OPTIONS} ${_name}
DEPENDS ${_item} ${FUNC_DEPENDS}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
VERBATIM
)
endforeach()
if(FUNC_TARGET)
if(NOT TARGET ${FUNC_TARGET})
add_custom_target(${FUNC_TARGET})
endif()
add_dependencies(${FUNC_TARGET} ${_out_files})
endif()
set(${_out} ${_out_files} PARENT_SCOPE)
endfunction()

View File

@@ -0,0 +1,96 @@
include_guard(DIRECTORY)
#[[
Installs a QML module and its runtime loadable plugin, meta information, QML files, and resources.
The module is identified by the given target name.
The Qt version should be greater than or equal to 6.3.
qm_install_qml_modules(target
[PREFIX prefix]
)
Arguments:
PREFIX: install directory prefix (default: "qml")
Notice:
For static library backing targets, you should specify "OUTPUT_TARGETS" when calling "qt_add_qml_module()"
to collect the internally generated targets (mainly object libraries), and then install them by calling:
install(TARGETS ${_output_targets}
EXPORT <target set>
RUNTIME DESTINATION bin
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib
OBJECTS DESTINATION lib
)
See also: https://doc.qt.io/qt-6/qt-add-qml-module.html
]] #
function(qm_install_qml_modules _target)
set(options)
set(oneValueArgs PREFIX)
set(multiValueArgs)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_prefix)
qm_set_value(_prefix FUNC_PREFIX "qml")
qt_query_qml_module(${_target}
URI _module_uri
VERSION _module_version
PLUGIN_TARGET _module_plugin_target
TARGET_PATH _module_target_path
QMLDIR _module_qmldir
TYPEINFO _module_typeinfo
QML_FILES _module_qml_files
QML_FILES_DEPLOY_PATHS _qml_files_deploy_paths
RESOURCES _module_resources
RESOURCES_DEPLOY_PATHS _resources_deploy_paths
)
# See also: https://doc.qt.io/qt-6/qt-query-qml-module.html#example
# Install the QML module runtime loadable plugin
set(_module_dir "${_prefix}/${_module_target_path}")
install(TARGETS "${_module_plugin_target}"
LIBRARY DESTINATION "${_module_dir}"
RUNTIME DESTINATION "${_module_dir}"
ARCHIVE DESTINATION "${_module_dir}"
)
# Install the QML module meta information.
install(FILES "${_module_qmldir}" DESTINATION "${_module_dir}")
install(FILES "${_module_typeinfo}" DESTINATION "${_module_dir}")
# Install QML files, possibly renamed.
list(LENGTH _module_qml_files _num_files)
if(_num_files GREATER 0)
math(EXPR _last_index "${_num_files} - 1")
foreach(_i RANGE 0 ${_last_index})
list(GET _module_qml_files ${_i} _src_file)
list(GET _qml_files_deploy_paths ${_i} _deploy_path)
get_filename_component(_dest_name "${_deploy_path}" NAME)
get_filename_component(_dest_dir "${_deploy_path}" DIRECTORY)
install(FILES "${_src_file}" DESTINATION "${_module_dir}/${_dest_dir}" RENAME "${_dest_name}")
endforeach()
endif()
# Install resources, possibly renamed.
list(LENGTH _module_resources _num_files)
if(_num_files GREATER 0)
math(EXPR _last_index "${_num_files} - 1")
foreach(_i RANGE 0 ${_last_index})
list(GET _module_resources ${_i} _src_file)
list(GET _resources_deploy_paths ${_i} _deploy_path)
get_filename_component(_dest_name "${_deploy_path}" NAME)
get_filename_component(_dest_dir "${_deploy_path}" DIRECTORY)
install(FILES "${_src_file}" DESTINATION "${_module_dir}/${_dest_dir}" RENAME "${_dest_name}")
endforeach()
endif()
endfunction()

View File

@@ -0,0 +1,374 @@
include_guard(DIRECTORY)
#[[
Add qt translation target.
qm_add_translation(<target>
[LOCALES locales]
[PREFIX prefix]
[SOURCES files... | DIRECTORIES dirs... | TARGETS targets... | TS_FILES files...]
[TS_DIR dir]
[QM_DIR dir]
[TS_OPTIONS options...]
[QM_OPTIONS options...]
[TS_DEPENDS targets...]
[QM_DEPENDS targets...]
[CREATE_ONCE]
)
Arguments:
LOCALES: language names, e.g. zh_CN en_US, must specify if SOURCES or TARGETS is specified
PREFIX: translation file prefix, default to target name
SOURCES: source files
DIRECTORIES: source directories
TARGETS: target names, the source files of which will be collected
TS_FILES: ts file names, add the specified ts file
TS_DIR: ts files destination, default to `CMAKE_CURRENT_SOURCE_DIR`
QM_DIR: qm files destination, default to `CMAKE_CURRENT_BINARY_DIR`
TS_DEPENDS: add lupdate task as a dependency to the given targets
QM_DEPENDS: add lrelease task as a dependency to the given targets
CREATE_ONCE: create translations at configure phase if not exist
]] #
function(qm_add_translation _target)
set(options CREATE_ONCE)
set(oneValueArgs PREFIX TS_DIR QM_DIR)
set(multiValueArgs LOCALES SOURCES DIRECTORIES TARGETS TS_FILES TS_OPTIONS QM_OPTIONS TS_DEPENDS QM_DEPENDS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Get linguist tools
if(NOT TARGET Qt${QT_VERSION_MAJOR}::lupdate OR NOT TARGET Qt${QT_VERSION_MAJOR}::lrelease)
message(FATAL_ERROR "qm_add_translation: linguist tools not defined. Add find_package(Qt\${QT_VERSION_MAJOR} COMPONENTS LinguistTools) to CMake to enable.")
endif()
set(_src_files)
set(_include_dirs)
# Collect source files
if(FUNC_SOURCES)
list(APPEND _src_files ${FUNC_SOURCES})
endif()
# Collect source files
if(FUNC_TARGETS)
foreach(_item IN LISTS FUNC_TARGETS)
get_target_property(_type ${_item} TYPE)
if((_type STREQUAL "UTILITY") OR(_type STREQUAL "INTERFACE_LIBRARY"))
continue()
endif()
set(_tmp_files)
get_target_property(_tmp_files ${_item} SOURCES)
set(_qml_files)
get_target_property(_qml_files ${_item} QT_QML_MODULE_QML_FILES)
if (NOT _qml_files STREQUAL ${_item}-NOTFOUND)
list(APPEND _tmp_files ${_qml_files})
endif()
list(FILTER _tmp_files INCLUDE REGEX ".+\\.(h|hh|hpp|hxx|c|cc|cpp|cxx|m|mm|qml|js|mjs)$")
list(FILTER _tmp_files EXCLUDE REGEX "^(qasc|moc)_.+")
# Need to convert to absolute path
get_target_property(_target_dir ${_item} SOURCE_DIR)
foreach(_file IN LISTS _tmp_files)
get_filename_component(_abs_file ${_file} ABSOLUTE BASE_DIR ${_target_dir})
list(APPEND _src_files ${_abs_file})
endforeach()
unset(_tmp_files)
get_target_property(_tmp_dirs ${_item} INCLUDE_DIRECTORIES)
list(APPEND _include_dirs ${_tmp_dirs})
endforeach()
endif()
# Collect source directories
if(FUNC_DIRECTORIES)
foreach(_item IN LISTS FUNC_DIRECTORIES)
file(GLOB _tmp
${_item}/*.h ${_item}/*.hpp
${_item}/*.hh ${_item}/*.hxx
${_item}/*.cpp ${_item}/*.cxx
${_item}/*.c ${_item}/*.cc
${_item}/*.m ${_item}/*.mm
)
list(APPEND _src_files ${_tmp})
endforeach()
endif()
if(_src_files)
if(NOT FUNC_LOCALES)
message(FATAL_ERROR "qm_add_translation: source files collected but LOCALES not specified!")
endif()
elseif(NOT FUNC_TS_FILES)
message(FATAL_ERROR "qm_add_translation: no source files or ts files collected!")
endif()
add_custom_target(${_target})
set(_qm_depends)
if(FUNC_TS_OPTIONS)
set(_ts_options ${FUNC_TS_OPTIONS})
endif()
if(FUNC_QM_OPTIONS)
set(_qm_options ${FUNC_QM_OPTIONS})
endif()
if(_src_files)
if(FUNC_PREFIX)
set(_prefix ${FUNC_PREFIX})
else()
set(_prefix ${_target})
endif()
if(FUNC_TS_DIR)
set(_ts_dir ${FUNC_TS_DIR})
else()
set(_ts_dir ${CMAKE_CURRENT_SOURCE_DIR})
endif()
set(_ts_files)
foreach(_loc IN LISTS FUNC_LOCALES)
list(APPEND _ts_files ${_ts_dir}/${_prefix}_${_loc}.ts)
endforeach()
# Include options
set(_include_options)
foreach(_inc IN LISTS _include_dirs)
list(APPEND _include_options "-I${_inc}")
endforeach()
# May be an lupdate bug, so we skip passing include directories
# list(APPEND _ts_options ${_include_options})
if(_ts_options)
list(PREPEND _ts_options OPTIONS)
endif()
set(_create_once)
if(FUNC_CREATE_ONCE)
set(_create_once CREATE_ONCE)
endif()
_qm_add_lupdate_target(${_target}_lupdate
INPUT ${_src_files}
OUTPUT ${_ts_files}
${_ts_options}
${_create_once}
)
# Add update dependencies
# add_dependencies(${_target} ${_target}_lupdate)
foreach(_item IN LISTS FUNC_TS_DEPENDS)
add_dependencies(${_item} ${_target}_lupdate)
endforeach()
# list(APPEND _qm_depends DEPENDS ${_target}_lupdate)
else()
if(FUNC_PREFIX)
message(WARNING "qm_add_translation: no source files collected, PREFIX ignored")
endif()
if(FUNC_TS_DIR)
message(WARNING "qm_add_translation: no source files collected, TS_DIR ignored")
endif()
if(FUNC_TS_DEPENDS)
message(WARNING "qm_add_translation: no source files collected, TS_DEPENDS ignored")
endif()
endif()
if(FUNC_QM_DIR)
set(_qm_dir ${FUNC_QM_DIR})
else()
set(_qm_dir ${CMAKE_CURRENT_BINARY_DIR})
endif()
set(_qm_target)
if(_qm_options)
list(PREPEND _qm_options OPTIONS)
endif()
_qm_add_lrelease_target(${_target}_lrelease
INPUT ${_ts_files} ${FUNC_TS_FILES}
DESTINATION ${_qm_dir}
${_qm_options}
${_qm_depends}
)
add_dependencies(${_target} ${_target}_lrelease)
# Add release dependencies
if(FUNC_TARGETS)
foreach(_item IN LISTS FUNC_TARGETS)
add_dependencies(${_item} ${_target}_lrelease)
endforeach()
endif()
foreach(_item IN LISTS FUNC_QM_DEPENDS)
add_dependencies(${_item} ${_target}_lrelease)
endforeach()
endfunction()
# ----------------------------------
# Private functions
# ----------------------------------
# Input: cxx source files
# Output: target ts files
function(_qm_add_lupdate_target _target)
set(options CREATE_ONCE)
set(oneValueArgs)
set(multiValueArgs INPUT OUTPUT OPTIONS DEPENDS)
cmake_parse_arguments(_LUPDATE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_lupdate_deps ${_LUPDATE_DEPENDS})
set(_my_sources ${_LUPDATE_INPUT})
set(_my_tsfiles ${_LUPDATE_OUTPUT})
add_custom_target(${_target} DEPENDS ${_lupdate_deps})
qm_get_executable_location(Qt${QT_VERSION_MAJOR}::lupdate _lupdate_exe)
set(_create_once_warning)
set(_create_once_warning_printed off)
# Prepare for create once
if(_LUPDATE_CREATE_ONCE)
# Check if all src files are available
foreach(_file IN LISTS _my_sources)
get_filename_component(_abs_file ${_file} ABSOLUTE)
if(NOT EXISTS ${_abs_file})
get_filename_component(_file ${_file} NAME)
set(_create_once_warning "source file \"${_file}\" is not available, skip generating ts file now")
break()
endif()
endforeach()
# Check if options contain generator expressions
if(NOT _create_once_warning)
foreach(_opt IN LISTS _LUPDATE_OPTIONS)
string(GENEX_STRIP "${_opt}" _no_genex)
if(NOT _no_genex STREQUAL _opt)
set(_create_once_warning "lupdate options contain generator expressions, skip generating ts file now")
break()
endif()
endforeach()
endif()
endif()
foreach(_ts_file IN LISTS _my_tsfiles)
# make a list file to call lupdate on, so we don't make our commands too
# long for some systems
get_filename_component(_ts_name ${_ts_file} NAME)
set(_ts_lst_file "${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${_ts_name}_lst_file")
set(_lst_file_srcs)
foreach(_lst_file_src IN LISTS _my_sources)
set(_lst_file_srcs "${_lst_file_src}\n${_lst_file_srcs}")
endforeach()
get_directory_property(_inc_DIRS INCLUDE_DIRECTORIES)
foreach(_pro_include IN LISTS _inc_DIRS)
get_filename_component(_abs_include "${_pro_include}" ABSOLUTE)
set(_lst_file_srcs "-I${_pro_include}\n${_lst_file_srcs}")
endforeach()
file(WRITE ${_ts_lst_file} "${_lst_file_srcs}")
get_filename_component(_ts_abs ${_ts_file} ABSOLUTE)
if(_LUPDATE_CREATE_ONCE AND NOT EXISTS ${_ts_abs})
if(_create_once_warning)
if(NOT _create_once_warning_printed)
message(WARNING "qm_add_translation: ${_create_once_warning}")
set(_create_once_warning_printed on)
endif()
else()
message(STATUS "Lupdate: Generating ${_ts_name}")
get_filename_component(_abs_file ${_ts_file} ABSOLUTE)
get_filename_component(_dir ${_abs_file} DIRECTORY)
file(MAKE_DIRECTORY ${_dir})
execute_process(
COMMAND ${_lupdate_exe} ${_LUPDATE_OPTIONS} "@${_ts_lst_file}" -ts ${_ts_file}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_QUIET
COMMAND_ERROR_IS_FATAL ANY
)
endif()
endif()
add_custom_command(
TARGET ${_target} POST_BUILD
COMMAND ${_lupdate_exe}
ARGS ${_LUPDATE_OPTIONS} "@${_ts_lst_file}" -ts ${_ts_file}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
BYPRODUCTS ${_ts_lst_file}
VERBATIM
)
endforeach()
endfunction()
# Input: ts files
# Output: list to append qm files
function(_qm_add_lrelease_target _target)
set(options)
set(oneValueArgs DESTINATION OUTPUT)
set(multiValueArgs INPUT OPTIONS DEPENDS)
cmake_parse_arguments(_LRELEASE "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
set(_lrelease_files ${_LRELEASE_INPUT})
set(_lrelease_deps ${_LRELEASE_DEPENDS})
qm_get_executable_location(Qt${QT_VERSION_MAJOR}::lrelease _lrelease_exe)
set(_qm_files)
foreach(_file IN LISTS _lrelease_files)
get_filename_component(_abs_FILE ${_file} ABSOLUTE)
get_filename_component(_qm_file ${_file} NAME)
# everything before the last dot has to be considered the file name (including other dots)
string(REGEX REPLACE "\\.[^.]*$" "" FILE_NAME ${_qm_file})
get_source_file_property(output_location ${_abs_FILE} OUTPUT_LOCATION)
if(output_location)
set(_out_dir ${output_location})
elseif(_LRELEASE_DESTINATION)
set(_out_dir ${_LRELEASE_DESTINATION})
else()
set(_out_dir ${CMAKE_CURRENT_BINARY_DIR})
endif()
set(_qm_file "${_out_dir}/${FILE_NAME}.qm")
add_custom_command(
OUTPUT ${_qm_file}
COMMAND ${CMAKE_COMMAND} -E make_directory ${_out_dir}
COMMAND ${_lrelease_exe} ARGS ${_LRELEASE_OPTIONS} ${_abs_FILE} -qm ${_qm_file}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${_lrelease_files}
VERBATIM
)
list(APPEND _qm_files ${_qm_file})
endforeach()
add_custom_target(${_target} ALL DEPENDS ${_lrelease_deps} ${_qm_files})
if(_LRELEASE_OUTPUT)
set(${_LRELEASE_OUTPUT} ${_qm_files} PARENT_SCOPE)
endif()
endfunction()

View File

@@ -0,0 +1,68 @@
#[[
Warning: This module is private, may be modified or removed in the future, please use with caution.
]] #
include_guard(DIRECTORY)
#[[
Create the names of output files preserving relative dirs. (Ported from MOC command)
qm_make_output_file(<infile> <prefix> <ext> <OUT>)
OUT: output source file paths
#]]
function(qm_make_output_file _infile _prefix _ext _out)
string(LENGTH ${CMAKE_CURRENT_BINARY_DIR} _binlength)
string(LENGTH ${_infile} _infileLength)
set(_checkinfile ${CMAKE_CURRENT_SOURCE_DIR})
if(_infileLength GREATER _binlength)
string(SUBSTRING "${_infile}" 0 ${_binlength} _checkinfile)
if(_checkinfile STREQUAL "${CMAKE_CURRENT_BINARY_DIR}")
file(RELATIVE_PATH _name ${CMAKE_CURRENT_BINARY_DIR} ${_infile})
else()
file(RELATIVE_PATH _name ${CMAKE_CURRENT_SOURCE_DIR} ${_infile})
endif()
else()
file(RELATIVE_PATH _name ${CMAKE_CURRENT_SOURCE_DIR} ${_infile})
endif()
if(CMAKE_HOST_WIN32 AND _name MATCHES "^([a-zA-Z]):(.*)$") # absolute path
set(_name "${CMAKE_MATCH_1}_${CMAKE_MATCH_2}")
endif()
set(_outfile "${CMAKE_CURRENT_BINARY_DIR}/${_name}")
string(REPLACE ".." "__" _outfile ${_outfile})
get_filename_component(_outpath ${_outfile} PATH)
get_filename_component(_outfile ${_outfile} NAME_WLE)
file(MAKE_DIRECTORY ${_outpath})
set(${_out} ${_outpath}/${_prefix}${_outfile}.${_ext} PARENT_SCOPE)
endfunction()
#[[
Create a custom command to run `xxd`.
qm_add_binary_resource(<input> <output>)
#]]
function(qm_add_binary_resource _input _output)
find_program(_xxd "xxd")
if(NOT _xxd)
get_filename_component(_name ${_output} NAME)
string(MAKE_C_IDENTIFIER ${_name} _name)
set(_cmd "${CMAKE_COMMAND}"
-D "input=${_input}"
-D "output=${_output}"
-D "name=${_name}"
-P "${QMSETUP_MODULES_DIR}/scripts/xxd.cmake")
else()
set(_cmd "${_xxd}" "-i" "${_input}" "${_output}")
endif()
add_custom_command(
OUTPUT ${_output}
COMMAND ${_cmd}
DEPENDS ${_input}
)
endfunction()

View File

@@ -0,0 +1,158 @@
#[[
Warning: This module is private, may be modified or removed in the future, please use with caution.
]] #
if(NOT DEFINED QMSETUP_PACKAGE_BUILD_TYPE)
set(QMSETUP_PACKAGE_BUILD_TYPE "Release")
endif()
include_guard(DIRECTORY)
#[[
Install external package at configuration phase.
qm_install_package(<name>
[SOURCE_DIR <dir>]
[BUILD_TREE_DIR <dir>]
[INSTALL_DIR <dir>]
[CMAKE_PACKAGE_SUBDIR <subdir>]
[BUILD_TYPE <type>]
[CONFIGURE_ARGS <arg...>]
[RESULT_PATH <VAR>]
)
]] #
function(qm_install_package _name)
set(options)
set(oneValueArgs SOURCE_DIR BUILD_TREE_DIR INSTALL_DIR CMAKE_PACKAGE_SUBDIR BUILD_TYPE RESULT_PATH)
set(multiValueArgs CONFIGURE_ARGS)
cmake_parse_arguments(FUNC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
# Directories
if(NOT FUNC_SOURCE_DIR)
message(FATAL_ERROR "pre_install_package: SOURCE_DIR is not spefified")
endif()
set(_src_dir ${FUNC_SOURCE_DIR})
if(FUNC_BUILD_TREE_DIR)
set(_build_tree_dir ${FUNC_BUILD_TREE_DIR})
else()
set(_build_tree_dir ${CMAKE_BINARY_DIR}/_build)
endif()
if(FUNC_INSTALL_DIR)
set(_install_dir ${FUNC_INSTALL_DIR})
else()
set(_install_dir ${CMAKE_BINARY_DIR}/_install)
endif()
if(FUNC_CMAKE_PACKAGE_SUBDIR)
set(_cmake_subdir ${FUNC_CMAKE_PACKAGE_SUBDIR})
else()
include(GNUInstallDirs)
set(_cmake_subdir "${CMAKE_INSTALL_LIBDIR}/cmake/${_name}")
endif()
# Build types
if(FUNC_BUILD_TYPE)
set(_build_type -DCMAKE_BUILD_TYPE=${FUNC_BUILD_TYPE})
elseif(CMAKE_BUILD_TYPE)
set(_build_type -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE})
elseif(NOT CMAKE_CONFIGURATION_TYPES)
set(_build_type -DCMAKE_BUILD_TYPE=${QMSETUP_PACKAGE_BUILD_TYPE})
else()
set(_build_type)
endif()
if(FUNC_BUILD_TYPE)
set(_build_types ${FUNC_BUILD_TYPE})
else()
set(_build_types ${QMSETUP_PACKAGE_BUILD_TYPE})
endif()
# Do it
set(_install_cmake_dir ${_install_dir}/${_cmake_subdir})
set(_build_dir ${_build_tree_dir}/${_name})
if(NOT IS_DIRECTORY ${_install_cmake_dir})
# Determine generator
set(_extra_args)
if(CMAKE_GENERATOR)
set(_extra_args -G "${CMAKE_GENERATOR}")
endif()
if(CMAKE_GENERATOR_PLATFORM)
set(_extra_args -A "${CMAKE_GENERATOR_PLATFORM}")
endif()
# Remove old build directory
if(IS_DIRECTORY ${_build_dir})
file(REMOVE_RECURSE ${_build_dir})
endif()
file(MAKE_DIRECTORY ${_build_tree_dir})
# Configure
message(STATUS "Configuring ${_name}...")
set(_log_file ${_build_tree_dir}/${_name}_configure.log)
execute_process(
COMMAND ${CMAKE_COMMAND} -S ${_src_dir} -B ${_build_dir}
${_extra_args} ${_build_type}
# Pass through CMAKE_INSTALL_LIBDIR to ensure the package uses the same
# lib directory convention (e.g., lib vs lib64) as the parent project.
# Without this, packages using GNUInstallDirs may default to a different
# directory structure than expected.
"-DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR}"
"-DCMAKE_INSTALL_PREFIX=${_install_dir}" ${FUNC_CONFIGURE_ARGS}
OUTPUT_FILE ${_log_file}
ERROR_FILE ${_log_file}
RESULT_VARIABLE _code
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
if(NOT ${_code} EQUAL 0)
message(FATAL_ERROR "Configure failed, check \"${_log_file}\"")
endif()
# Build
foreach(_item IN LISTS _build_types)
message(STATUS "Building ${_name} (${_item})...")
set(_log_file ${_build_tree_dir}/${_name}_build-${_item}.log)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${_build_dir} --config ${_item} --parallel
OUTPUT_FILE ${_log_file}
ERROR_FILE ${_log_file}
RESULT_VARIABLE _code
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
if(NOT ${_code} EQUAL 0)
message(FATAL_ERROR "Build failed, check \"${_log_file}\"")
endif()
endforeach()
# Install
foreach(_item IN LISTS _build_types)
message(STATUS "Installing ${_name} (${_item})...")
set(_log_file ${_build_tree_dir}/${_name}_install-${_item}.log)
execute_process(
COMMAND ${CMAKE_COMMAND} --build ${_build_dir} --config ${_item} --target install
OUTPUT_FILE ${_log_file}
ERROR_FILE ${_log_file}
RESULT_VARIABLE _code
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
)
if(NOT ${_code} EQUAL 0)
message(FATAL_ERROR "Install failed, check \"${_log_file}\"")
endif()
endforeach()
endif()
if(FUNC_RESULT_PATH)
set(${FUNC_RESULT_PATH} ${_install_cmake_dir} PARENT_SCOPE)
endif()
endfunction()

View File

@@ -0,0 +1,37 @@
include_guard(DIRECTORY)
function(qm_get_windows_proxy _out)
if(NOT WIN32)
return()
endif()
execute_process(
COMMAND reg query "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyEnable
OUTPUT_VARIABLE _proxy_enable_output
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NOT _proxy_enable_output MATCHES "ProxyEnable[ \t\r\n]+REG_DWORD[ \t\r\n]+0x1")
return()
endif()
execute_process(
COMMAND reg query "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings" /v ProxyServer
OUTPUT_VARIABLE _proxy_server_output
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
if(NOT _proxy_server_output MATCHES "ProxyServer[ \t\r\n]+REG_SZ[ \t\r\n]+(.*)")
return()
endif()
set(${_out} ${CMAKE_MATCH_1} PARENT_SCOPE)
endfunction()
macro(qm_set_proxy_env _proxy)
set(ENV{HTTP_PROXY} "http://${_proxy}")
set(ENV{HTTPS_PROXY} "http://${_proxy}")
set(ENV{ALL_PROXY} "http://${_proxy}")
endmacro()

View File

@@ -0,0 +1,19 @@
# MIT License
# Copyright (c) 2023 SineStriker
# Description:
# Command that executes `configure_file`.
# Usage:
# cmake
# -D input=<file>
# -D output=<file>
# -D force=TRUE/FALSE
# [-D args=<args...>]
# -P configure_file.cmake
if(force AND EXISTS ${_output})
file(REMOVE ${_output})
endif()
configure_file(${input} ${output} ${args})

View File

@@ -0,0 +1,64 @@
# MIT License
# Copyright (c) 2023 SineStriker
# Description:
# Copy file or directory to destination if different.
# Mainly use `file(INSTALL)` to implement.
# Usage:
# cmake
# -D src=<files/dirs...>
# -D dest=<dir>
# [-D dest_base=<dir>]
# [-D args=<args...>]
# -P copy.cmake
# Check required arguments
if(NOT src)
message(FATAL_ERROR "src not defined.")
endif()
if(NOT dest)
message(FATAL_ERROR "dest not defined.")
endif()
# Calculate destination
if(dest_base)
set(_dest_base ${dest_base})
else()
set(_dest_base ${CMAKE_BINARY_DIR})
endif()
get_filename_component(_dest ${dest} ABSOLUTE BASE_DIR ${_dest_base})
# Copy
foreach(_file IN LISTS src)
# Avoid using `get_filename_component` to keep the trailing slash
set(_path ${_file})
if(NOT IS_ABSOLUTE ${_path})
set(_path "${CMAKE_BINARY_DIR}/${_path}")
endif()
if(IS_DIRECTORY ${_path})
file(INSTALL DESTINATION ${_dest}
TYPE DIRECTORY
FILES ${_path}
${args}
)
else()
set(_paths)
if(${_path} MATCHES "\\*\\*")
file(GLOB_RECURSE _paths ${_path})
else()
file(GLOB _paths ${_path})
endif()
file(INSTALL DESTINATION ${_dest}
TYPE FILE
FILES ${_paths}
${args}
)
endif()
endforeach()

View File

@@ -0,0 +1,245 @@
#!/bin/bash
# MIT License
# Copyright (c) 2023 SineStriker
# Description: This script calls `qmcorecmd` to deploy dependencies on Mac/Linux.
# Show usage
usage() {
echo "Usage: $(basename $0) -i <dir> -m <path>"
echo " --plugindir <plugin_dir> --libdir <lib_dir> --qmldir <qml_dir>"
echo " [--qmake <qmake_path>] [--extra <extra_path>]..."
echo " [--qml <qml_module>]... [--plugin <plugin>]... [--copy <src> <dest>]..."
echo " [-f] [-s] [-V] [-h]"
echo " -i <input_dir> Directory containing binaries and libraries"
echo " -m <corecmd_path> Path to corecmd"
echo " --plugindir <plugin_dir> Output directory for plugins"
echo " --libdir <lib_dir> Output directory for libraries"
echo " --qmldir <qml_dir> Output directory for QML files"
echo " --qmake <qmake_path> Path to qmake (optional)"
echo " --extra <extra_path> Extra plugin searching path (repeatable)"
echo " --qml <qml_module> Relative path to QML directory (repeatable)"
echo " --plugin <plugin> Specify a Qt plugin to deploy (repeatable)"
echo " --copy <src> <dest> Specify additional binary file to copy and its destination directory (repeatable)"
echo " --linkdirs-file Add library searching paths from a list file"
echo " -L Add a library searching path"
echo " -f Force overwrite existing files"
echo " -s Ignore C/C++ runtime and system libraries"
echo " -V Show verbose output"
echo " -h Show this help message"
}
# Initialize arguments
EXTRA_PLUGIN_PATHS=()
QML_REL_PATHS=()
ARGS=()
VERBOSE=""
PLUGINS=()
FILES=""
# Parse command line
while (( "$#" )); do
case "$1" in
-i) INPUT_DIR="$2"; shift 2;;
-m) CORECMD_PATH="$2"; shift 2;;
-L) ARGS+=("-L \"$2\""); shift 2;;
--linkdirs-file) ARGS+=("--linkdirs-file \"$2\""); shift 2;;
--plugindir) PLUGIN_DIR="$2"; shift 2;;
--libdir) LIB_DIR="$2"; shift 2;;
--qmldir) QML_DIR="$2"; shift 2;;
--qmake) QMAKE_PATH="$2"; shift 2;;
--extra) EXTRA_PLUGIN_PATHS+=("$2"); shift 2;;
--plugin) PLUGINS+=("$2"); shift 2;;
--qml) QML_REL_PATHS+=("$2"); shift 2;;
--copy) ARGS+=("-c \"$2\" \"$3\""); shift 3;;
-f|-s) ARGS+=("$1"); shift;;
-V) VERBOSE="-V"; shift;;
-h) usage; exit 0;;
*) echo "Error: Unsupported argument $1"; usage; exit 1;;
esac
done
# Check required arguments
for arg in INPUT_DIR PLUGIN_DIR LIB_DIR QML_DIR CORECMD_PATH; do
if [[ -z ${!arg} ]]; then
echo "Error: Missing required argument '$arg'"
usage
exit 1
fi
done
# Get Qt plugin and QML paths
PLUGIN_PATHS=()
QML_PATH=""
if [[ -n "$QMAKE_PATH" ]]; then
QMAKE_PLUGIN_PATH=$($QMAKE_PATH -query QT_INSTALL_PLUGINS)
PLUGIN_PATHS+=("$QMAKE_PLUGIN_PATH")
QML_PATH=$($QMAKE_PATH -query QT_INSTALL_QML)
fi
# Add extra plugin searching paths
PLUGIN_PATHS+=("${EXTRA_PLUGIN_PATHS[@]}")
# Ensure that the QML search path is not empty
# when the QML related path is specified (qmake needs to be specified)
if [[ ${#QML_REL_PATHS[@]} -gt 0 && -z "$QML_PATH" ]]; then
echo "Error: qmake path must be specified when QML paths are provided"
usage
exit 1
fi
# Search input directory
search_input_dir() {
local path="$1"
for item in "$path"/*; do
if [ -d "$item" ]; then
# Check if the directory is mac .framework
if [[ "OSTYPE" == "darwin"* ]] && [[ "$item" == *.framework ]]; then
FILES="$FILES \"$item\""
else
search_input_dir "$item"
fi
elif [ -f "$item" ]; then
if [[ "$OSTYPE" == "msys"* ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin"* ]]; then
# On Windows, search for.exe and.dll files
FILES="$FILES \"$item\""
else
# On Unix, traverse all files, using the file command to
# check for executable binary files
file_type=$(file -b "$item")
if [[ ($file_type == "ELF"* || $file_type == "Mach-O"*) && -x "$item" ]]; then
FILES="$FILES \"$item\""
fi
fi
fi
done
}
search_input_dir "$INPUT_DIR"
# Find the full path to the Qt plugin
for plugin_path in "${PLUGINS[@]}"; do
# Check format
if [[ $plugin_path == */* ]]; then
IFS='/' read -r -a plugin_parts <<< "$plugin_path"
# Extracts the category and name
category=${plugin_parts[0]}
name=${plugin_parts[1]}
# Calculate destination directory
dest_dir="${PLUGIN_DIR}/${category}"
# Initialize an array to store found plugins
found_plugins=""
# Traverse the path and find the specific plug-in files
for search_path in "${PLUGIN_PATHS[@]}"; do
while IFS= read -r plugin; do
# Get name
plugin_name=$(basename "$plugin")
# Check if the plugin was already found to avoid duplicates
if [[ ! $found_plugins =~ $plugin_name ]]; then
found_plugins+="$plugin_name "
ARGS+=("-c \"$plugin\" \"$dest_dir\"")
fi
done < <(find "${search_path}/${category}" -name "lib${name}.*" ! -name "*debug*" ! -type d -print)
done
if [ ${#found_plugins[@]} -eq 0 ]; then
echo "Error: Plugin '${plugin_path}' not found in any search paths."
exit 1
fi
else
echo "Error: Invalid plugin format '${plugin_path}'. Expected format: <category>/<name>"
exit 1
fi
done
# Process QML item
handle_qml_file() {
local file="$1"
local rel_path="${file#$QML_PATH/}"
local target="$QML_DIR/$rel_path"
local target_dir="$(dirname "$target")"
# Directory: must be mac framework
if [ -d "$file" ]; then
ARGS+=("-c \"$file\" \"$target_dir\"")
return
fi
# Ignore specific files
if [[ "$OSTYPE" == "msys"* ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin"* ]]; then
if [[ "$file" == *.pdb ]] || [[ "$file" == *d.dll ]]; then
return
fi
else
if [[ "$file" == *_debug.dylib ]] || [[ "$file" == *.so.debug ]]; then
return
fi
fi
# Determine whether it is an executable binary file and handle it accordingly
if [[ "$OSTYPE" == "msys"* ]] || [[ "$OSTYPE" == "win32" ]] || [[ "$OSTYPE" == "cygwin"* ]]; then
if [[ "$file" == *.dll || "$file" == *.exe ]]; then
ARGS+=("-c \"$file\" \"$target_dir\"")
else
mkdir -p "$target_dir"
cp "$file" "$target"
fi
else
file_type=$(file -b "$file")
if [[ ($file_type == "ELF"* || $file_type == "Mach-O"*) && -x "$file" ]]; then
ARGS+=("-c \"$file\" \"$target_dir\"")
else
mkdir -p "$target_dir"
cp "$file" "$target"
fi
fi
}
# Search QML directory
search_qml_dir() {
local path="$1"
for item in "$path"/*; do
if [ -d "$item" ]; then
# Check if the path is mac .framework
if [[ "OSTYPE" == "darwin"* ]] && [[ "$item" == *.framework ]]; then
handle_qml_file "$item"
else
search_qml_dir "$item"
fi
elif [ -f "$item" ]; then
handle_qml_file "$item"
fi
done
}
# Process QML directories
for qml_rel_path in "${QML_REL_PATHS[@]}"; do
full_path="$QML_PATH/$qml_rel_path"
if [[ -d "$full_path" ]]; then
# Directory
search_qml_dir "$full_path"
elif [[ -f "$full_path" ]]; then
# File
handle_qml_file "$full_path" "$QML_DIR"
fi
done
# Build and execute the deploy command
DEPLOY_CMD="$CORECMD_PATH deploy $FILES ${ARGS[@]} -o \"$LIB_DIR\" $VERBOSE"
if [[ "$VERBOSE" == "-V" ]]; then
echo "Executing: $DEPLOY_CMD"
fi
eval $DEPLOY_CMD
# Check the deployment result
if [ $? -ne 0 ]; then
exit 1
fi

View File

@@ -0,0 +1,294 @@
:: MIT License
:: Copyright (c) 2023 SineStriker
:: Description: This script calls `qmcorecmd` to deploy dependencies on Windows.
@echo off
setlocal enabledelayedexpansion
:: Initialize arguments
set "INPUT_DIR="
set "PLUGIN_DIR="
set "LIB_DIR="
set "QML_DIR="
set "QMAKE_PATH="
set "CORECMD_PATH="
set "VERBOSE="
set "FILES="
set "EXTRA_PLUGIN_PATHS="
set "PLUGINS=" & set /a "PLUGIN_COUNT=0"
set "QML_REL_PATHS="
set "ARGS=" & set /a "ARG_COUNT=0"
:: Parse command line
:parse_args
if "%~1"=="" goto :end_parse_args
if "%1"=="-i" set "INPUT_DIR=%~2" & shift & shift & goto :parse_args
if "%1"=="-m" set "CORECMD_PATH=%~2" & shift & shift & goto :parse_args
if "%1"=="--plugindir" set "PLUGIN_DIR=%~2" & shift & shift & goto :parse_args
if "%1"=="--libdir" set "LIB_DIR=%~2" & shift & shift & goto :parse_args
if "%1"=="--qmldir" set "QML_DIR=%~2" & shift & shift & goto :parse_args
if "%1"=="--qmake" set "QMAKE_PATH=%~2" & shift & shift & goto :parse_args
if "%1"=="--extra" set "EXTRA_PLUGIN_PATHS=!EXTRA_PLUGIN_PATHS! %~2" & shift & shift & goto :parse_args
if "%1"=="--plugin" set /a "PLUGIN_COUNT+=1" & set "PLUGINS[!PLUGIN_COUNT!]=%~2" & shift & shift & goto :parse_args
if "%1"=="--qml" set "QML_REL_PATHS=!QML_REL_PATHS! %~2" & shift & shift & goto :parse_args
if "%1"=="--copy" call :push_args -c %~2 %~3 & shift & shift & shift & goto :parse_args
if "%1"=="-f" call :push_args -f & shift & goto :parse_args
if "%1"=="-s" call :push_args -s & shift & goto :parse_args
if "%1"=="-V" set "VERBOSE=-V" & shift & goto :parse_args
if "%1"=="-h" call :usage & exit /b
if "%1"=="--linkdirs-file" call :push_args --linkdirs-file %~2 & shift & shift & goto :parse_args
if "%1"=="-L" call :push_args -L %~2 & shift & shift & goto :parse_args
shift
goto :parse_args
:end_parse_args
:: Check required arguments
if not defined INPUT_DIR echo Error: Missing required argument 'INPUT_DIR' & call :usage & exit /b
if not defined PLUGIN_DIR echo Error: Missing required argument 'PLUGIN_DIR' & call :usage & exit /b
if not defined LIB_DIR echo Error: Missing required argument 'LIB_DIR' & call :usage & exit /b
if not defined QML_DIR echo Error: Missing required argument 'QML_DIR' & call :usage & exit /b
if not defined CORECMD_PATH echo Error: Missing required argument 'CORECMD_PATH' & call :usage & exit /b
:: Normalize
set "INPUT_DIR=!INPUT_DIR:/=\!"
set "PLUGIN_DIR=!PLUGIN_DIR:/=\!"
set "LIB_DIR=!LIB_DIR:/=\!"
set "QML_DIR=!QML_DIR:/=\!"
set "CORECMD_PATH=!CORECMD_PATH:/=\!"
:: Get Qt plugin and QML paths
set "PLUGIN_PATHS="
set "QML_PATH="
if defined QMAKE_PATH (
for /f "tokens=*" %%a in ('!QMAKE_PATH! -query QT_INSTALL_PLUGINS') do set "QMAKE_PLUGIN_PATH=%%a"
set "PLUGIN_PATHS=!QMAKE_PLUGIN_PATH!"
for /f "tokens=*" %%a in ('!QMAKE_PATH! -query QT_INSTALL_QML') do set "QML_PATH=%%a"
set "QML_PATH=!QML_PATH:/=\!"
:: Add Qt bin directory
for /f "tokens=*" %%a in ('!QMAKE_PATH! -query QT_INSTALL_BINS') do set "QT_BIN_PATH=%%a"
call :push_args -L !QT_BIN_PATH!
)
:: Add extra plugin searching paths
set "PLUGIN_PATHS=!PLUGIN_PATHS! !EXTRA_PLUGIN_PATHS!"
:: Ensure that the QML search path is not empty
:: when the QML related path is specified (qmake needs to be specified)
if not "%QML_REL_PATHS%"=="" (
if "%QML_PATH%"=="" (
echo Error: qmake path must be specified when QML paths are provided
exit /b
)
)
:: The type of file to be searched depends on the operating system
:: On Windows, search for.exe and.dll files
for /r "%INPUT_DIR%" %%f in (*.exe *.dll) do (
set "FILES=!FILES! %%f"
)
:: Find the full path to the Qt plugin
for /L %%i in (1,1,%PLUGIN_COUNT%) do (
set "plugin_path=!PLUGINS[%%i]!"
:: Check format
echo !plugin_path! | findstr /R "[^/]*\/[^/]*" >nul
if errorlevel 1 (
echo Error: Invalid plugin format '!plugin_path!'. Expected format: ^<category^>/^<name^>
exit /b
)
:: Extracts the category and name
for /f "tokens=1,2 delims=/" %%a in ("!plugin_path!") do (
set "category=%%a"
set "name=%%b"
:: Calculate destination directory
set "DESTINATION_DIR=!PLUGIN_DIR!\!category!"
set "DESTINATION_DIR=!DESTINATION_DIR:/=\!"
:: Traverse the path and find the specific plug-in file
set "FOUND_PLUGINS="
call :search_plugin
if not defined FOUND_PLUGINS (
echo Error: Plugin '!plugin_path!' not found in any search paths.
exit /b
)
)
)
:: Process QML directories
for %%q in (%QML_REL_PATHS%) do (
call :search_qml_dir "%%q"
)
:: Build and execute the deploy
set "ARGS_FILE=windeps_args.txt"
if exist "%ARGS_FILE%" del %ARGS_FILE%
for %%a in (!FILES!) do (
echo %%a >> %ARGS_FILE%
)
for /L %%i in (1,1,%ARG_COUNT%) do (
echo !ARGS[%%i]! >> %ARGS_FILE%
)
echo -o >> %ARGS_FILE%
echo !LIB_DIR! >> %ARGS_FILE%
echo !VERBOSE! >> %ARGS_FILE%
set "DEPLOY_CMD=!CORECMD_PATH! deploy @!ARGS_FILE!"
call !DEPLOY_CMD!
:: Check the deployment result
if %errorlevel% neq 0 exit /b
del %ARGS_FILE%
exit /b
:: ----------------------------------------------------------------------------------
:: Add args
:push_args
for %%x in (%*) do set /a "ARG_COUNT+=1" & set "ARGS[!ARG_COUNT!]=%%x"
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Search plugins
:search_plugin
for %%d in (!PLUGIN_PATHS!) do (
for %%f in ("%%d\!category!\!name!.dll") do (
if exist "%%f" (
call :check_debug "%%f"
if "!ok!"=="0" (
call :add_plugin "%%f"
)
)
)
)
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Add plugin if not already found
:add_plugin
set "plugin=%~1"
set "plugin_name=%~nx1"
for %%i in (!FOUND_PLUGINS!) do (
if "%%i"=="!plugin_name!" (
exit /b
)
)
set "FOUND_PLUGINS=!FOUND_PLUGINS! !plugin_name!"
call :push_args -c !plugin! !DESTINATION_DIR!
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Search QML directory
:search_qml_dir
set "full_path=%QML_PATH%\%~1"
if exist "%full_path%\" (
:: Directory
for /r "%full_path%" %%f in (*) do (
call :handle_qml_file "%%f"
)
) else if exist "%full_path%" (
:: File
call :handle_qml_file "%full_path%"
)
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Check debug version of a dll
:check_debug
set "ok=1"
set "file_path=%~1"
if "!file_path:~-4!"==".pdb" exit /b
if "!file_path:~-10!"==".dll.debug" exit /b
if "!file_path:~-5!" == "d.dll" (
set "prefix=!file_path:~0,-5!"
if exist "!prefix!.dll" (
exit /b
)
)
set "ok=0"
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Copy or add to a deployment command
:handle_qml_file
set "file=%~1"
set "file=!file:/=\!"
:: Ignore specific files (example)
call :check_debug "%file%"
if "!ok!"=="1" (
exit /b
)
:: Computes target file and folder in a very stupid way
set "rel_path=!file:%QML_PATH%\=!"
set "target=%QML_DIR%\%rel_path%"
for %%I in ("!file!") do (
set "file_dir=%%~dpI"
)
set "rel_dir_path=!file_dir:%QML_PATH%\=!"
set "target_dir=%QML_DIR%\%rel_dir_path%"
:: Determine whether it is an executable binary file and handle it accordingly
if "%file:~-4%"==".dll" (
call :push_args -c !file! !target_dir!
) else if "%file:~-4%"==".exe" (
call :push_args -c !file! !target_dir!
) else (
if not exist "%target%" (
mkdir "%target_dir%" >nul 2>&1
)
copy /Y "%file%" "%target%" >nul 2>&1
)
exit /b
:: ----------------------------------------------------------------------------------
:: ----------------------------------------------------------------------------------
:: Show usage
:usage
echo Usage: %~n0 -i ^<dir^> -m ^<path^>
echo --plugindir ^<plugin_dir^> --libdir ^<lib_dir^> --qmldir ^<qml_dir^>
echo [--qmake ^<qmake_path^>] [--extra ^<extra_path^>]...
echo [--qml ^<qml_module^>]... [--plugin ^<plugin^>]... [--copy ^<src^> ^<dest^>]...
echo [--linkdirs-file ^<file^>]... [-L ^<path^>]...
echo [-f] [-s] [-V] [-h]
exit /b
:: ----------------------------------------------------------------------------------

View File

@@ -0,0 +1,23 @@
if(NOT DEFINED input)
message(FATAL_ERROR "input not defined")
endif()
if(NOT DEFINED output)
message(FATAL_ERROR "output not defined")
endif()
if(NOT DEFINED name)
message(FATAL_ERROR "name not defined")
endif()
get_filename_component(_output_dir ${output} DIRECTORY)
if(NOT EXISTS ${_output_dir})
file(MAKE_DIRECTORY ${_output_dir})
endif()
file(READ ${input} _file_content HEX)
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1, " _hex_content ${_file_content})
file(WRITE ${output}
"unsigned char ${name}[] = {${_hex_content}};\n"
"unsigned int ${name}_len = sizeof(${name});\n"
)

View File

@@ -0,0 +1,8 @@
Set oWS = WScript.CreateObject("WScript.Shell")
sLinkFile = "@SHORTCUT_PATH@"
Set oLink = oWS.CreateShortcut(sLinkFile)
oLink.TargetPath = "@SHORTCUT_TARGET_PATH@"
oLink.WorkingDirectory = "@SHORTCUT_WORKING_DIRECOTRY@"
oLink.Description = "@SHORTCUT_DESCRIPTION@"
oLink.IconLocation = "@SHORTCUT_ICON_LOCATION@"
oLink.Save

View File

@@ -0,0 +1,84 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<description>@MANIFEST_DESCRIPTION@</description>
<assemblyIdentity
version="@MANIFEST_VERSION@"
name="@MANIFEST_IDENTIFIER@"
type="win32"
/>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
@MANIFEST_PRIVILEGES@
</requestedPrivileges>
</security>
</trustInfo>
<dependency>
<dependentAssembly>
<assemblyIdentity
type="win32"
name="Microsoft.Windows.Common-Controls"
version="6.0.0.0"
processorArchitecture="*"
publicKeyToken="6595b64144ccf1df"
language="*"
/>
</dependentAssembly>
</dependency>
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
<application>
<!-- XAML Island won't work without the maxversiontested list -->
<!-- Windows 10 Version 1809 (October 2018 Update) -->
<maxversiontested Id="10.0.17763.0"/>
<!-- Windows 10 Version 1903 (May 2019 Update) -->
<maxversiontested Id="10.0.18362.0"/>
<!-- Windows 10 Version 1909 (November 2019 Update) -->
<maxversiontested Id="10.0.18363.0"/>
<!-- Windows 10 Version 2004 (May 2020 Update) -->
<maxversiontested Id="10.0.19041.0"/>
<!-- Windows 10 Version 20H2 (October 2020 Update) -->
<maxversiontested Id="10.0.19042.0"/>
<!-- Windows 10 Version 21H1 (May 2021 Update) -->
<maxversiontested Id="10.0.19043.0"/>
<!-- Windows 10 Version 21H2 (November 2021 Update) -->
<maxversiontested Id="10.0.19044.0"/>
<!-- Windows 10 Version 22H2 -->
<maxversiontested Id="10.0.19045.0"/>
<!-- Windows 11 Version 21H2 -->
<maxversiontested Id="10.0.22000.0"/>
<!-- Windows 11 Version 22H2 -->
<maxversiontested Id="10.0.22621.0"/>
<!-- Windows 11 Version 23H2 -->
<maxversiontested Id="10.0.22631.0"/>
<!-- Windows 11 Version 24H2 -->
<maxversiontested Id="10.0.26100.0"/>
<!-- Windows 11 Version 25H2 -->
<maxversiontested Id="10.0.26200.0"/>
<!-- Windows Vista and Windows Server 2008 -->
<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
<!-- Windows 7 and Windows Server 2008 R2 -->
<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
<!-- Windows 8 and Windows Server 2012 -->
<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
<!-- Windows 8.1 and Windows Server 2012 R2 -->
<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
<!-- Windows 10 and Windows 11 -->
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
</application>
</compatibility>
<application xmlns="urn:schemas-microsoft-com:asm.v3">
<windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
<printerDriverIsolation xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">True</printerDriverIsolation>
<disableWindowFiltering xmlns="http://schemas.microsoft.com/SMI/2011/WindowsSettings">True</disableWindowFiltering>
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2, PerMonitor</dpiAwareness>
<longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">True</longPathAware>
<heapType xmlns="http://schemas.microsoft.com/SMI/2020/WindowsSettings">SegmentHeap</heapType>
@MANIFEST_UTF8@
</windowsSettings>
</application>
</assembly>

View File

@@ -0,0 +1,29 @@
#include <windows.h>
#ifndef VS_VERSION_INFO
#define VS_VERSION_INFO 1
#endif
@RC_ICON_COMMENT@ IDI_ICON1 ICON DISCARDABLE "@RC_ICON_PATH@"
VS_VERSION_INFO VERSIONINFO
FILEVERSION @RC_VERSION@
PRODUCTVERSION @RC_VERSION@
{
BLOCK "StringFileInfo"
{
// U.S. English - Windows, Multilingual
BLOCK "040904E4"
{
VALUE "FileDescription", "@RC_DESCRIPTION@"
VALUE "FileVersion", "@RC_VERSION_STRING@"
VALUE "ProductName", "@RC_APPLICATION_NAME@"
VALUE "ProductVersion", "@RC_VERSION_STRING@"
VALUE "LegalCopyright", "@RC_COPYRIGHT@"
}
}
BLOCK "VarFileInfo"
{
VALUE "Translation", 0x409, 1252 // 1252 = 0x04E4
}
}

View File

@@ -0,0 +1,38 @@
#include <windows.h>
@RC_ICONS@
VS_VERSION_INFO VERSIONINFO
FILEVERSION @RC_VERSION@
PRODUCTVERSION @RC_VERSION@
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS VS_FF_DEBUG
#else
FILEFLAGS 0x0L
#endif
FILEOS VOS_NT_WINDOWS32
FILETYPE @RC_FILE_TYPE@
FILESUBTYPE VFT2_UNKNOWN
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "040904b0"
BEGIN
VALUE "Comments", "@RC_COMMENTS@"
VALUE "CompanyName", "@RC_COMPANY@"
VALUE "FileDescription", "@RC_DESCRIPTION@"
VALUE "FileVersion", "@RC_VERSION_STRING@"
VALUE "InternalName", "@RC_INTERNAL_NAME@"
VALUE "LegalCopyright", "@RC_COPYRIGHT@"
VALUE "LegalTrademarks", "@RC_TRADEMARK@"
VALUE "OriginalFilename", "@RC_ORIGINAL_FILENAME@"
VALUE "ProductName", "@RC_APPLICATION_NAME@"
VALUE "ProductVersion", "@RC_VERSION_STRING@"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x409, 1200
END
END

View File

@@ -0,0 +1,56 @@
# Core Command
`qmcorecmd` is an executable written by C++. It is designed to provide a set of solutions that are fixed in form and that CMake scripts cannot implement due to insufficient features or inefficiencies.
`qmcorecmd` uses `syscmdline` to parse command line arguments and have multiple subcommands.
You can refer to the help message of each command to acquire more usage information, since this document provides only a brief introduction.
## Filesystem Commands
### Copy
Copies a file or folder. If the given folder path ends with the separator, copies the contents of the folder.
+ Read timestamp to determine whether to overwrite an existing object file.
+ Support ignoring specific files specified by regular expressions.
### Rmdir
Recursively deletes all empty directories.
Since CMake copies all directories unconditionally when executing the `install(DIRECTORY ...)` command, even if the directory is empty. This command can be used to fix this unreasonable phenomenon.
### Touch
Since Microsoft Windows does not provide a native command for handling file timestamps, this command is designed to copy or update a file's time stamp.
Only some of the functionality of Unix `touch` command is implemented.
## Buildsystem Commands
### Configure
Generates configuration header files.
It allows users to add macro definitions via the `-D` option, which are directly written into the generated header file. Additionally, users can specify a warning file with the `-w` option, the contents of which are added as comments at the top of the header file.
A key feature of this command is its ability to compute the SHA-256 hash of the definitions and embed it in the generated file, facilitating the tracking of configuration changes.
### Incsync
Reorganizes header files in include directories.
It supports defining path patterns and corresponding subdirectories through the `-i` option, reorganizing header files by writing relative reference statement or copying directly. The `-e` option can be used to exclude files matching certain patterns.
While processing header files, `incsync` considers the last modified time to determine whether to copy or update files.
### Deploy
Resolves and deploys a binary's shared library dependencies.
It analyzes dependencies of specified files and copies these dependencies to a designated output directory. Moreover, it handles details like fixing RPATH or dealing with Windows-specific library search paths.
The `-s` option is provided to ignore standard C/C++ runtime and system libraries, reducing unnecessary dependency copying.
All shared libraries that are deployed must have different file names.

View File

@@ -0,0 +1,12 @@
#ifndef QMSETUP_GLOBAL_H
#define QMSETUP_GLOBAL_H
#ifdef _WIN32
# define QMSETUP_DECL_EXPORT __declspec(dllexport)
# define QMSETUP_DECL_IMPORT __declspec(dllimport)
#else
# define QMSETUP_DECL_EXPORT __attribute__((visibility("default")))
# define QMSETUP_DECL_IMPORT
#endif
#endif // QMSETUP_GLOBAL_H

View File

@@ -0,0 +1,79 @@
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
if(QMSETUP_STATIC_RUNTIME)
# To avoid a command execution that can't find the library, we choose to link compiler runtime statically
if(MINGW)
add_link_options(-static -static-libgcc -static-libstdc++)
elseif(MSVC)
add_compile_options(/MT)
endif()
endif()
# Get syscmdline
find_package(syscmdline QUIET)
if(NOT TARGET syscmdline::syscmdline)
set(SYSCMDLINE_INSTALL off)
add_subdirectory(syscmdline)
endif()
# Add subdirectories
add_subdirectory(corecmd)
# Add headers
set(_corelib_name library)
add_library(${_corelib_name} INTERFACE)
target_include_directories(${_corelib_name} INTERFACE
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}/${QMSETUP_INSTALL_NAME}>"
)
install(TARGETS ${_corelib_name}
EXPORT ${QMSETUP_INSTALL_NAME}Targets
)
if(TRUE)
# Add install target
set(_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/${QMSETUP_INSTALL_NAME})
# Add version file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}ConfigVersion.cmake"
VERSION ${QMSETUP_VERSION}
COMPATIBILITY AnyNewerVersion
)
# Add configuration file
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake"
INSTALL_DESTINATION ${_install_dir}
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# Install cmake files
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${QMSETUP_INSTALL_NAME}ConfigVersion.cmake"
DESTINATION ${_install_dir}
)
# Install cmake targets files
install(EXPORT ${QMSETUP_INSTALL_NAME}Targets
FILE "${QMSETUP_INSTALL_NAME}Targets.cmake"
NAMESPACE qmsetup::
DESTINATION ${_install_dir}
)
# Install headers
install(DIRECTORY ../include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
)
# Install cmake modules
install(DIRECTORY ../cmake/
DESTINATION ${_install_dir}/cmake
)
endif()

View File

@@ -0,0 +1,89 @@
project(qmcorecmd VERSION ${QMSETUP_VERSION})
string(TIMESTAMP _year "%Y")
set(RC_COPYRIGHT "Copyright 2023-${_year} Stdware Collections")
set(RC_DESCRIPTION "QMSetup Core Utility Command")
set(_src
main.cpp
sha-256.h
sha-256.cpp
utils.h
utils.cpp
)
if(WIN32)
list(APPEND _src utils_win.cpp)
else()
list(APPEND _src utils_unix.cpp)
endif()
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline::syscmdline)
# Add features
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20 cxx_std_17)
set_target_properties(${PROJECT_NAME} PROPERTIES
CXX_EXTENSIONS OFF
CXX_STANDARD 17
CXX_STANDARD_REQUIRED ON
)
# Compat with gcc 8
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9")
target_link_libraries(${PROJECT_NAME} PRIVATE stdc++fs)
endif()
if(WIN32)
target_link_libraries(${PROJECT_NAME} PRIVATE shlwapi)
target_compile_definitions(${PROJECT_NAME} PRIVATE _CRT_SECURE_NO_WARNINGS)
endif()
if(WIN32)
qm_add_win_rc(${PROJECT_NAME}
COPYRIGHT ${RC_COPYRIGHT}
DESCRIPTION ${RC_DESCRIPTION}
)
# Make sure Windows UAC does not believe it is an installer
qm_add_win_manifest(${PROJECT_NAME}
DESCRIPTION ${RC_DESCRIPTION}
)
endif()
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_DESC="${RC_DESCRIPTION}, Version ${QMSETUP_VERSION}")
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_COPYRIGHT="${RC_COPYRIGHT}, checkout https://github.com/stdware/qmsetup")
target_compile_definitions(${PROJECT_NAME} PRIVATE TOOL_VERSION="${QMSETUP_VERSION}")
set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME corecmd)
# Vcpkg requires the binary to be installed in "tools/${PORT}" directory
if(QMSETUP_VCPKG_TOOLS_HINT)
set(_tools_dir tools/${QMSETUP_INSTALL_NAME})
else()
set(_tools_dir ${CMAKE_INSTALL_BINDIR})
endif()
install(TARGETS ${PROJECT_NAME}
EXPORT ${QMSETUP_INSTALL_NAME}Targets
RUNTIME DESTINATION "${_tools_dir}" OPTIONAL
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
)
# Check patchelf
if (NOT WIN32 AND NOT APPLE)
execute_process(
COMMAND patchelf --version
OUTPUT_VARIABLE _patchelf_version_output
ERROR_QUIET
OUTPUT_STRIP_TRAILING_WHITESPACE
)
if(NOT "${_patchelf_version_output}" MATCHES "patchelf")
message(WARNING "Patchelf not found, the deploy feature won't work.")
else()
string(REGEX REPLACE "patchelf (.+)" "\\1" _patchelf_version ${_patchelf_version_output})
message(STATUS "Found patchelf, version ${_patchelf_version}")
endif()
endif()

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,210 @@
#include "sha-256.h"
#include <cstring>
#include <string>
static constexpr const auto CHUNK_SIZE = 64;
static constexpr const auto TOTAL_LEN_LEN = 8;
/*
* ABOUT bool: this file does not use bool in order to be as pre-C99 compatible as possible.
*/
/*
* Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.
* When useful for clarification, portions of the pseudo-code are reproduced here too.
*/
/*
* Initialize array of round constants:
* (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):
*/
static constexpr const uint32_t k[] = {
0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2
};
struct buffer_state {
const uint8_t * p;
size_t len;
size_t total_len;
int single_one_delivered; /* bool */
int total_len_delivered; /* bool */
};
static inline constexpr uint32_t right_rot(uint32_t value, unsigned int count)
{
/*
* Defined behaviour in standard C for all count where 0 < count < 32,
* which is what we need here.
*/
return value >> count | value << (32 - count);
}
static void init_buf_state(struct buffer_state * state, const void * input, size_t len)
{
state->p = static_cast<const uint8_t*>(input);
state->len = len;
state->total_len = len;
state->single_one_delivered = 0;
state->total_len_delivered = 0;
}
/* Return value: bool */
static int calc_chunk(uint8_t chunk[CHUNK_SIZE], struct buffer_state * state)
{
size_t space_in_chunk;
if (state->total_len_delivered) {
return 0;
}
if (state->len >= CHUNK_SIZE) {
std::memcpy(chunk, state->p, CHUNK_SIZE);
state->p += CHUNK_SIZE;
state->len -= CHUNK_SIZE;
return 1;
}
std::memcpy(chunk, state->p, state->len);
chunk += state->len;
space_in_chunk = CHUNK_SIZE - state->len;
state->p += state->len;
state->len = 0;
/* If we are here, space_in_chunk is one at minimum. */
if (!state->single_one_delivered) {
*chunk++ = 0x80;
space_in_chunk -= 1;
state->single_one_delivered = 1;
}
/*
* Now:
* - either there is enough space left for the total length, and we can conclude,
* - or there is too little space left, and we have to pad the rest of this chunk with zeroes.
* In the latter case, we will conclude at the next invokation of this function.
*/
if (space_in_chunk >= TOTAL_LEN_LEN) {
const size_t left = space_in_chunk - TOTAL_LEN_LEN;
size_t len = state->total_len;
int i;
std::memset(chunk, 0x00, left);
chunk += left;
/* Storing of len * 8 as a big endian 64-bit without overflow. */
chunk[7] = (uint8_t) (len << 3);
len >>= 5;
for (i = 6; i >= 0; i--) {
chunk[i] = (uint8_t) len;
len >>= 8;
}
state->total_len_delivered = 1;
} else {
std::memset(chunk, 0x00, space_in_chunk);
}
return 1;
}
/*
* Limitations:
* - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem
* for large data sizes.
* - SHA algorithms theoretically operate on bit strings. However, this implementation has no support
* for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes.
* In particular, the len parameter is a number of bytes.
*/
void calc_sha_256(uint8_t hash[32], const void * input, size_t len)
{
/*
* Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32.
* Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 = i = 63
* Note 3: The compression function uses 8 working variables, a through h
* Note 4: Big-endian convention is used when expressing the constants in this pseudocode,
* and when parsing message block data from bytes to words, for example,
* the first word of the input message "abc" after padding is 0x61626380
*/
/*
* Initialize hash values:
* (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):
*/
uint32_t h[] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };
int i, j;
/* 512-bit chunks is what we will operate on. */
uint8_t chunk[64];
struct buffer_state state;
init_buf_state(&state, input, len);
while (calc_chunk(chunk, &state)) {
uint32_t ah[8];
/*
* create a 64-entry message schedule array w[0..63] of 32-bit words
* (The initial values in w[0..63] don't matter, so many implementations zero them here)
* copy chunk into first 16 words w[0..15] of the message schedule array
*/
uint32_t w[64];
const uint8_t *p = chunk;
std::memset(w, 0x00, sizeof w);
for (i = 0; i < 16; i++) {
w[i] = (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 |
(uint32_t) p[2] << 8 | (uint32_t) p[3];
p += 4;
}
/* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */
for (i = 16; i < 64; i++) {
const uint32_t s0 = right_rot(w[i - 15], 7) ^ right_rot(w[i - 15], 18) ^ (w[i - 15] >> 3);
const uint32_t s1 = right_rot(w[i - 2], 17) ^ right_rot(w[i - 2], 19) ^ (w[i - 2] >> 10);
w[i] = w[i - 16] + s0 + w[i - 7] + s1;
}
/* Initialize working variables to current hash value: */
for (i = 0; i < 8; i++)
ah[i] = h[i];
/* Compression function main loop: */
for (i = 0; i < 64; i++) {
const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);
const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);
const uint32_t temp1 = ah[7] + s1 + ch + k[i] + w[i];
const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);
const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);
const uint32_t temp2 = s0 + maj;
ah[7] = ah[6];
ah[6] = ah[5];
ah[5] = ah[4];
ah[4] = ah[3] + temp1;
ah[3] = ah[2];
ah[2] = ah[1];
ah[1] = ah[0];
ah[0] = temp1 + temp2;
}
/* Add the compressed chunk to the current hash value: */
for (i = 0; i < 8; i++)
h[i] += ah[i];
}
/* Produce the final hash value (big-endian): */
for (i = 0, j = 0; i < 8; i++)
{
hash[j++] = (uint8_t) (h[i] >> 24);
hash[j++] = (uint8_t) (h[i] >> 16);
hash[j++] = (uint8_t) (h[i] >> 8);
hash[j++] = (uint8_t) h[i];
}
}

View File

@@ -0,0 +1,19 @@
#ifndef SHA_256_H
#define SHA_256_H
// https://github.com/notepad-plus-plus/notepad-plus-plus/blob/master/PowerEditor/src/MISC/sha2
#include <cstddef>
#include <cstdint>
#ifdef __cplusplus
extern "C" {
#endif
void calc_sha_256(uint8_t hash[32], const void *input, size_t len);
#ifdef __cplusplus
}
#endif
#endif // SHA_256_H

View File

@@ -0,0 +1,31 @@
#include "utils.h"
namespace fs = std::filesystem;
#ifdef _WIN32
# define _TSTR(X) L##X
# define tstrcmp wcscmp
#else
# define _TSTR(X) X
# define tstrcmp strcmp
#endif
namespace Utils {
std::filesystem::path cleanPath(const std::filesystem::path &path) {
fs::path result;
for (const auto &part : path) {
if (part == _TSTR("..")) {
if (!result.empty() && result.filename() != _TSTR("..")) {
result = result.parent_path();
} else {
result /= part;
}
} else if (part != _TSTR(".")) {
result /= part;
}
}
return result;
}
}

View File

@@ -0,0 +1,158 @@
#ifndef UTILS_H
#define UTILS_H
#include <string>
#include <vector>
#include <filesystem>
#include <chrono>
#include <algorithm>
#include <cctype>
#include <vector>
namespace Utils {
// Filesystem Utils
struct FileTime {
std::chrono::system_clock::time_point accessTime;
std::chrono::system_clock::time_point modifyTime;
std::chrono::system_clock::time_point statusChangeTime; // Creation time on Windows
};
FileTime fileTime(const std::filesystem::path &path);
void setFileTime(const std::filesystem::path &path, const FileTime &times);
inline void syncFileTime(const std::filesystem::path &dest, const std::filesystem::path &src) {
setFileTime(dest, fileTime(src));
}
std::vector<std::filesystem::path> getPathsFromEnv();
std::filesystem::path cleanPath(const std::filesystem::path &path);
// String Utils
template <class T>
std::vector<std::basic_string<T>> split(const std::basic_string<T> &s,
const std::basic_string<T> &delimiter) {
std::vector<std::basic_string<T>> tokens;
typename std::basic_string<T>::size_type start = 0;
typename std::basic_string<T>::size_type end = s.find(delimiter);
while (end != std::basic_string<T>::npos) {
tokens.push_back(s.substr(start, end - start));
start = end + delimiter.size();
end = s.find(delimiter, start);
}
tokens.push_back(s.substr(start));
return tokens;
}
template <class T>
std::basic_string<T> trim(const std::basic_string<T> &str) {
auto start = str.begin();
while (start != str.end() && std::isspace(*start)) {
start++;
}
auto end = str.end();
do {
end--;
} while (std::distance(start, end) > 0 && std::isspace(*end));
return {start, end + 1};
}
template <class T>
void replaceString(std::basic_string<T> &s, const std::basic_string<T> &pattern,
const std::basic_string<T> &text) {
size_t idx = 0;
while ((idx = s.find(pattern, idx)) != std::basic_string<T>::npos) {
s.replace(idx, pattern.size(), text);
idx += text.size();
}
};
template <class T>
std::basic_string<T> join(const std::vector<std::basic_string<T>> &v,
const std::basic_string<T> &delimiter) {
if (v.empty())
return {};
std::basic_string<T> res;
for (int i = 0; i < v.size() - 1; ++i) {
res.append(v[i]);
res.append(delimiter);
}
res.append(v.back());
return res;
}
template <class T>
std::basic_string<T> toLower(std::basic_string<T> s) {
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
return s;
}
template <class T>
std::basic_string<T> toUpper(std::basic_string<T> s) {
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
return s;
}
inline bool starts_with(const std::string_view &s, const std::string_view &prefix) {
#if __cplusplus >= 202002L
return s.starts_with(prefix);
#else
return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
#endif
}
inline bool starts_with(const std::wstring_view &s, const std::wstring_view &prefix) {
#if __cplusplus >= 202002L
return s.starts_with(prefix);
#else
return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
#endif
}
template <class Container, class T>
inline bool contains(const Container &container, const T &key) {
#if __cplusplus >= 202002L
return container.contains(key);
#else
return container.count(key) != 0;
#endif
}
// OS Utils
#ifdef _WIN32
std::wstring executeCommand(const std::wstring &command, const std::vector<std::wstring> &args);
std::vector<std::wstring>
resolveWinBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed);
#else
std::string executeCommand(const std::string &command, const std::vector<std::string> &args);
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths);
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed = nullptr);
#endif
#ifdef __APPLE__
std::vector<std::string> getMacAbsoluteDependencies(const std::string &file);
void replaceMacFileDependencies(
const std::string &file, const std::vector<std::pair<std::string, std::string>> &depPairs);
#elif defined(__linux__)
std::string getInterpreter(const std::string &file);
bool setFileInterpreter(const std::string &file, const std::string &interpreter);
#endif
}
#endif // UTILS_H

View File

@@ -0,0 +1,500 @@
#include "utils.h"
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>
#include <utime.h>
#include <cstring>
#include <filesystem>
#include <regex>
#include <set>
#include <sstream>
namespace fs = std::filesystem;
namespace Utils {
std::string executeCommand(const std::string &command, const std::vector<std::string> &args) {
// printf("Executing command: %s", command.data());
// for (const auto &arg : args) {
// printf(" %s", arg.data());
// }
// printf("\n");
static const auto &dupStr = [](char *&dest, const char *src, size_t size) {
dest = new char[size + 1];
memcpy(dest, src, size);
dest[size] = '\0';
};
enum PipeNum {
PN_Read,
PN_Write,
};
// Create pipe
int pipefd[2];
if (pipe(pipefd) == -1) {
throw std::runtime_error("failed to call \"pipe\"");
}
pid_t pid = fork();
if (pid < 0) {
// Close pipes right away
close(pipefd[PN_Read]);
close(pipefd[PN_Write]);
throw std::runtime_error("failed to call \"fork\"");
}
if (pid == 0) {
// --------
// Child process
// --------
// Close read pipe
close(pipefd[PN_Read]);
// Redirect "stdout" and "stderr" to write pipe
dup2(pipefd[PN_Write], STDOUT_FILENO);
dup2(pipefd[PN_Write], STDERR_FILENO);
// Prepare arguments
auto argv = new char *[args.size() + 2]; // +2 for command and nullptr
dupStr(argv[0], command.data(), command.size());
for (size_t i = 0; i < args.size(); ++i) {
dupStr(argv[i + 1], args[i].data(), args[i].size());
}
argv[args.size() + 1] = nullptr;
// Call "exec"
execvp(argv[0], argv);
// If the control flow reaches here, there must be a mistake
// Clean allocated memory
for (size_t i = 0; i < args.size() + 2; ++i) {
delete[] argv[i];
}
delete[] argv;
// Fail
printf("exec \"%s\" failed: %s\n", command.data(), strerror(errno));
// No need to close write pipe, let the System handle everything
// Simply exit
exit(EXIT_FAILURE);
}
// --------
// Parent process
// --------
// Close write pipe
close(pipefd[PN_Write]);
// Read child process output
char buffer[256];
std::string output;
ssize_t bytesRead;
while ((bytesRead = read(pipefd[0], buffer, sizeof(buffer) - 1)) > 0) {
buffer[bytesRead] = '\0';
output += buffer;
}
// Close read pipe
close(pipefd[PN_Read]);
// Wait for child process to terminate
int status;
waitpid(pid, &status, 0);
// Check exit status
if (WIFEXITED(status)) {
auto exitCode = WEXITSTATUS(status);
if (exitCode == 0)
return output;
// Throw error
throw std::runtime_error(trim(output));
}
if (WIFSIGNALED(status)) {
auto sig = WTERMSIG(status);
throw std::runtime_error("command \"" + command + "\" was terminated by signal " +
std::to_string(sig));
}
throw std::runtime_error("command \"" + command + "\" terminated abnormally with status " +
std::to_string(status));
}
FileTime fileTime(const fs::path &path) {
struct stat sb;
if (stat(path.c_str(), &sb) == -1) {
throw std::runtime_error("failed to get file time: \"" + path.string() + "\"");
}
FileTime times;
times.accessTime = std::chrono::system_clock::from_time_t(sb.st_atime);
times.modifyTime = std::chrono::system_clock::from_time_t(sb.st_mtime);
times.statusChangeTime = std::chrono::system_clock::from_time_t(sb.st_ctime);
return times;
}
void setFileTime(const fs::path &path, const FileTime &times) {
struct utimbuf new_times;
new_times.actime = std::chrono::system_clock::to_time_t(times.accessTime);
new_times.modtime = std::chrono::system_clock::to_time_t(times.modifyTime);
if (utime(path.c_str(), &new_times) != 0) {
throw std::runtime_error("failed to set file time: \"" + path.string() + "\"");
}
}
std::vector<fs::path> getPathsFromEnv() {
const char *pathEnv = std::getenv("PATH");
if (pathEnv == nullptr) {
return {};
}
std::string pathStr = pathEnv;
std::stringstream ss(pathStr);
std::string item;
std::vector<fs::path> paths;
while (std::getline(ss, item, ':')) {
if (!item.empty()) {
paths.push_back(fs::path(item));
}
}
return paths;
}
#ifdef __APPLE__
// Mac
// Use `otool` and `install_name_tool`
static std::vector<std::string> readMacBinaryRPaths(const std::string &path) {
std::vector<std::string> rpaths;
std::string output;
try {
output = executeCommand("otool", {"-l", path});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get RPATHs: " + std::string(e.what()));
}
static const std::regex rpathRegex(R"(\s*path\s+(.*)\s+\(offset.*)");
std::istringstream iss(output);
std::string line;
std::smatch match;
while (std::getline(iss, line)) {
if (line.find("cmd LC_RPATH") != std::string::npos) {
// skip 2 lines
std::getline(iss, line);
std::getline(iss, line);
if (std::regex_match(line, match, rpathRegex) && match.size() >= 2) {
rpaths.emplace_back(match[1].str());
}
}
}
return rpaths;
}
static std::vector<std::string> readMacBinaryDependencies(const std::string &path) {
std::vector<std::string> dependencies;
std::string output;
// Get dependencies
try {
output = executeCommand("otool", {"-L", path});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get dependencies: " + std::string(e.what()));
}
static const std::regex depRegex(
R"(^\t(.+) \(compatibility version (\d+\.\d+\.\d+), current version (\d+\.\d+\.\d+)(, weak)?\)$)");
std::istringstream iss(output);
std::string line;
std::smatch match;
// skip first line
std::getline(iss, line);
const std::string &loaderPath = path;
while (std::getline(iss, line)) {
if (std::regex_search(line, match, depRegex) && match.size() >= 2) {
std::string dep = match[1].str();
dependencies.emplace_back(dep);
}
}
return dependencies;
}
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
auto rpaths = readMacBinaryRPaths(path);
for (const auto &item : searchingPaths) {
rpaths.push_back(item);
}
auto dependencies = readMacBinaryDependencies(path);
const std::string &loaderPath = fs::canonical(path).parent_path();
std::vector<std::string> res;
for (auto dep : std::as_const(dependencies)) {
// Replace @executable_path and @loader_path
replaceString(dep, std::string("@executable_path"), loaderPath);
replaceString(dep, std::string("@loader_path"), loaderPath);
// Find dependency
std::string target = dep;
if (dep.find("@rpath") != std::string::npos) {
for (auto rpath : rpaths) {
// Replace again
replaceString(rpath, std::string("@executable_path"), loaderPath);
replaceString(rpath, std::string("@loader_path"), loaderPath);
std::string fullPath = dep;
replaceString(fullPath, std::string("@rpath"), rpath);
if (fs::exists(fullPath)) {
target = fullPath;
break;
}
}
}
target = cleanPath(target);
if (fs::exists(target)) {
if (fs::canonical(target).filename() == fs::canonical(path).filename())
continue;
res.push_back(target);
} else if (unparsed) {
unparsed->push_back(target);
}
}
return res;
}
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths) {
// Remove rpaths
do {
auto rpaths = readMacBinaryRPaths(file);
if (rpaths.empty())
break;
std::vector<std::string> args;
args.reserve(rpaths.size() * 2 + 1);
for (const auto &rpath : std::as_const(rpaths)) {
args.push_back("-delete_rpath");
args.push_back(rpath);
}
args.push_back(file);
try {
std::ignore = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to remove rpaths: " + std::string(e.what()));
}
} while (false);
// Add rpaths
if (!paths.empty()) {
std::set<std::string> visited;
std::vector<std::string> args;
args.reserve(paths.size() * 2 + 1);
for (const auto &rpath : std::as_const(paths)) {
if (Utils::contains(visited, rpath))
continue;
visited.insert(rpath);
args.push_back("-add_rpath");
args.push_back(rpath);
}
args.push_back(file);
try {
std::ignore = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to add rpaths: " + std::string(e.what()));
}
}
// Remove code signature if it exists
try {
std::ignore = executeCommand("codesign", {"--remove-signature", file});
std::ignore = executeCommand("codesign", {"-s", "-", file});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to resign: " + std::string(e.what()));
}
}
std::vector<std::string> getMacAbsoluteDependencies(const std::string &file) {
auto deps = readMacBinaryDependencies(file);
std::vector<std::string> res;
for (const auto &dep : std::as_const(deps)) {
if (fs::path(dep).is_absolute()) {
res.push_back(dep);
}
}
return res;
}
void replaceMacFileDependencies(
const std::string &file, const std::vector<std::pair<std::string, std::string>> &depPairs) {
std::string output;
std::vector<std::string> args;
args.reserve(depPairs.size() * 3 + 1);
std::string id;
for (const auto &pair : depPairs) {
if (fs::exists(pair.first) &&
fs::canonical(pair.first).filename() == fs::canonical(file).filename()) {
id = pair.second;
continue;
}
args.push_back("-change");
args.push_back(pair.first);
args.push_back(pair.second);
}
if (!id.empty()) {
args.push_back("-id");
args.push_back(id);
}
args.push_back(file);
try {
output = executeCommand("install_name_tool", args);
} catch (const std::exception &e) {
throw std::runtime_error("Failed to replace dependency: " + std::string(e.what()));
}
}
#else
// Linux
// Use `ldd` and `patchelf`
static std::vector<std::string>
readLddOutput(const std::string &fileName,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
std::string output;
try {
output = executeCommand("ldd", {fileName});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to get dependencies: " + std::string(e.what()));
}
std::istringstream iss(output);
std::string line;
static const std::regex regexp("^\\s*.+ => (.+) \\(.*");
static const std::regex regexp2("^\\s*(.+) => not found");
std::vector<std::string> dependencies;
while (std::getline(iss, line)) {
std::smatch match;
if (std::regex_match(line, match, regexp) && match.size() >= 2) {
dependencies.push_back(cleanPath(match[1].str()));
} else if (std::regex_match(line, match, regexp2) && match.size() >= 2) {
// Search in search paths
fs::path target;
for (const auto &item : searchingPaths) {
auto fullPath = item / match[1].str();
if (fs::exists(fullPath)) {
target = fullPath;
break;
}
}
if (!target.empty()) {
dependencies.push_back(target);
} else {
unparsed->push_back(cleanPath(match[1].str()));
}
}
}
return dependencies;
}
std::vector<std::string>
resolveUnixBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
return readLddOutput(path, searchingPaths, unparsed);
}
void setFileRPaths(const std::string &file, const std::vector<std::string> &paths) {
if (paths.empty()) {
try {
std::ignore = executeCommand("patchelf", {
"--remove-rpath",
file,
});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to remove rpaths: " + std::string(e.what()));
}
return;
}
try {
std::ignore = executeCommand("patchelf", {
"--set-rpath",
join(paths, std::string(":")),
file,
});
} catch (const std::exception &e) {
throw std::runtime_error("Failed to replace rpaths: " + std::string(e.what()));
}
}
#endif
#ifdef __linux__
static inline bool errorIsInterpNotFound(const std::string &what) {
return what.find("cannot find section '.interp'") != std::string::npos;
};
std::string getInterpreter(const std::string &file) {
std::string output;
try {
output = executeCommand("patchelf", {
"--print-interpreter",
file,
});
} catch (const std::exception &e) {
if (errorIsInterpNotFound(e.what()))
return {};
throw std::runtime_error("Failed to get interpreter: " + std::string(e.what()));
}
output = trim(output);
replaceString(output, std::string("$ORIGIN"), fs::canonical(file).parent_path().string());
return output;
}
bool setFileInterpreter(const std::string &file, const std::string &interpreter) {
try {
std::ignore = executeCommand("patchelf", {
"--set-interpreter",
interpreter,
file,
});
} catch (const std::exception &e) {
if (errorIsInterpNotFound(e.what()))
return false;
throw std::runtime_error("Failed to set interpreter: " + std::string(e.what()));
}
return true;
}
#endif
}

View File

@@ -0,0 +1,500 @@
#include "utils.h"
#include <shlwapi.h>
#include <delayimp.h>
#include <windows.h>
#include <algorithm>
#include <sstream>
#include <filesystem>
#include <stdexcept>
#include <utility>
#include <syscmdline/system.h>
namespace SCL = SysCmdLine;
namespace fs = std::filesystem;
namespace Utils {
static constexpr const DWORD g_EnglishLangId = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US);
static std::wstring winErrorMessage(uint32_t error, bool nativeLanguage = true) {
std::wstring rc;
wchar_t *lpMsgBuf;
DWORD len = ::FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, error, nativeLanguage ? 0 : g_EnglishLangId,
reinterpret_cast<LPWSTR>(&lpMsgBuf), 0, NULL);
if (len) {
// Remove tail line breaks
if (lpMsgBuf[len - 1] == L'\n') {
lpMsgBuf[len - 1] = L'\0';
if (len > 2 && lpMsgBuf[len - 2] == L'\r') {
lpMsgBuf[len - 2] = L'\0';
}
}
rc = std::wstring(lpMsgBuf, int(len));
::LocalFree(lpMsgBuf);
} else {
rc += L"unknown error";
}
return rc;
}
static std::wstring quoteAndEscapeArg(const std::wstring &arg) {
if (arg.find_first_of(L" \t\"") == std::wstring::npos) {
// No need to quote or escape
return arg;
}
std::wstring escapedArg = L"\"";
for (auto ch : arg) {
if (ch == L'"') {
// Escape double quotes
escapedArg += L"\"\"";
} else {
escapedArg += ch;
}
}
escapedArg += L"\"";
return escapedArg;
}
std::wstring executeCommand(const std::wstring &command,
const std::vector<std::wstring> &args) {
SECURITY_ATTRIBUTES sa;
HANDLE hReadPipe, hWritePipe;
STARTUPINFOW si;
PROCESS_INFORMATION pi;
DWORD read;
wchar_t buffer[4096]; // For captured output
// Initialize security attributes to allow pipe handles to be inherited.
sa.nLength = sizeof(SECURITY_ATTRIBUTES);
sa.bInheritHandle = TRUE;
sa.lpSecurityDescriptor = NULL;
// Create a pipe for the child process's STDOUT.
if (!::CreatePipe(&hReadPipe, &hWritePipe, &sa, 0)) {
throw std::runtime_error("failed to call \"CreatePipe\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Ensure the read handle to the pipe for STDOUT is not inherited.
if (!::SetHandleInformation(hReadPipe, HANDLE_FLAG_INHERIT, 0)) {
::CloseHandle(hWritePipe);
::CloseHandle(hReadPipe);
throw std::runtime_error("failed to call \"SetHandleInformation\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Set up members of the STARTUPINFO structure.
ZeroMemory(&si, sizeof(STARTUPINFO));
si.cb = sizeof(STARTUPINFO);
si.hStdError = hWritePipe;
si.hStdOutput = hWritePipe;
si.dwFlags |= STARTF_USESTDHANDLES;
// Construct command line
std::wstringstream cmdLineStream;
cmdLineStream << quoteAndEscapeArg(command) << L" ";
for (const auto &arg : args) {
cmdLineStream << quoteAndEscapeArg(arg) << L" ";
}
std::wstring cmdLine = cmdLineStream.str();
// Start the child process.
if (!::CreateProcessW(NULL, cmdLine.data(), NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
::CloseHandle(hWritePipe);
::CloseHandle(hReadPipe);
throw std::runtime_error("failed to call \"CreateProcess\": " +
SCL::wideToUtf8(winErrorMessage(::GetLastError())));
}
// Close the write end of the pipe before reading from the read end of the pipe.
::CloseHandle(hWritePipe);
// Read output from the child process's pipe for STDOUT
// and write to the parent process's STDOUT.
std::wstring output;
while (true) {
if (!::ReadFile(hReadPipe, buffer, sizeof(buffer) - sizeof(wchar_t), &read, NULL) ||
read == 0)
break;
buffer[read / sizeof(wchar_t)] = L'\0';
output += buffer;
}
// Wait until child process exits.
::WaitForSingleObject(pi.hProcess, INFINITE);
DWORD exitCode;
::GetExitCodeProcess(pi.hProcess, &exitCode);
// Close process and thread handles and the read end of the pipe.
::CloseHandle(pi.hProcess);
::CloseHandle(pi.hThread);
::CloseHandle(hReadPipe);
if (exitCode == 0)
return output;
throw std::runtime_error(SCL::wideToUtf8(output));
}
// Helper functions to convert between FILETIME and std::chrono::system_clock::time_point
static std::chrono::system_clock::time_point filetime_to_timepoint(const FILETIME &ft) {
// Windows file time starts from January 1, 1601
// std::chrono::system_clock starts from January 1, 1970
static constexpr const long long WIN_EPOCH =
116444736000000000LL; // in hundreds of nanoseconds
long long duration = (static_cast<long long>(ft.dwHighDateTime) << 32) + ft.dwLowDateTime;
duration -= WIN_EPOCH; // convert to Unix epoch
return std::chrono::system_clock::from_time_t(duration / 10000000LL);
}
static FILETIME timepoint_to_filetime(const std::chrono::system_clock::time_point &tp) {
FILETIME ft;
static constexpr const long long WIN_EPOCH =
116444736000000000LL; // in hundreds of nanoseconds
long long duration =
std::chrono::duration_cast<std::chrono::microseconds>(tp.time_since_epoch()).count();
duration = duration * 10 + WIN_EPOCH;
ft.dwLowDateTime = static_cast<DWORD>(duration & 0xFFFFFFFF);
ft.dwHighDateTime = static_cast<DWORD>((duration >> 32) & 0xFFFFFFFF);
return ft;
}
FileTime fileTime(const fs::path &path) {
HANDLE hFile = ::CreateFileW(path.wstring().data(), GENERIC_READ, FILE_SHARE_READ, nullptr,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
throw std::runtime_error("invalid path: \"" + SCL::wideToUtf8(path.wstring()) + "\"");
}
FILETIME creationTime, lastAccessTime, lastWriteTime;
if (!::GetFileTime(hFile, &creationTime, &lastAccessTime, &lastWriteTime)) {
::CloseHandle(hFile);
throw std::runtime_error("failed to get file time: \"" +
SCL::wideToUtf8(path.wstring()) + "\"");
}
::CloseHandle(hFile);
FileTime times;
// ... (convert FILETIMEs to std::chrono::system_clock::time_point and store in times)
times.accessTime = filetime_to_timepoint(lastAccessTime);
times.modifyTime = filetime_to_timepoint(lastWriteTime);
times.statusChangeTime = filetime_to_timepoint(creationTime);
return times;
}
void setFileTime(const fs::path &path, const FileTime &times) {
HANDLE hFile = ::CreateFileW(path.wstring().data(), FILE_WRITE_ATTRIBUTES, FILE_SHARE_WRITE,
nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
if (hFile == INVALID_HANDLE_VALUE) {
throw std::runtime_error("invalid path: \"" + SCL::wideToUtf8(path.wstring()) + "\"");
}
FILETIME creationTime, lastAccessTime, lastWriteTime;
lastAccessTime = timepoint_to_filetime(times.accessTime);
lastWriteTime = timepoint_to_filetime(times.modifyTime);
creationTime = timepoint_to_filetime(times.statusChangeTime);
if (!::SetFileTime(hFile, &creationTime, &lastAccessTime, &lastWriteTime)) {
::CloseHandle(hFile);
throw std::runtime_error("failed to set file time: \"" +
SCL::wideToUtf8(path.wstring()) + "\"");
}
::CloseHandle(hFile);
}
std::vector<std::filesystem::path> getPathsFromEnv() {
DWORD length = ::GetEnvironmentVariableW(L"PATH", nullptr, 0);
if (length == 0) {
return {};
}
auto buf = new wchar_t[length];
::GetEnvironmentVariableW(L"PATH", buf, length);
std::wstring pathStr = buf;
delete[] buf;
std::wstringstream wss(pathStr);
std::wstring item;
std::vector<fs::path> paths;
while (std::getline(wss, item, L';')) {
if (!item.empty()) {
paths.push_back(item);
}
}
return paths;
}
// ================================================================================
// Modified from windeployqt 5.15.2(Copyright Qt company)
// ================================================================================
namespace WindowsDeployQt {
static inline std::string stringFromRvaPtr(const void *rvaPtr) {
return static_cast<const char *>(rvaPtr);
}
// Helper for reading out PE executable files: Find a section header for an RVA
// (IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32).
template <class ImageNtHeader>
static const IMAGE_SECTION_HEADER *findSectionHeader(DWORD rva,
const ImageNtHeader *nTHeader) {
const IMAGE_SECTION_HEADER *section = IMAGE_FIRST_SECTION(nTHeader);
const IMAGE_SECTION_HEADER *sectionEnd =
section + nTHeader->FileHeader.NumberOfSections;
for (; section < sectionEnd; ++section)
if (rva >= section->VirtualAddress &&
rva < (section->VirtualAddress + section->Misc.VirtualSize))
return section;
return 0;
}
// Helper for reading out PE executable files: convert RVA to pointer (IMAGE_NT_HEADERS64,
// IMAGE_NT_HEADERS32).
template <class ImageNtHeader>
inline const void *rvaToPtr(DWORD rva, const ImageNtHeader *nTHeader,
const void *imageBase) {
const IMAGE_SECTION_HEADER *sectionHdr = findSectionHeader(rva, nTHeader);
if (!sectionHdr)
return 0;
const DWORD delta = sectionHdr->VirtualAddress - sectionHdr->PointerToRawData;
return static_cast<const char *>(imageBase) + rva - delta;
}
// Helper for reading out PE executable files: return word size of a IMAGE_NT_HEADERS64,
// IMAGE_NT_HEADERS32
template <class ImageNtHeader>
static unsigned ntHeaderWordSize(const ImageNtHeader *header) {
// defines IMAGE_NT_OPTIONAL_HDR32_MAGIC, IMAGE_NT_OPTIONAL_HDR64_MAGIC
enum { imageNtOptionlHeader32Magic = 0x10b, imageNtOptionlHeader64Magic = 0x20b };
if (header->OptionalHeader.Magic == imageNtOptionlHeader32Magic)
return 32;
if (header->OptionalHeader.Magic == imageNtOptionlHeader64Magic)
return 64;
return 0;
}
// Helper for reading out PE executable files: Retrieve the NT image header of an
// executable via the legacy DOS header.
static IMAGE_NT_HEADERS *getNtHeader(void *fileMemory, std::wstring *errorMessage) {
IMAGE_DOS_HEADER *dosHeader = static_cast<PIMAGE_DOS_HEADER>(fileMemory);
// Check DOS header consistency
if (IsBadReadPtr(dosHeader, sizeof(IMAGE_DOS_HEADER)) ||
dosHeader->e_magic != IMAGE_DOS_SIGNATURE) {
*errorMessage = L"DOS header check failed.";
return 0;
}
// Retrieve NT header
char *ntHeaderC = static_cast<char *>(fileMemory) + dosHeader->e_lfanew;
IMAGE_NT_HEADERS *ntHeaders = reinterpret_cast<IMAGE_NT_HEADERS *>(ntHeaderC);
// check NT header consistency
if (IsBadReadPtr(ntHeaders, sizeof(ntHeaders->Signature)) ||
ntHeaders->Signature != IMAGE_NT_SIGNATURE ||
IsBadReadPtr(&ntHeaders->FileHeader, sizeof(IMAGE_FILE_HEADER))) {
*errorMessage = L"NT header check failed.";
return 0;
}
// Check magic
if (!ntHeaderWordSize(ntHeaders)) {
std::wostringstream ss;
ss << "NT header check failed; magic " << ntHeaders->OptionalHeader.Magic
<< " is invalid.";
*errorMessage = ss.str();
return 0;
}
// Check section headers
IMAGE_SECTION_HEADER *sectionHeaders = IMAGE_FIRST_SECTION(ntHeaders);
if (IsBadReadPtr(sectionHeaders, ntHeaders->FileHeader.NumberOfSections *
sizeof(IMAGE_SECTION_HEADER))) {
*errorMessage = L"NT header section header check failed.";
return 0;
}
return ntHeaders;
}
// Helper for reading out PE executable files: Read out import sections from
// IMAGE_NT_HEADERS64, IMAGE_NT_HEADERS32.
template <class ImageNtHeader>
static std::vector<std::string> readImportSections(const ImageNtHeader *ntHeaders,
const void *base,
std::wstring *errorMessage) {
// Get import directory entry RVA and read out
const DWORD importsStartRVA =
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]
.VirtualAddress;
if (!importsStartRVA) {
*errorMessage = L"Failed to find IMAGE_DIRECTORY_ENTRY_IMPORT entry.";
return {};
}
const IMAGE_IMPORT_DESCRIPTOR *importDesc =
static_cast<const IMAGE_IMPORT_DESCRIPTOR *>(
rvaToPtr(importsStartRVA, ntHeaders, base));
if (!importDesc) {
*errorMessage = L"Failed to find IMAGE_IMPORT_DESCRIPTOR entry.";
return {};
}
std::vector<std::string> result;
for (; importDesc->Name; ++importDesc)
result.push_back(stringFromRvaPtr(rvaToPtr(importDesc->Name, ntHeaders, base)));
// Read delay-loaded DLLs, see http://msdn.microsoft.com/en-us/magazine/cc301808.aspx .
// Check on grAttr bit 1 whether this is the format using RVA's > VS 6
if (const DWORD delayedImportsStartRVA =
ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT]
.VirtualAddress) {
const ImgDelayDescr *delayedImportDesc = static_cast<const ImgDelayDescr *>(
rvaToPtr(delayedImportsStartRVA, ntHeaders, base));
for (; delayedImportDesc->rvaDLLName && (delayedImportDesc->grAttrs & 1);
++delayedImportDesc)
result.push_back(
stringFromRvaPtr(rvaToPtr(delayedImportDesc->rvaDLLName, ntHeaders, base)));
}
return result;
}
template <class ImageNtHeader>
static void determineDependentLibs(const ImageNtHeader *nth, const void *fileMemory,
bool isMinGW,
std::vector<std::string> *dependentLibrariesIn,
std::wstring *errorMessage) {
std::vector<std::string> dependentLibraries;
if (dependentLibrariesIn)
dependentLibraries = readImportSections(nth, fileMemory, errorMessage);
if (dependentLibrariesIn)
*dependentLibrariesIn = dependentLibraries;
}
// Read a PE executable and determine dependent libraries, word size.
bool readPeExecutable(const std::wstring &peExecutableFileName, std::wstring *errorMessage,
std::vector<std::string> *dependentLibrariesIn, unsigned *wordSizeIn,
bool isMinGW, unsigned short *machineArchIn) {
bool result = false;
HANDLE hFile = NULL;
HANDLE hFileMap = NULL;
void *fileMemory = 0;
if (dependentLibrariesIn)
dependentLibrariesIn->clear();
if (wordSizeIn)
*wordSizeIn = 0;
do {
// Create a memory mapping of the file
hFile = CreateFileW(peExecutableFileName.data(), GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE || hFile == NULL) {
std::wostringstream ss;
ss << L"Cannot open \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
hFileMap = CreateFileMappingW(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hFileMap == NULL) {
std::wostringstream ss;
ss << L"Cannot create file mapping of \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
fileMemory = MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (!fileMemory) {
std::wostringstream ss;
ss << L"Cannot map \"" << peExecutableFileName << L"\": "
<< winErrorMessage(::GetLastError());
*errorMessage = ss.str();
break;
}
const IMAGE_NT_HEADERS *ntHeaders = getNtHeader(fileMemory, errorMessage);
if (!ntHeaders)
break;
const unsigned wordSize = ntHeaderWordSize(ntHeaders);
if (wordSizeIn)
*wordSizeIn = wordSize;
if (wordSize == 32) {
determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS32 *>(ntHeaders),
fileMemory, isMinGW, dependentLibrariesIn, errorMessage);
} else {
determineDependentLibs(reinterpret_cast<const IMAGE_NT_HEADERS64 *>(ntHeaders),
fileMemory, isMinGW, dependentLibrariesIn, errorMessage);
}
if (machineArchIn)
*machineArchIn = ntHeaders->FileHeader.Machine;
result = true;
} while (false);
if (fileMemory)
UnmapViewOfFile(fileMemory);
if (hFileMap != NULL)
CloseHandle(hFileMap);
if (hFile != NULL && hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
return result;
}
}
std::vector<std::wstring>
resolveWinBinaryDependencies(const std::filesystem::path &path,
const std::vector<std::filesystem::path> &searchingPaths,
std::vector<std::string> *unparsed) {
std::wstring errorMessage;
std::vector<std::string> dependentLibrariesIn;
unsigned wordSizeIn;
bool isMinGW = false;
unsigned short machineArchIn;
if (!WindowsDeployQt::readPeExecutable(path, &errorMessage, &dependentLibrariesIn,
&wordSizeIn, isMinGW, &machineArchIn)) {
throw std::runtime_error(SCL::wideToUtf8(errorMessage));
}
// Search
std::vector<std::wstring> result;
for (const auto &item : std::as_const(dependentLibrariesIn)) {
fs::path fullPath;
for (const auto &dir : std::as_const(searchingPaths)) {
fs::path targetPath = dir / item;
if (fs::exists(targetPath)) {
fullPath = targetPath;
break;
}
}
if (!fullPath.empty()) {
result.push_back(fullPath);
continue;
}
if (unparsed) {
unparsed->push_back(item);
}
}
return result;
}
}

View File

@@ -0,0 +1,13 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
include("${CMAKE_CURRENT_LIST_DIR}/qmsetupTargets.cmake")
include("${CMAKE_CURRENT_LIST_DIR}/cmake/QMSetupAPI.cmake")
if ("All" IN_LIST qmsetup_FIND_COMPONENTS)
qm_import_all()
else()
qm_import(${qmsetup_FIND_COMPONENTS})
endif()

View File

@@ -0,0 +1,130 @@
# References:
# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
# https://code.qt.io/cgit/qt/qt5.git/tree/_clang-format
BasedOnStyle: LLVM
Standard: c++17
# 指针和引用的对齐方式。
# 可能的值有:
# PAS_Left (在配置中: Left) 指针左对齐。
# PAS_Right (在配置中: Right) 指针右对齐。
# PAS_Middle (在配置中: Middle) 指针中间对齐。
PointerAlignment: Right
# public/protected/private 等访问修饰符偏移量
AccessModifierOffset: -4
# 缩进长度
IndentWidth: 4
# 连续空行的最大数
MaxEmptyLinesToKeep: 999
# 在OC中的@property后面添加一个空格。例如使用“@property (readonly)”而不是“@property(readonly)”
ObjCSpaceAfterProperty: true
# OC块中所拍的字符数
ObjCBlockIndentWidth: 4
# 取决于值, 语句“int f() { return 0; }”可以被放到一个单行。
# 可能的值有:
# SFS_None (在配置中: None) 从不合并方法或函数到单独的一行。
# SFS_Empty (在配置中: Empty) 仅合并空的函数。
# SFS_Inline (在配置中: Inline) 仅合并类中定义的方法或函数. 意味着 “empty”.
# SFS_All (在配置中: All) 合并所有的方法适应单行.
AllowShortFunctionsOnASingleLine: None
# 如果为真true, 语句“if (a) return;” 能被放到单行。
AllowShortIfStatementsOnASingleLine: false
# 如果为真true, 对齐注释。
AlignTrailingComments: true
# 如果为真,对齐连续的宏定义
AlignConsecutiveMacros: true
# 如果为真true,将会在“[”之后和“]”之前插入空格。
SpacesInSquareBrackets: false
# 如果为真true, 将会在“(”之后和“)”之前插入空格。
SpacesInParentheses : false
# 如果为真true, 校准连续的声明。
# 这将会校准连续多行的声明的名字。这将会导致像下面这样的格式:
# int aaaa = 12;
# float b = 23;
# std::string ccc = 23;
AlignConsecutiveDeclarations: false
# 如果为真true连续调整多行
# 这将会调整连续行中的分配操作符。这将会导致像下面这样的格式:
# int aaaa = 12;
# int b = 23;
# int ccc = 23;
AlignConsecutiveAssignments: false
# 如果为假false移除分配操作符=)前空格。
SpaceBeforeAssignmentOperators: true
# 如果为真true, 将会在字面量容器中插入空格(例如 OC和Javascript的数组和字典字面量)。
SpacesInContainerLiterals: false
# 缩进case标签
IndentCaseLabels: true
# 如果表达式中包含函数调用,并且函数调用因为表达式太长被放到了下一行,是否缩进
IndentWrappedFunctionNames: true
# 如果为真true, 保持块的起始空行。
# true: false:
# if (foo) { vs. if (foo) {
# bar();
# bar(); }
# }
KeepEmptyLinesAtTheStartOfBlocks: true
# 允许所有参数都被放在下一行
AllowAllParametersOfDeclarationOnNextLine: false
# 使用C风格强制类型转换后是否在中间添加一个空格
SpaceAfterCStyleCast: true
# 在模板定义后换行
AlwaysBreakTemplateDeclarations: Yes
# Tab长度
TabWidth: 4
# 是否使用Tab
UseTab: Never
# 在括号后对齐参数
# someLongFunction(argument1,
# argument2);
AlignAfterOpenBracket: Align
# 名字空间内部缩进
NamespaceIndentation: All
# 一行最长列数
ColumnLimit: 100
# 按层次缩进宏定义
IndentPPDirectives: AfterHash
# 预处理语句缩进为 2
PPIndentWidth: 2
# 数组元素对齐
AlignArrayOfStructures: Left
# 不对头文件排序
SortIncludes: Never
FixNamespaceComments: false
StatementMacros: ['__qas_attr__', '__qas_exclude__', '__qas_include__']
ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH, forever, Q_FOREVER, QBENCHMARK, QBENCHMARK_ONCE ]

View File

@@ -0,0 +1,98 @@
# This file is used to ignore files which are generated
# ----------------------------------------------------------------------------
*~
*.log
*.autosave
*.a
*.core
*.moc
*.o
*.obj
*.orig
*.rej
*.so
*.so.*
*_pch.h.cpp
*_resource.rc
*.qm
.#*
*.*#
core
!core/
tags
.DS_Store
.directory
*.debug
Makefile*
*.prl
*.app
moc_*.cpp
ui_*.h
qrc_*.cpp
Thumbs.db
# *.res
# *.rc
/.qmake.cache
/.qmake.stash
# qtcreator generated files
*.pro.user*
# xemacs temporary files
*.flc
# Vim temporary files
.*.swp
# Visual Studio generated files
*.ib_pdb_index
*.idb
*.ilk
*.pdb
*.sln
*.suo
*.vcproj
*vcproj.*.*.user
*.ncb
*.sdf
*.opensdf
*.vcxproj
*vcxproj.*
# MinGW generated files
*.Debug
*.Release
# Python byte code
__pycache__
*.pyc
# Binaries
# --------
*.dll
*.exe
.DS_Store
*/.DS_Store
build-src*
build/
cmake-build*
*.user
*.lnk
_workingDir*
.vscode
.idea
.cache
cache
.vs
out/
CMakeSettings.json
# /vcpkg
/data
/*.natvis
*.sublime-*
setup-vcpkg.json
setup-vcpkg-temp*

View File

@@ -0,0 +1,165 @@
cmake_minimum_required(VERSION 3.17)
project(syscmdline VERSION 1.0.0.0 LANGUAGES C CXX)
# ----------------------------------
# Build Options
# ----------------------------------
option(SYSCMDLINE_BUILD_STATIC "Build static library" ON)
option(SYSCMDLINE_BUILD_EXAMPLES "Build examples" OFF)
option(SYSCMDLINE_BUILD_TESTS "Build test cases" OFF)
option(SYSCMDLINE_FORCE_VALIDITY_CHECK "Force to enable validity check" OFF)
option(SYSCMDLINE_INSTALL "Install library" ON)
# ----------------------------------
# CMake Settings
# ----------------------------------
if(NOT DEFINED CMAKE_RUNTIME_OUTPUT_DIRECTORY)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
endif()
if(NOT DEFINED CMAKE_LIBRARY_OUTPUT_DIRECTORY)
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_ARCHIVE_OUTPUT_DIRECTORY)
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib)
endif()
if(NOT DEFINED CMAKE_BUILD_SHARE_DIR)
set(CMAKE_BUILD_SHARE_DIR ${CMAKE_BINARY_DIR}/share)
endif()
if(MSVC)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /manifest:no")
set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} /manifest:no")
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /manifest:no")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /utf-8")
if(NOT DEFINED CMAKE_DEBUG_POSTFIX)
set(CMAKE_DEBUG_POSTFIX "d")
endif()
endif()
if(SYSCMDLINE_INSTALL)
include(GNUInstallDirs)
include(CMakePackageConfigHelpers)
endif()
# ----------------------------------
# Project Variables
# ----------------------------------
set(SYSCMDLINE_VERSION ${PROJECT_VERSION})
set(SYSCMDLINE_INSTALL_NAME ${PROJECT_NAME})
# ----------------------------------
# Main Project
# ----------------------------------
if(TRUE)
# Add library
if(SYSCMDLINE_BUILD_STATIC)
add_library(${PROJECT_NAME} STATIC)
target_compile_definitions(${PROJECT_NAME} PUBLIC SYSCMDLINE_STATIC)
else()
add_library(${PROJECT_NAME} SHARED)
endif()
add_library(${PROJECT_NAME}::${PROJECT_NAME} ALIAS ${PROJECT_NAME})
# Add sources
file(GLOB_RECURSE _src include/syscmdline/*.h src/*.h src/*.cpp)
target_sources(${PROJECT_NAME} PRIVATE ${_src})
# Add features
target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20 cxx_std_17)
# Add options
target_compile_definitions(${PROJECT_NAME} PRIVATE SYSCMDLINE_LIBRARY)
if(SYSCMDLINE_FORCE_VALIDITY_CHECK)
target_compile_definitions(${PROJECT_NAME} PRIVATE SYSCMDLINE_ENABLE_VALIDITY_CHECK)
else()
target_compile_definitions(${PROJECT_NAME} PRIVATE
"$<$<CONFIG:Debug>:SYSCMDLINE_ENABLE_VALIDITY_CHECK>"
)
endif()
# Include directories
target_include_directories(${PROJECT_NAME} PUBLIC
"$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>"
)
target_include_directories(${PROJECT_NAME} PRIVATE include/syscmdline)
# Add platform specific
if(WIN32)
set(RC_DESCRIPTION "Another C++ Command Line Parser")
set(RC_COPYRIGHT "Copyright 2023 SineStriker")
include("cmake/winrc.cmake")
endif()
# if(MSVC)
# if(${CMAKE_BUILD_TYPE} STREQUAL "Release")
# target_compile_options(${PROJECT_NAME} PRIVATE /GL /Os)
# target_link_options(${PROJECT_NAME} PRIVATE /OPT:REF /OPT:ICF /OPT:LBR)
# endif()
# endif()
if(SYSCMDLINE_INSTALL)
target_include_directories(${PROJECT_NAME} PUBLIC
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>"
)
install(TARGETS ${PROJECT_NAME}
EXPORT ${SYSCMDLINE_INSTALL_NAME}Targets
RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" OPTIONAL
LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" OPTIONAL
)
endif()
endif()
if(SYSCMDLINE_BUILD_TESTS)
add_subdirectory(tests)
endif()
if(SYSCMDLINE_BUILD_EXAMPLES)
add_subdirectory(examples)
endif()
if(SYSCMDLINE_INSTALL)
# Add install target
set(_install_dir ${CMAKE_INSTALL_LIBDIR}/cmake/${SYSCMDLINE_INSTALL_NAME})
# Add version file
write_basic_package_version_file(
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}ConfigVersion.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY AnyNewerVersion
)
# Add configuration file
configure_package_config_file(
${CMAKE_CURRENT_LIST_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake.in
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake"
INSTALL_DESTINATION ${_install_dir}
NO_CHECK_REQUIRED_COMPONENTS_MACRO
)
# Install cmake files
install(FILES
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}Config.cmake"
"${CMAKE_CURRENT_BINARY_DIR}/${SYSCMDLINE_INSTALL_NAME}ConfigVersion.cmake"
DESTINATION ${_install_dir}
)
# Install cmake targets files
install(EXPORT ${SYSCMDLINE_INSTALL_NAME}Targets
FILE "${SYSCMDLINE_INSTALL_NAME}Targets.cmake"
NAMESPACE syscmdline::
DESTINATION ${_install_dir}
)
install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}
FILES_MATCHING PATTERN "*.h"
)
endif()

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 SineStriker
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,186 @@
# SysCmdLine
Another C++ Command Line Parser.
Inspired by Qt `QCommandLineParser` and C# `System.CommandLine`.
## Introduction
To be in line with the principle of "don't learn anything new if you don't need to", on the one hand, `syscmdline` contains as many common features as possible, on the other hand, it's simple enough to be easy to understand.
Therefore, the project is designed to be configurable, but it's not intended to be as complex as a framework.
## Features
+ Support sub-commands
+ Support case-insensitive parsing
+ Support global options
+ Support mutually exclusive options
+ Support short options and group flags
+ Support help text customization
+ Support localization
+ Simple tips for typo correction
+ Special implementations for Windows
+ Highly configurable
+ Friendly interface
<!-- ## Help Text
The help text is highly configurable, here we show some simple ones.
### Unix Style
```
Description:
Move source(s) to destination directory.
Usage:
mv <src>... <dest> [options]
Arguments:
src Source(s)
dest Destination directory
Options:
-v, --version Show version information
-h, --help Show help information
```
### Dos Style
```
Description:
Deletes one or more files.
Usage:
del files... [/P] [/F] [/S] [/Q] [/?]
Arguments:
files Specifies a list of one or more files or directories.
Options:
/P Prompts for confirmation before deleting the specified file.
/F Forces deletion of read-only files.
/S Deletes specified files from the current directory and all subdirectories.
/Q Specifies quiet mode. You are not prompted for delete confirmation.
/? Show help information.
``` -->
## Simple Example
A simple `mv` command:
```c++
#include <iostream>
#include <syscmdline/parser.h>
namespace SCL = SysCmdLine;
int main(int argc, char *argv[]) {
SCL::Command cmd("mv", "move files to directory");
cmd.addArguments({
SCL::Argument("files", "Source files").multi(),
SCL::Argument("dir", "Destination directory"),
});
cmd.addHelpOption();
cmd.setHandler([](const SCL::ParseResult &result) -> int {
std::cout << "[Sources]" << std::endl;
for (const auto &item : result.values("files")) {
std::cout << item.toString() << std::endl;
}
std::cout << "[Destination]" << std::endl;
std::cout << result.value("dir").toString() << std::endl;
return 0;
});
return SCL::Parser(cmd).invoke(argc, argv);
}
```
Running the code:
```sh
> ./mv --help
Description:
move files to directory
Usage:
mv <files>... <dir> [options]
Arguments:
<files> Source files
<dir> Destination directory
Options:
-h, --help Show help information
```
```sh
> ./mv 1 2
[Sources]
1
[Destination]
2
```
```sh
>./mv 1 2 3
[Sources]
1
2
[Destination]
3
```
## Quick Start
### Concepts
If you are confused about some of the concepts of command line programs, you can learn the following, which will help you use this project.
See [Concepts](docs/concepts.md) to learn more.
### More Examples
See [Examples](docs/examples.md) to learn more.
## CMake Intergration
### Build & Install
```sh
cmake -B build -G Ninja
cmake --build build --target all
cmake --build build --target install
```
### Import
```cmake
find_package(syscmdline REQUIRED)
target_link_libraries(my_project PRIVATE syscmdline::syscmdline)
```
## Notice
<!-- + C++ Standard
+ You need a C++ compiler which supports C++17 to build the library. The interface is compatible with C++11. -->
+ Minimize Size
+ In order to achieve more functionalities, this project contains a large amount of codes so that the binary size may be relatively large compared with other libraries. Therefore, this implementation uses STL templates as little as possible.
+ It's suggested to enable size optimizing option for your compiler when building executables.
+ Validity Check
+ The root command must be valid, otherwise the parsing result is undefined and may even cause crash.
+ Validity checking is enabled if `SYSCMDLINE_ENABLE_VALIDITY_CHECK` is defined, which reduces parsing performance. Therefore, this macro is enabled only in debug mode.
## Thanks
### Other Projects
+ https://github.com/qt/qtbase
+ https://github.com/dotnet/command-line-api
<!-- + https://github.com/tanakh/cmdline
+ https://github.com/p-ranav/argparse
+ https://github.com/CLIUtils/CLI11 -->
### Contributors
+ [wangwenx190](https://github.com/wangwenx190)
+ [JobSecond](https://github.com/JobSecond)
## License
This project is licensed under the MIT License.

View File

@@ -0,0 +1,34 @@
set(_rc_content "#include <windows.h>
#ifndef VS_VERSION_INFO
#define VS_VERSION_INFO 1
#endif
#define _STRINGIFY(x) #x
#define STRINGIFY(x) _STRINGIFY(x)
VS_VERSION_INFO VERSIONINFO
FILEVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},${PROJECT_VERSION_TWEAK}
PRODUCTVERSION ${PROJECT_VERSION_MAJOR},${PROJECT_VERSION_MINOR},${PROJECT_VERSION_PATCH},${PROJECT_VERSION_TWEAK}
{
BLOCK \"StringFileInfo\"
{
// U.S. English - Windows, Multilingual
BLOCK \"040904E4\"
{
VALUE \"FileDescription\", STRINGIFY(${RC_DESCRIPTION})
VALUE \"FileVersion\", STRINGIFY(${PROJECT_VERSION})
VALUE \"ProductName\", STRINGIFY(${PROJECT_NAME})
VALUE \"ProductVersion\", STRINGIFY(${PROJECT_VERSION})
VALUE \"LegalCopyright\", STRINGIFY(${RC_COPYRIGHT})
}
}
BLOCK \"VarFileInfo\"
{
VALUE \"Translation\", 0x409, 1252 // 1252 = 0x04E4
}
}")
set(_rc_file ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_res.rc)
file(WRITE ${_rc_file} ${_rc_content})
target_sources(${PROJECT_NAME} PRIVATE ${_rc_file})

View File

@@ -0,0 +1,23 @@
# Concepts
## Command
+ **Command** is the beginning symbol used to determine the behavior of a set of command-line arguments.
+ The commands of a CLI program are usually in the form of a tree structure, and the program itself is the root command. Take `git` as an example, `git` is the root command and `merge`/`commit`/... are the sub-commands.
+ However, most commands doesn't have sub-commands because the root command is sufficient in most scenarios.
+ Command line parameters must start with the root command and list the subcommands in sequence along the path to a certain leaf command. When the target sub-command is listed, all subsequent symbols are regarded as the arguments or options of this sub-command.
## Argument
+ **Argument** is the positional argument, which belongs to a command or an option.
+ The positional arguments are parsed according to the order in which they are specified. Take `mv` as an example, `mv <src> <dest>` shows that it has two positional arguments and the first will be recognized as source and the second as destination.
## Option
+ **Option**, usually a word or letter beginning with the `-` or `/` sign. (`-` is the Unix style and `/` is the Dos style)
+ Options can have none or a fixed number of positional arguments which are recommended to be non-required.

View File

@@ -0,0 +1,251 @@
# Examples
## Basic
+ Acquire Program Arguments
+ Positional Argument
+ Optinal Argument And Restricted Argument
+ Option
+ Multi-argument Option
+ Multi-value Argument
### Acquire Program Arguments
The most common way to get command-line arguments is directly from the arguments of the `main` function.
```c
int main(int argc, char *argv[]);
```
If your host system is Unix, then everything is fine. However, this set of arguments from `main` is likely to cause problems on Windows because they are in ANSI encoding.
On Windows, we must call `GetCommandLineW()` to get the correct UTF-16 encoded arguments.
As a result, `syscmdline` provides a generic way to get command-line arguments that does not depend on the `main` function.
+ On Windows, it acquires arguments by calling `GetCommandLineW()` and converts the result to UTF-8 string list.
+ On Mac, it acquires arguments by calling `_NSGetArgv`.
+ On Linux, it reads the arguments from `/proc/self/cmdline`.
```c++
namespace SysCmdLine {
std::vector<std::string> commandLineArguments();
}
```
Include `system.h` to import this function.
```c++
#include <syscmdline/system.h>
```
If you are developing a program on Windows, we recommend using this function to get command line arguments.
`syscmdline` uses UTF-8 internally to output to the console, error may occur if the ANSI-encoded arguments contain non-ASCII characters.
### Positional Argument
Simple Example of one positional argument:
```c++
#include <iostream>
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
namespace SCL = SysCmdLine;
int main(int /* argc */, char * /* argv */ []) {
SCL::Argument intArg("int", "Integer");
intArg.setDefaultValue(0);
SCL::Command rootCommand("square", "Display the square of the specified integer.");
rootCommand.addArgument(intArg);
rootCommand.addHelpOption();
rootCommand.setHandler([](const SCL::ParseResult &result) {
auto num = result.value("int").toInt();
std::cout << (num * num) << std::endl;
return 0;
});
SCL::Parser parser(rootCommand);
return parser.invoke(SCL::commandLineArguments());
}
```
+ In this case, we call `addArgument` to add one positional argument.
+ The `Argument` instance is default to be required, and we set an implicit defualt value to force the parser to to accept only numeric input.
Help text:
```
Description:
Display the square of a given integer.
Usage:
square <int> [options]
Arguments:
<int> Integer
Options:
-h, --help Show help information
```
Test:
```sh
> ./square 32
1024
```
### Optinal Argument And Restricted Argument
Simple example of one restricted argument and one optional argument:
```c++
int main(int /* argc */, char * /* argv */ []) {
SCL::Argument weekdayArg("weekday", "Weekday");
weekdayArg.setExpectedValues({
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday",
});
SCL::Argument eventArg("event", "Event to do");
eventArg.setDefaultValue("football");
eventArg.setRequired(false);
SCL::Command rootCommand("work", "What to do on which day?");
rootCommand.addArguments({weekdayArg, eventArg});
rootCommand.addHelpOption();
rootCommand.setHandler([](const SCL::ParseResult &result) {
std::cout << result.value("weekday").toString() << std::endl;
std::cout << result.value("event").toString() << std::endl;
return 0;
});
SCL::Parser parser(rootCommand);
parser.setDisplayOptions(SCL::Parser::ShowArgumentDefaultValue |
SCL::Parser::ShowArgumentExpectedValues);
return parser.invoke(SCL::commandLineArguments());
}
```
+ In this case, we add a restricted argument and another required argument with default value.
+ If an argument has an expect value list, only symbols listed in it will be regarded as valid argument.
+ We also tell the parser to show default value and expect values on help list which would be hided if the flags weren't set.
<!-- We could simplify the above code by using the self-return API:
```c++
int main(int /* argc */, char * /* argv */ []) {
SCL::Command rootCommand("work", "What to do on which day?");
rootCommand.addArguments({
SCL::Argument("weekday", "Weekday").expect({
"Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday",
}),
SCL::Argument("event", "Event to do", false, "football"),
});
rootCommand.help().action([](const SCL::ParseResult &result) {
std::cout << result.value("weekday").toString() << std::endl;
std::cout << result.value("event").toString() << std::endl;
return 0;
});
SCL::Parser parser(rootCommand);
parser.setDisplayOptions(SCL::Parser::ShowArgumentDefaultValue |
SCL::Parser::ShowArgumentExpectedValues);
return parser.invoke(SCL::commandLineArguments());
}
``` -->
Help Text:
```
Description:
What to do on which day?
Usage:
work <weekday> [<event>] [options]
Arguments:
<weekday> Weekday [Expected: "Monday", "Tuesday", "Wednesday", ...]
<event> Event to do [Default: football]
Options:
-h, --help Show help information
```
Test:
```sh
> ./work Monday
Monday
football
```
```sh
> ./work Tuesday basketball
Tuesday
basketball
```
### Option
Simple example of a converter:
```c++
int main(int /* argc */, char * /* argv */ []) {
SCL::Option inputOpt("--input", "Input file");
inputOpt.addArgument(SCL::Argument("file"));
inputOpt.setRequired(true);
SCL::Option outputOpt("--output", "Output file");
outputOpt.addArgument(SCL::Argument("file"));
SCL::Command rootCommand("converter", "Convert A to B.");
rootCommand.addOptions({inputOpt, outputOpt});
rootCommand.addHelpOption();
rootCommand.setHandler([](const SCL::ParseResult &result) {
std::cout << "Input: " << result.valueForOption("--input").toString() << std::endl;
if (result.isOptionSet("--output")) {
std::cout << "Output: " << result.valueForOption("--output").toString() << std::endl;
}
return 0;
});
SCL::Parser parser(rootCommand);
parser.setDisplayOptions(SCL::Parser::ShowOptionalOptionsOnUsage);
return parser.invoke(SCL::commandLineArguments());
}
```
+ In this case, we add two single argument options `input` and `output`, and `input` is required.
+ We also tell the parser to show verbose options on usage.
Help Text:
```
Description:
Convert A to B.
Usage:
converter --input <file> [--output <file>] [-h]
Options:
--input <file> Input file
--output <file> Output file
-h, --help Show help information
```
Test:
```sh
> ./converter --input in
Input: in
```
```sh
> ./converter --input in --output out
Input: in
Output: out
```
### Multi-argument Option
TODO
### Multi-value Argument
TODO

View File

@@ -0,0 +1,4 @@
add_subdirectory(del)
add_subdirectory(mv)
add_subdirectory(git)
add_subdirectory(gcc)

View File

@@ -0,0 +1,7 @@
project(del)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)

View File

@@ -0,0 +1,111 @@
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
namespace zh_CN {
static const char *error_strings[] = {
R"(没有错误。)",
R"(未知的选项 "%1"。)",
R"(未知的命令或参数 "%1"。)",
R"(选项 "%2" 缺少必选参数 "%1"。)",
R"(缺少必选参数 "%1"。)",
R"(参数过多。)",
R"(参数 "%2" 被指定非法的值 "%1"。)",
R"(选项 "%1" 出现在非法的位置,此处应为参数 "%2"。)",
R"(缺少必选选项 "%1"。)",
R"(选项 "%1" 出现次数过多,最多 %2 次。)",
R"(参数 "%2" 被指定非法的值 "%1",应为 "%3"。)",
R"(参数 "%2" 被指定非法的值 "%1",原因: %3)",
R"(选项 "%1""%2" 是互斥的。)",
R"(选项 "%1" 与其他参数不能同时指定。)",
R"(选项 "%1" 与其他选项不能同时指定。)",
R"(无法读取响应文件 "%1")",
};
static const char *title_strings[] = {
"错误", "用法", "简介", "参数", "选项", "命令", "必选", "默认", "合法值",
};
static const char *role_strings[] = {
"", "调试模式", "显示详细信息", "显示版本信息", "显示帮助信息",
};
static const char *info_strings[] = {
R"("%1" 未匹配。您是否想指定以下之一?)",
};
static const char **strings[] = {
error_strings,
title_strings,
role_strings,
info_strings,
};
static std::string provider(int category, int index) {
return strings[category][index];
}
}
using namespace SysCmdLine;
static int routine(const ParseResult &result) {
auto fileValues = Value::toStringList(result.values("files"));
u8info("将要被删除的文件:\n");
for (const auto &item : std::as_const(fileValues)) {
u8info(" %s\n", item.data());
}
bool prompt = result.isOptionSet("/P");
bool force = result.isOptionSet("/F");
bool subdir = result.isOptionSet("/S");
bool quiet = result.isOptionSet("/Q");
u8info("模式: \n");
if (prompt) {
u8info(" 提示\n");
}
if (force) {
u8info(" 强制\n");
}
if (subdir) {
u8info(" 子文件夹\n");
}
if (quiet) {
u8info(" 静默\n");
}
return 0;
}
int main(int argc, char *argv[]) {
SYSCMDLINE_UNUSED(argc);
SYSCMDLINE_UNUSED(argv);
Option promptOption({"/P", "-p"}, "删除每一个文件之前提示确认");
Option forceOption({"/F", "-f"}, "强制删除只读文件");
Option subdirOption({"/S", "-s"}, "删除所有子目录中的指定的文件");
Option quietOption({"/Q", "-q"}, "安静模式。删除全局通配符时,不要求确认");
Argument fileArg("files", "指定一个或多个文件或者目录列表");
fileArg.setDisplayName("files");
fileArg.setMultiValueEnabled(true);
Command rootCommand("del", {});
rootCommand.addArgument(fileArg);
rootCommand.addOptions({
promptOption,
forceOption,
subdirOption,
quietOption,
});
rootCommand.addHelpOption(false, false, {"/?"});
rootCommand.setHandler(routine);
Parser parser(rootCommand);
parser.setTextProvider(zh_CN::provider);
parser.setDisplayOptions(Parser::ShowOptionalOptionsOnUsage | Parser::ShowArgumentIsRequired |
Parser::AlignAllCatalogues);
return parser.invoke(commandLineArguments(), -1,
Parser::IgnoreOptionCase | Parser::AllowUnixGroupFlags);
}

View File

@@ -0,0 +1,7 @@
project(gcc)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)

View File

@@ -0,0 +1,36 @@
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
using namespace SysCmdLine;
int main(int /* argc */, char * /* argv */[]) {
Option linkDirOption({"-L", "--linkdir"}, "Add link directory");
linkDirOption.setShortMatchRule(Option::ShortMatchSingleChar);
linkDirOption.addArgument(Argument("dir"));
linkDirOption.setUnlimitedOccurrence();
Option includeDirOption({"-I", "--includedir"}, "Add include directory");
includeDirOption.setShortMatchRule(Option::ShortMatchSingleChar);
includeDirOption.addArgument(Argument("dir"));
includeDirOption.setUnlimitedOccurrence();
Command rootCommand("gcc", "GNU C/C++ Compiler");
rootCommand.addOptions({
linkDirOption,
includeDirOption,
});
rootCommand.addVersionOption("0.0.0.1");
rootCommand.addHelpOption(true, true);
rootCommand.setHandler([](const ParseResult &result) {
u8info("Link directories:\n");
for (const auto &item : result.option("-L").allValues())
u8info(" %s\n", item.toString().data());
u8info("Include directories:\n");
for (const auto &item : result.option("-I").allValues())
u8info(" %s\n", item.toString().data());
return 0;
});
Parser parser(rootCommand);
return parser.invoke(commandLineArguments());
}

View File

@@ -0,0 +1,7 @@
project(git)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)

View File

@@ -0,0 +1,39 @@
#include <syscmdline/parser.h>
#include <syscmdline/system.h>
using namespace SysCmdLine;
int main(int /* argc */, char * /* argv */[]) {
Command cloneCommand("clone", "Clone a repository into a new directory");
Command initCommand("init", "Create an empty Git repository or reinitialize an existing one");
Command commitCommand("commit", "Record changes to the repository");
Command mergeCommand("merge", "Join two or more development histories together");
Command rebaseCommand("rebase", "Reapply commits on top of another base tip");
Command rootCommand("git", "Git is a distributed version control system.");
rootCommand.addCommands({
cloneCommand,
initCommand,
commitCommand,
mergeCommand,
rebaseCommand,
});
rootCommand.addVersionOption("0.0.0.1");
rootCommand.addHelpOption(true, true);
rootCommand.setHandler([](const ParseResult &result) {
u8info("%s\n", result.value("weekday").toString().data());
u8info("%s\n", result.value("event").toString().data());
return 0;
});
CommandCatalogue cc;
cc.addCommands("start a working area", {"clone", "init"});
cc.addCommands("grow, mark and tweak your common history", {"commit", "merge", "rebase"});
rootCommand.setCatalogue(cc);
Parser parser(rootCommand);
parser.setDisplayOptions(Parser::ShowArgumentDefaultValue | Parser::ShowArgumentExpectedValues |
Parser::DontShowHelpOnError | Parser::AlignAllCatalogues);
return parser.invoke(SysCmdLine::commandLineArguments());
}

View File

@@ -0,0 +1,7 @@
project(mv)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)

View File

@@ -0,0 +1,24 @@
#include <iostream>
#include <syscmdline/parser.h>
using namespace SysCmdLine;
int main(int argc, char *argv[]) {
Command cmd("mv", "move files to directory");
cmd.addArguments({
Argument("files", "Source files").multi(),
Argument("dir", "Destination directory"),
});
cmd.addHelpOption();
cmd.setHandler([](const ParseResult &result) -> int {
std::cout << "[Sources]" << std::endl;
for (const auto &item : result.values("files")) {
std::cout << item.toString() << std::endl;
}
std::cout << "[Destination]" << std::endl;
std::cout << result.value("dir").toString() << std::endl;
return 0;
});
Parser parser(cmd);
parser.setDisplayOptions(Parser::ShowOptionsHintFront);
return parser.invoke(argc, argv);
}

View File

@@ -0,0 +1,164 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef ARGUMENT_H
#define ARGUMENT_H
#include <functional>
#include <vector>
#include <syscmdline/symbol.h>
#include <syscmdline/value.h>
namespace SysCmdLine {
class ArgumentPrivate;
class SYSCMDLINE_EXPORT Argument : public Symbol {
SYSCMDLINE_DECL_PRIVATE(Argument)
public:
enum Number {
Single,
MultiValue,
Remainder,
};
using Validator = std::function<bool /* result */ (
const std::string & /* token */, Value * /* out */, std::string * /* errorMessage */)>;
Argument();
Argument(const std::string &name, const std::string &desc = {}, bool required = true,
const Value &defaultValue = {});
std::string displayedText() const;
using Symbol::helpText;
std::string helpText(HelpPosition pos, int displayOptions, void *extra) const override;
public:
std::string name() const;
void setName(const std::string &name);
std::string displayName() const;
void setDisplayName(const std::string &displayName);
bool isRequired() const;
void setRequired(bool required);
inline bool isOptional() const;
inline void setOptional(bool optional);
Value defaultValue() const;
void setDefaultValue(const Value &defaultValue);
const std::vector<Value> &expectedValues() const;
void setExpectedValues(const std::vector<Value> &expectedValues);
bool multiValueEnabled() const;
SYSCMDLINE_DECL_DEPRECATED void setMultiValueEnabled(bool on);
Number number() const;
void setNumber(Number valuePolicy);
Validator validator() const;
void setValidator(const Validator &validator);
public:
inline Argument &metavar(const std::string &metavar);
inline Argument &required(bool required = true);
inline Argument &default_value(const Value &value);
inline Argument &expect(const std::vector<Value> &expectedValues);
SYSCMDLINE_DECL_DEPRECATED inline Argument &multi(bool multiValueEnabled = true);
inline Argument &validate(const Validator &validator);
inline Argument &nargs(Number valuePolicy);
};
inline bool Argument::isOptional() const {
return !isRequired();
}
inline void Argument::setOptional(bool optional) {
setRequired(!optional);
}
inline Argument &Argument::metavar(const std::string &metavar) {
setDisplayName(metavar);
return *this;
}
inline Argument &Argument::required(bool required) {
setRequired(required);
return *this;
}
Argument &Argument::default_value(const Value &value) {
setDefaultValue(value);
return *this;
}
Argument &Argument::expect(const std::vector<Value> &expectedValues) {
setExpectedValues(expectedValues);
return *this;
}
inline Argument &Argument::multi(bool multiValueEnabled) {
setMultiValueEnabled(multiValueEnabled);
return *this;
}
inline Argument &Argument::validate(const Argument::Validator &validator) {
setValidator(validator);
return *this;
}
inline Argument &Argument::nargs(const Argument::Number valuePolicy) {
setNumber(valuePolicy);
return *this;
}
class ArgumentHolderPrivate;
class SYSCMDLINE_EXPORT ArgumentHolder : public Symbol {
SYSCMDLINE_DECL_PRIVATE(ArgumentHolder)
public:
std::string displayedArguments(int displayOptions) const;
public:
inline void addArgument(const Argument &argument);
void addArguments(const std::vector<Argument> &arguments);
using Symbol::helpText;
protected:
ArgumentHolder(ArgumentHolderPrivate *d);
};
inline void ArgumentHolder::addArgument(const Argument &argument) {
addArguments({argument});
}
}
#endif // ARGUMENT_H

View File

@@ -0,0 +1,147 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef COMMAND_H
#define COMMAND_H
#include <syscmdline/option.h>
#include <syscmdline/helplayout.h>
namespace SysCmdLine {
class ParseResult;
class CommandCataloguePrivate;
class SYSCMDLINE_EXPORT CommandCatalogue : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(CommandCatalogue)
public:
CommandCatalogue();
public:
void addArguments(const std::string &name, const std::vector<std::string> &args);
void addOptions(const std::string &name, const std::vector<std::string> &options);
void addCommands(const std::string &name, const std::vector<std::string> &commands);
};
class CommandPrivate;
class SYSCMDLINE_EXPORT Command : public ArgumentHolder {
SYSCMDLINE_DECL_PRIVATE(Command)
public:
using Handler = std::function<int /* code */ (const ParseResult & /* result */)>;
Command();
Command(const std::string &name, const std::string &desc = {},
const std::vector<Argument> &args = {});
using Symbol::helpText;
std::string helpText(HelpPosition pos, int displayOptions, void *extra) const override;
public:
std::string name() const;
void setName(const std::string &name);
inline void addOption(const Option &option, const std::string &group = {});
void addOptions(const std::vector<Option> &options, const std::string &group = {});
int commandCount() const;
Command command(int index) const;
inline void addCommand(const Command &command);
void addCommands(const std::vector<Command> &commands);
std::string detailedDescription() const;
void setDetailedDescription(const std::string &detailedDescription);
Handler handler() const;
void setHandler(const Handler &handler);
CommandCatalogue catalogue() const;
void setCatalogue(const CommandCatalogue &catalogue);
std::string versionString() const;
void addVersionOption(const std::string &version,
const std::vector<std::string> &tokens = {},
const std::string &desc = {});
void addHelpOption(bool showHelpIfNoArg = false, bool global = false,
const std::vector<std::string> &tokens = {},
const std::string &desc = {});
HelpLayout helpLayout() const;
void setHelpLayout(const HelpLayout &helpLayout);
public:
inline Command &detailed(const std::string &detailedDescription);
inline Command &action(const Handler &handler);
inline Command &catalog(const CommandCatalogue &catalogue);
inline Command &version(const std::string &version,
const std::vector<std::string> &tokens = {},
const std::string &desc = {});
inline Command &help(bool showHelpIfNoArg = false, bool global = false,
const std::vector<std::string> &tokens = {},
const std::string &desc = {});
};
inline void Command::addCommand(const Command &command) {
addCommands({command});
}
inline void Command::addOption(const Option &option, const std::string &group) {
addOptions({option}, group);
}
inline Command &Command::detailed(const std::string &detailedDescription) {
setDetailedDescription(detailedDescription);
return *this;
}
inline Command &Command::action(const Command::Handler &handler) {
setHandler(handler);
return *this;
}
inline Command &Command::catalog(const CommandCatalogue &catalogue) {
setCatalogue(catalogue);
return *this;
}
inline Command &Command::version(const std::string &version,
const std::vector<std::string> &tokens,
const std::string &desc) {
addVersionOption(version, tokens, desc);
return *this;
}
inline Command &Command::help(bool showHelpIfNoArg, bool global,
const std::vector<std::string> &tokens, const std::string &desc) {
addHelpOption(showHelpIfNoArg, global, tokens, desc);
return *this;
}
}
#endif // COMMAND_H

View File

@@ -0,0 +1,71 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef GLOBAL_H
#define GLOBAL_H
// Export define
#ifdef _WIN32
# define SYSCMDLINE_DECL_EXPORT __declspec(dllexport)
# define SYSCMDLINE_DECL_IMPORT __declspec(dllimport)
#else
# define SYSCMDLINE_DECL_EXPORT __attribute__((visibility("default")))
# define SYSCMDLINE_DECL_IMPORT __attribute__((visibility("default")))
#endif
#ifdef SYSCMDLINE_STATIC
# define SYSCMDLINE_EXPORT
#else
# ifdef SYSCMDLINE_LIBRARY
# define SYSCMDLINE_EXPORT SYSCMDLINE_DECL_EXPORT
# else
# define SYSCMDLINE_EXPORT SYSCMDLINE_DECL_IMPORT
# endif
#endif
#ifdef _WIN32
# define SYSCMDLINE_DECL_DEPRECATED __declspec(deprecated)
#else
# define SYSCMDLINE_DECL_DEPRECATED __attribute__((__deprecated__))
#endif
// Utils
#define SYSCMDLINE_UNUSED(X) (void) X;
#define SYSCMDLINE_DECL_PRIVATE(X) \
public: \
const X##Private *d_func() const { \
return reinterpret_cast<const X##Private *>(d_ptr); \
}
#if defined(__GNUC__) || defined(__clang__)
# define SYSCMDLINE_PRINTF_FORMAT(fmtpos, attrpos) \
__attribute__((__format__(__printf__, fmtpos, attrpos)))
#else
# define SYSCMDLINE_PRINTF_FORMAT(fmtpos, attrpos)
#endif
#endif // GLOBAL_H

View File

@@ -0,0 +1,109 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef HELPLAYOUT_H
#define HELPLAYOUT_H
#include <string>
#include <vector>
#include <functional>
#include <syscmdline/sharedbase.h>
namespace SysCmdLine {
class Parser;
class HelpLayoutPrivate;
class SYSCMDLINE_EXPORT HelpLayout : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(HelpLayout)
public:
HelpLayout();
enum HelpTextItem {
HT_Prologue,
HT_Epilogue,
HT_Description,
HT_Usage,
};
enum HelpListItem {
HL_Arguments,
HL_Options,
HL_Commands,
};
enum MessageItem {
MI_Information,
MI_Warning,
MI_Critical,
};
struct Text {
std::string title;
std::string lines;
};
struct List {
std::string title;
std::vector<std::string> firstColumn;
std::vector<std::string> secondColumn;
};
struct Context {
const Parser *parser;
union {
// Available on `HelpTextItem`, `UserHelpTextItem`
const Text *text;
// Available on `HelpListItem`, `UserHelpListItem`
struct {
const List *list;
int firstColumnLength;
};
};
bool hasNext;
};
using Output = std::function<void(const Context & /* context */)>;
public:
void addHelpTextItem(HelpTextItem type, const Output &out = {});
void addHelpListItem(HelpListItem type, const Output &out = {});
void addMessageItem(MessageItem type, const Output &out = {});
void addUserHelpTextItem(const Text &text, const Output &out = {});
void addUserHelpListItem(const List &list, const Output &out = {});
void addUserIntroItem(const Output &out);
public:
static HelpLayout defaultHelpLayout();
};
}
#endif // HELPLAYOUT_H

View File

@@ -0,0 +1,178 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef OPTION_H
#define OPTION_H
#include <syscmdline/argument.h>
namespace SysCmdLine {
class OptionPrivate;
class SYSCMDLINE_EXPORT Option : public ArgumentHolder {
SYSCMDLINE_DECL_PRIVATE(Option)
public:
enum Role {
NoRole,
Debug,
Verbose,
Version,
Help,
};
enum PriorLevel {
NoPrior,
IgnoreMissingArguments,
IgnoreMissingSymbols,
AutoSetWhenNoSymbols,
ExclusiveToArguments,
ExclusiveToOptions,
ExclusiveToAll,
};
enum ShortMatchRule {
NoShortMatch,
ShortMatchSingleLetter,
ShortMatchSingleChar,
ShortMatchAll,
};
Option();
Option(Role role, const std::vector<std::string> &tokens = {}, const std::string &desc = {});
Option(const std::string &token, const std::string &desc = {}, const Argument &arg = {},
bool required = false);
Option(const std::vector<std::string> &tokens, const std::string &desc = {},
const Argument &arg = {}, bool required = false);
inline Option(std::initializer_list<std::string> tokens, const std::string &desc = {},
const Argument &arg = {}, bool required = false);
using Symbol::helpText;
std::string helpText(HelpPosition pos, int displayOptions, void *extra) const override;
public:
Role role() const;
void setRole(Role role);
const std::vector<std::string> &tokens() const;
inline std::string token() const;
void setTokens(const std::vector<std::string> &tokens);
inline void setToken(const std::string &token);
bool isRequired() const;
void setRequired(bool required);
inline bool isOptional() const;
inline void setOptional(bool optional);
ShortMatchRule shortMatchRule() const;
void setShortMatchRule(ShortMatchRule shortMatchRule);
PriorLevel priorLevel() const;
void setPriorLevel(PriorLevel priorLevel);
bool isGlobal() const;
void setGlobal(bool on);
int maxOccurrence() const;
void setMaxOccurrence(int max);
inline void setUnlimitedOccurrence();
public:
inline Option &arg(const std::string &name, bool required = true,
const Value &defaultValue = {});
inline Option &arg(const Argument &arg);
inline Option &required(bool required = true);
inline Option &short_match(ShortMatchRule shortMatchRule = ShortMatchAll);
inline Option &prior(PriorLevel priorLevel);
inline Option &global(bool global = true);
inline Option &multi(int maxOccurrence = 0);
};
inline Option::Option(std::initializer_list<std::string> tokens, const std::string &desc,
const Argument &arg, bool required)
: Option(std::vector<std::string>(tokens), desc, arg, required) {
}
inline std::string Option::token() const {
return tokens().front();
}
inline void Option::setToken(const std::string &token) {
setTokens({token});
}
inline bool Option::isOptional() const {
return !isRequired();
}
inline void Option::setOptional(bool optional) {
setRequired(!optional);
}
inline void Option::setUnlimitedOccurrence() {
setMaxOccurrence(0);
}
inline Option &Option::arg(const std::string &name, bool required, const Value &defaultValue) {
// Option's argument doesn't need a description
addArgument(Argument(name, {}, required, defaultValue));
return *this;
}
inline Option &Option::arg(const Argument &arg) {
addArgument(arg);
return *this;
}
inline Option &Option::required(bool required) {
setRequired(required);
return *this;
}
inline Option &Option::short_match(Option::ShortMatchRule shortMatchRule) {
setShortMatchRule(shortMatchRule);
return *this;
}
inline Option &Option::prior(Option::PriorLevel priorLevel) {
setPriorLevel(priorLevel);
return *this;
}
inline Option &Option::global(bool global) {
setGlobal(global);
return *this;
}
inline Option &Option::multi(int maxOccurrence) {
setMaxOccurrence(maxOccurrence);
return *this;
}
}
#endif // OPTION_H

View File

@@ -0,0 +1,121 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef PARSER_H
#define PARSER_H
#include <syscmdline/parseresult.h>
namespace SysCmdLine {
class ParserPrivate;
class SYSCMDLINE_EXPORT Parser : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(Parser)
public:
Parser();
Parser(const Command &rootCommand);
public:
enum ParseOption {
Standard = 0,
IgnoreCommandCase = 0x1,
IgnoreOptionCase = 0x2,
AllowUnixGroupFlags = 0x4,
AllowDosShortOptions = 0x8,
DontAllowUnixShortOptions = 0x10,
EnableResponseFile = 0x20,
};
enum DisplayOption {
Normal = 0,
DontShowHelpOnError = 0x1,
DontShowIntroOnError = 0x2,
SkipCorrection = 0x4,
ShowArgumentDefaultValue = 0x8,
ShowArgumentExpectedValues = 0x10,
ShowArgumentIsRequired = 0x20,
ShowOptionIsRequired = 0x40,
AlignAllCatalogues = 0x80,
DontShowRequiredOptionsOnUsage = 0x100,
ShowOptionalOptionsOnUsage = 0x200,
ShowOptionsBehindArguments = 0x400,
ShowOptionsHintFront = 0x800,
};
std::string prologue() const;
void setPrologue(const std::string &prologue);
std::string epilogue() const;
void setEpilogue(const std::string &epilogue);
int displayOptions() const;
void setDisplayOptions(int displayOptions);
Command rootCommand() const;
void setRootCommand(const Command &rootCommand);
ParseResult parse(const std::vector<std::string> &args, int parseOptions = Standard);
inline int invoke(const std::vector<std::string> &args, int errCode = -1,
int parseOptions = Standard);
// Don't use this pair of API on Windows because the arguemnts passed by `main`
// entry is in ANSI encoding, but the library uses UTF-8.
inline ParseResult parse(int argc, char **argv, int parseOptions = Standard);
inline int invoke(int argc, char **argv, int errCode = -1, int parseOptions = Standard);
public:
using TextProvider = std::string (*)(int /* category */, int /* index */);
enum SizeType {
ST_Indent,
ST_Spacing,
ST_ConsoleWidth,
};
int size(SizeType sizeType) const;
void setSize(SizeType sizeType, int value);
TextProvider textProvider();
void setTextProvider(TextProvider textProvider);
static TextProvider defaultTextProvider();
};
inline int Parser::invoke(const std::vector<std::string> &args, int errCode, int parseOptions) {
return parse(args, parseOptions).invoke(errCode);
}
inline ParseResult Parser::parse(int argc, char **argv, int parseOptions) {
return parse({argv, argv + argc}, parseOptions);
}
inline int Parser::invoke(int argc, char **argv, int errCode, int parseOptions) {
return parse({argv, argv + argc}, parseOptions).invoke(errCode);
}
}
#endif // PARSER_H

View File

@@ -0,0 +1,279 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef PARSERESULT_H
#define PARSERESULT_H
#include <syscmdline/command.h>
namespace SysCmdLine {
class Parser;
class ParseResultPrivate;
class SYSCMDLINE_EXPORT OptionResult {
public:
OptionResult();
public:
Option option() const;
int indexOf(const std::string &name) const;
int count() const;
inline bool isSet() const;
inline bool isArgumentSet(const Argument &arg, int n = 0) const;
inline bool isArgumentSet(const std::string &name, int n = 0) const;
inline bool isArgumentSet(int index, int n = 0) const;
// Get all values of an argument
inline std::vector<Value> allValues(const Argument &arg) const;
inline std::vector<Value> allValues(const std::string &name) const;
std::vector<Value> allValues(int index = 0) const;
// Get values of multi-value argument at the option's N-th occurrence
inline const std::vector<Value> &values(const Argument &arg, int n = 0) const;
inline const std::vector<Value> &values(const std::string &name, int n = 0) const;
const std::vector<Value> &values(int index = 0, int n = 0) const;
// Get value of single-value argument at the option's N-th occurrence or its default value
inline Value value(const Argument &arg, int n = 0) const;
inline Value value(const std::string &name, int n = 0) const;
Value value(int index = 0, int n = 0) const;
private:
inline OptionResult(const void *data);
const void *data;
friend class ParseResult;
};
inline bool OptionResult::isSet() const {
return count() > 0;
}
inline bool OptionResult::isArgumentSet(const Argument &arg, int n) const {
return !values(indexOf(arg.name()), n).empty();
}
inline bool OptionResult::isArgumentSet(const std::string &name, int n) const {
return !values(indexOf(name), n).empty();
}
inline bool OptionResult::isArgumentSet(int index, int n) const {
return !values(index, n).empty();
}
inline std::vector<Value> OptionResult::allValues(const Argument &arg) const {
return allValues(indexOf(arg.name()));
}
inline std::vector<Value> OptionResult::allValues(const std::string &name) const {
return allValues(indexOf(name));
}
inline const std::vector<Value> &OptionResult::values(const Argument &arg, int n) const {
return values(indexOf(arg.name()), n);
}
inline const std::vector<Value> &OptionResult::values(const std::string &name, int n) const {
return values(indexOf(name), n);
}
inline Value OptionResult::value(const Argument &arg, int n) const {
return value(indexOf(arg.name()), n);
}
inline Value OptionResult::value(const std::string &name, int n) const {
return value(indexOf(name), n);
}
inline OptionResult::OptionResult(const void *data) : data(data) {
}
class SYSCMDLINE_EXPORT ParseResult : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(ParseResult)
public:
ParseResult();
inline bool isValid() const;
public:
Command rootCommand() const;
const std::vector<std::string> &arguments() const;
int invoke(int errCode = -1) const;
int dispatch() const;
public:
enum Error {
NoError,
UnknownOption,
UnknownCommand,
MissingOptionArgument,
MissingCommandArgument,
TooManyArguments,
InvalidArgumentValue,
InvalidOptionPosition,
MissingRequiredOption,
OptionOccurTooMuch,
ArgumentTypeMismatch,
ArgumentValidateFailed,
MutuallyExclusiveOptions,
PriorOptionWithArguments,
PriorOptionWithOptions,
ErrorReadingResponseFile,
};
Error error() const;
std::string errorText() const;
std::string correctionText() const;
std::string cancellationToken() const;
Command command() const;
std::vector<Option> globalOptions() const;
std::vector<int> commandIndexStack() const;
int indexOfArgument(const std::string &name) const;
int indexOfOption(const std::string &token) const;
void showError() const;
void showHelpText() const;
void showMessage(const std::string &info, const std::string &warning,
const std::string &error) const;
public:
inline bool isVersionSet() const;
inline bool isHelpSet() const;
bool isRoleSet(Option::Role role) const;
inline bool isArgumentSet(const Argument &arg) const;
inline bool isArgumentSet(const std::string &name) const;
inline bool isArgumentSet(int index) const;
// Get values of multi-value argument
inline const std::vector<Value> &values(const Argument &arg) const;
inline const std::vector<Value> &values(const std::string &name) const;
const std::vector<Value> &values(int index) const;
// Get value of single-value argument or its default value
inline Value value(const Argument &arg) const;
inline Value value(const std::string &name) const;
Value value(int index) const;
inline bool isOptionSet(const Option &option) const;
inline bool isOptionSet(const std::string &token) const;
inline bool isOptionSet(int index) const;
// Detailed result for an option
inline OptionResult option(const Option &option) const;
inline OptionResult option(const std::string &token) const;
OptionResult option(int index) const;
// Get value of single-value argument of given option or its default value
inline Value valueForOption(const Option &option) const;
inline Value valueForOption(const std::string &token) const;
inline Value valueForOption(int index) const;
protected:
ParseResult(ParseResultPrivate *d);
friend class Parser;
};
inline bool ParseResult::isValid() const {
return d_ptr != nullptr;
}
inline bool ParseResult::isVersionSet() const {
return isRoleSet(Option::Version);
}
inline bool ParseResult::isHelpSet() const {
return isRoleSet(Option::Help);
}
inline bool ParseResult::isArgumentSet(const Argument &arg) const {
return isArgumentSet(indexOfArgument(arg.name()));
}
inline bool ParseResult::isArgumentSet(const std::string &name) const {
return isArgumentSet(indexOfArgument(name));
}
inline bool ParseResult::isArgumentSet(int index) const {
return !values(index).empty();
}
inline const std::vector<Value> &ParseResult::values(const Argument &arg) const {
return values(indexOfArgument(arg.name()));
}
inline const std::vector<Value> &ParseResult::values(const std::string &name) const {
return values(indexOfArgument(name));
}
inline Value ParseResult::value(const Argument &arg) const {
return value(indexOfArgument(arg.name()));
}
inline Value ParseResult::value(const std::string &name) const {
return value(indexOfArgument(name));
}
inline bool ParseResult::isOptionSet(const Option &option) const {
return this->option(indexOfOption(option.token())).count() > 0;
}
inline bool ParseResult::isOptionSet(const std::string &token) const {
return option(indexOfOption(token)).count() > 0;
}
inline bool ParseResult::isOptionSet(int index) const {
return option(index).count() > 0;
}
inline OptionResult ParseResult::option(const Option &option) const {
return this->option(indexOfOption(option.token()));
}
inline OptionResult ParseResult::option(const std::string &token) const {
return option(indexOfOption(token));
}
inline Value ParseResult::valueForOption(const Option &option) const {
return this->option(indexOfOption(option.token())).value();
}
inline Value ParseResult::valueForOption(const std::string &token) const {
return option(indexOfOption(token)).value();
}
inline Value ParseResult::valueForOption(int index) const {
return option(index).value();
}
}
#endif // PARSERESULT_H

View File

@@ -0,0 +1,72 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef SHAREDBASE_H
#define SHAREDBASE_H
#include <utility>
#include <syscmdline/global.h>
namespace SysCmdLine {
class SharedBasePrivate;
class SYSCMDLINE_EXPORT SharedBase {
SYSCMDLINE_DECL_PRIVATE(SharedBase)
public:
virtual ~SharedBase();
SharedBase(const SharedBase &other);
inline SharedBase(SharedBase &&other) noexcept;
SharedBase &operator=(const SharedBase &other);
inline SharedBase &operator=(SharedBase &&other) noexcept;
public:
inline void swap(SharedBase &other) noexcept;
bool isDetached() const;
void detach();
protected:
SharedBase(SharedBasePrivate *d);
SharedBasePrivate *d_ptr;
};
inline SharedBase::SharedBase(SharedBase &&other) noexcept : d_ptr(nullptr) {
swap(other);
}
inline SharedBase &SharedBase::operator=(SharedBase &&other) noexcept {
swap(other);
return *this;
}
void SharedBase::swap(SharedBase &other) noexcept {
std::swap(d_ptr, other.d_ptr);
}
}
#endif // SHAREDBASE_H

View File

@@ -0,0 +1,75 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef STRINGS_H
#define STRINGS_H
namespace SysCmdLine {
namespace Strings {
enum TextCategory {
ParseError,
Title,
OptionRole,
Information,
Token,
};
enum TitleText {
Error,
Usage,
Description,
Arguments,
Options,
Commands,
Required,
Default,
ExpectedValues,
};
enum OptionRoleText {
NoRole,
Debug,
Verbose,
Version,
Help,
};
enum InformationText {
MatchCommand,
};
enum TokenText {
OptionalCommands,
OptionalOptions,
};
}
}
#endif // STRINGS_H

View File

@@ -0,0 +1,81 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef SYMBOL_H
#define SYMBOL_H
#include <functional>
#include <string>
#include <syscmdline/sharedbase.h>
namespace SysCmdLine {
class SymbolPrivate;
class SYSCMDLINE_EXPORT Symbol : public SharedBase {
SYSCMDLINE_DECL_PRIVATE(Symbol)
public:
enum SymbolType {
ST_Command,
ST_Option,
ST_Argument,
};
enum HelpPosition {
HP_Usage,
HP_ErrorText,
HP_FirstColumn,
HP_SecondColumn,
};
using HelpProvider =
std::function<std::string(const Symbol * /* this */, HelpPosition /* pos */,
int /* displayOptions */, void * /* extra */)>;
inline std::string helpText(HelpPosition pos, int displayOptions) const;
virtual std::string helpText(HelpPosition pos, int displayOptions, void *extra) const;
public:
SymbolType type() const;
std::string description() const;
void setDescription(const std::string &desc);
HelpProvider helpProvider() const;
void setHelpProvider(const HelpProvider &helpProvider);
protected:
Symbol(SymbolPrivate *d);
};
inline std::string Symbol::helpText(Symbol::HelpPosition pos, int displayOptions) const {
return helpText(pos, displayOptions, nullptr);
}
}
#endif // SYMBOL_H

View File

@@ -0,0 +1,83 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef SYSTEM_H
#define SYSTEM_H
#include <string>
#include <vector>
#include <syscmdline/global.h>
namespace SysCmdLine {
SYSCMDLINE_EXPORT std::string wideToUtf8(const std::wstring &s);
SYSCMDLINE_EXPORT std::wstring utf8ToWide(const std::string &s);
#ifdef _WIN32
SYSCMDLINE_EXPORT std::string ansiToUtf8(const std::string &s);
#endif
SYSCMDLINE_EXPORT std::string appPath();
SYSCMDLINE_EXPORT std::string appDirectory();
SYSCMDLINE_EXPORT std::string appFileName();
SYSCMDLINE_EXPORT std::string appName();
SYSCMDLINE_EXPORT std::vector<std::string> commandLineArguments();
enum ConsoleColor {
DefaultColor = -1,
Black = 0x0,
Red = 0x1,
Green = 0x2,
Blue = 0x4,
Yellow = Red | Green,
Purple = Red | Blue,
Cyan = Green | Blue,
White = Red | Green | Blue,
Intensified = 0x100,
};
int u8printf(int foreground, int background, const char *fmt, ...)
SYSCMDLINE_PRINTF_FORMAT(3, 4);
int u8vprintf(int foreground, int background, const char *fmt, va_list args);
enum MessageType {
MT_Debug,
MT_Message,
MT_Healthy,
MT_Warning,
MT_Critical,
};
SYSCMDLINE_EXPORT int u8debug(MessageType messageType, bool highlight, const char *fmt, ...)
SYSCMDLINE_PRINTF_FORMAT(3, 4);
SYSCMDLINE_EXPORT int u8info(const char *fmt, ...) SYSCMDLINE_PRINTF_FORMAT(1, 2);
}
#endif // SYSTEM_H

View File

@@ -0,0 +1,120 @@
/****************************************************************************
*
* MIT License
*
* Copyright (c) 2023 SineStriker
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
****************************************************************************/
#ifndef VALUE_H
#define VALUE_H
#include <string>
#include <vector>
#include <cstdint>
#include <syscmdline/global.h>
namespace SysCmdLine {
class SYSCMDLINE_EXPORT Value {
public:
enum Type {
Null,
Bool,
Int,
Int64,
Double,
String,
};
Value(Type type = Null);
inline Value(bool b);
inline Value(int i);
inline Value(int64_t l);
inline Value(double d);
inline Value(const std::string &s);
inline Value(const char *ch, int size = -1);
~Value();
Value(const Value &other);
Value(Value &&other) noexcept;
Value &operator=(const Value &other);
Value &operator=(Value &&other) noexcept;
inline Type type() const;
bool isEmpty() const;
bool toBool() const;
int toInt() const;
int64_t toInt64() const;
double toDouble() const;
std::string toString() const;
bool operator==(const Value &other) const;
bool operator!=(const Value &other) const;
static Value fromString(const std::string &s, Type type);
static std::vector<std::string> toStringList(const std::vector<Value> &values);
static const char *typeName(Type type);
protected:
union {
bool b;
int i;
int64_t l;
double d;
std::string *s;
} data;
Type _type;
};
inline Value::Value(bool b) : _type(Bool) {
data.b = b;
}
inline Value::Value(int i) : _type(Int) {
data.i = i;
}
inline Value::Value(int64_t l) : _type(Int64) {
data.l = l;
}
inline Value::Value(double d) : _type(Double) {
data.d = d;
}
inline Value::Value(const std::string &s) : _type(String) {
data.s = new std::string(s);
}
inline Value::Value(const char *ch, int size) : _type(String) {
data.s = size >= 0 ? new std::string(ch, size) : new std::string(ch);
}
inline Value::Type Value::type() const {
return _type;
}
}
#endif // VALUE_H

View File

@@ -0,0 +1,312 @@
#include "argument.h"
#include "argument_p.h"
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
# include <stdexcept>
#endif
#include <algorithm>
#include "strings.h"
#include "parser.h"
#include "utils_p.h"
namespace SysCmdLine {
ArgumentPrivate::ArgumentPrivate(std::string name, const std::string &desc, bool required,
Value defaultValue)
: SymbolPrivate(Symbol::ST_Argument, desc), name(std::move(name)), required(required),
defaultValue(std::move(defaultValue)), number(Argument::Single) {
}
SymbolPrivate *ArgumentPrivate::clone() const {
return new ArgumentPrivate(*this);
}
Argument::Argument() : Argument(std::string()) {
}
Argument::Argument(const std::string &name, const std::string &desc, bool required,
const Value &defaultValue)
: Symbol(new ArgumentPrivate(name, desc, required, defaultValue)) {
}
std::string Argument::displayedText() const {
Q_D2(Argument);
std::string res = d->displayName.empty() ? ("<" + d->name + ">") : d->displayName;
return res;
}
std::string Argument::helpText(Symbol::HelpPosition pos, int displayOptions,
void *extra) const {
Q_D2(Argument);
if (auto ss = Symbol::helpText(pos, displayOptions, extra); !ss.empty()) {
return ss;
}
switch (pos) {
case Symbol::HP_Usage: {
return displayedText() + ((d->number != Single) ? "..." : "");
}
case Symbol::HP_SecondColumn: {
auto textProvider = reinterpret_cast<Parser::TextProvider>(extra);
if (!textProvider) {
textProvider = Parser::defaultTextProvider();
}
std::string appendix;
// Required
if (d->required && (displayOptions & Parser::ShowArgumentIsRequired)) {
appendix += " [" + textProvider(Strings::Title, Strings::Required) + "]";
}
// Default Value
if (d->defaultValue.type() != Value::Null &&
(displayOptions & Parser::ShowArgumentDefaultValue)) {
appendix += " [" + textProvider(Strings::Title, Strings::Default) + ": " +
d->defaultValue.toString() + "]";
}
// Expected Values
if (!d->expectedValues.empty() &&
(displayOptions & Parser::ShowArgumentExpectedValues)) {
std::vector<std::string> values;
values.reserve(d->expectedValues.size());
for (const auto &item : d->expectedValues) {
switch (item.type()) {
case Value::String:
values.push_back("\"" + item.toString() + "\"");
break;
default:
values.push_back(item.toString());
break;
}
}
appendix += " [" + textProvider(Strings::Title, Strings::ExpectedValues) +
": " + Utils::join(values, ", ") + "]";
}
return d->desc + appendix;
}
default:
break;
}
return displayedText();
}
std::string Argument::name() const {
Q_D2(Argument);
return d->name;
}
void Argument::setName(const std::string &name) {
Q_D(Argument);
d->name = name;
}
std::string Argument::displayName() const {
Q_D2(Argument);
return d->displayName;
}
void Argument::setDisplayName(const std::string &displayName) {
Q_D(Argument);
d->displayName = displayName;
}
bool Argument::isRequired() const {
Q_D2(Argument);
return d->required;
}
void Argument::setRequired(bool required) {
Q_D(Argument);
d->required = required;
}
Value Argument::defaultValue() const {
Q_D2(Argument);
return d->defaultValue;
}
void Argument::setDefaultValue(const Value &defaultValue) {
Q_D(Argument);
d->defaultValue = defaultValue;
}
const std::vector<Value> &Argument::expectedValues() const {
Q_D2(Argument);
return d->expectedValues;
}
void Argument::setExpectedValues(const std::vector<Value> &expectedValues) {
Q_D(Argument);
d->expectedValues = expectedValues;
}
bool Argument::multiValueEnabled() const {
Q_D2(Argument);
return d->number != Single;
}
void Argument::setMultiValueEnabled(bool on) {
Q_D(Argument);
d->number = on ? MultiValue : Single;
}
Argument::Number Argument::number() const {
Q_D2(Argument);
return d->number;
}
void Argument::setNumber(Number number) {
Q_D(Argument);
d->number = number;
}
Argument::Validator Argument::validator() const {
Q_D2(Argument);
return d->validator;
}
void Argument::setValidator(const Validator &validator) {
Q_D(Argument);
d->validator = validator;
}
ArgumentHolderPrivate::ArgumentHolderPrivate(Symbol::SymbolType type, const std::string &desc)
: SymbolPrivate(type, desc) {
}
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
void ArgumentHolderPrivate::checkAddedArgument(const Argument &arg) const {
const auto &d = arg.d_func();
const auto &name = d->name;
// Empty argument name?
if (name.empty()) {
throw std::runtime_error("argument doesn't have a name");
}
// Duplicated argument name?
if (std::any_of(arguments.begin(), arguments.end(), [&name](const Argument &arg) {
return arg.name() == name; //
})) {
throw std::runtime_error(Utils::formatText("argument name \"%1\" duplicated", {name}));
}
// Required argument behind optional one?
if (!arguments.empty() && arguments.back().isOptional() && d->required) {
throw std::runtime_error("required argument after required arguments is prohibited");
}
bool hasMultiValueArgument =
std::any_of(arguments.begin(), arguments.end(), [&name](const Argument &arg) {
return arg.multiValueEnabled(); //
});
if (arg.multiValueEnabled()) {
// Multiple multi-value argument?
if (hasMultiValueArgument) {
throw std::runtime_error("at most one multi-value argument");
}
hasMultiValueArgument = true;
} else if (hasMultiValueArgument && !d->required) {
// Optional argument after multi-value argument?
throw std::runtime_error("required argument after multi-value argument is prohibited");
}
{
const auto &expectedValues = d->expectedValues;
const auto &defaultValue = d->defaultValue;
if (!expectedValues.empty()) {
// Null expected value?
for (size_t i = 0; i < expectedValues.size(); ++i) {
if (expectedValues[i].type() == Value::Null) {
throw std::runtime_error(
Utils::formatText("expected value at %1 is null", {std::to_string(i)}));
}
}
// Invalid default value?
if (defaultValue.type() != Value::Null &&
std::find(expectedValues.begin(), expectedValues.end(), defaultValue) ==
expectedValues.end()) {
throw std::runtime_error(Utils::formatText(
"default value \"%1\" is not in expect values", {defaultValue.toString()}));
}
}
const auto &validator = d->validator;
if (validator && defaultValue.type() != Value::Null) {
// Validator is incompatible with the default value?
Value val;
std::string errorMessage;
auto res = validator(defaultValue.toString(), &val, &errorMessage);
if (!res) {
throw std::runtime_error("validator is not able to handle the default value.");
}
}
}
// ...
}
#endif
std::string ArgumentHolder::displayedArguments(int displayOptions) const {
Q_D2(ArgumentHolder);
// Use C Style to traverse
auto arguments = d->arguments.data();
int size = int(d->arguments.size());
std::string ss;
int optionalIdx = size;
for (int i = 0; i < size; ++i) {
if (!arguments[i].isRequired()) {
optionalIdx = i;
break;
}
}
if (optionalIdx > 0) {
for (int i = 0; i < optionalIdx - 1; ++i) {
ss += arguments[i].helpText(Symbol::HP_Usage, displayOptions, nullptr);
ss += " ";
}
ss += arguments[optionalIdx - 1].helpText(Symbol::HP_Usage, displayOptions, nullptr);
}
if (optionalIdx < size) {
if (optionalIdx > 0)
ss += " ";
ss += "[";
for (int i = optionalIdx; i < size - 1; ++i) {
ss += arguments[i].helpText(Symbol::HP_Usage, displayOptions, nullptr);
ss += " ";
}
ss += arguments[size - 1].helpText(Symbol::HP_Usage, displayOptions, nullptr);
ss += "]";
}
return ss;
}
void ArgumentHolder::addArguments(const std::vector<Argument> &arguments) {
Q_D(ArgumentHolder);
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
for (const auto &arg : arguments) {
d->checkAddedArgument(arg);
d->arguments.push_back(arg);
}
#else
// d->arguments.insert(d->arguments.end(), arguments.begin(), arguments.end());
d->arguments = Utils::concatVector(d->arguments, arguments);
#endif
}
ArgumentHolder::ArgumentHolder(ArgumentHolderPrivate *d) : Symbol(d) {
}
}

View File

@@ -0,0 +1,40 @@
#ifndef ARGUMENT_P_H
#define ARGUMENT_P_H
#include "argument.h"
#include "symbol_p.h"
namespace SysCmdLine {
class ArgumentPrivate : public SymbolPrivate {
public:
ArgumentPrivate(std::string name, const std::string &desc, bool required,
Value defaultValue);
SymbolPrivate *clone() const override;
public:
std::string name;
bool required;
Value defaultValue;
std::vector<Value> expectedValues;
std::string displayName;
Argument::Number number;
Argument::Validator validator;
};
class ArgumentHolderPrivate : public SymbolPrivate {
public:
ArgumentHolderPrivate(Symbol::SymbolType type, const std::string &desc);
public:
std::vector<Argument> arguments;
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
void checkAddedArgument(const Argument &arg) const;
#endif
};
}
#endif // ARGUMENT_P_H

View File

@@ -0,0 +1,480 @@
#include "command.h"
#include "command_p.h"
#include <algorithm>
#include <utility>
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
# include <stdexcept>
# include "option_p.h"
#endif
#include "parser.h"
#include "utils_p.h"
#include "option_p.h"
#include "strings.h"
namespace SysCmdLine {
SharedBasePrivate *CommandCataloguePrivate::clone() const {
return new CommandCataloguePrivate(*this);
}
static void addIndexes(GenericMap &indexes, StringList &keys, const std::string &key,
const StringList &val) {
auto it = indexes.find(key);
if (it == indexes.end()) {
indexes[key] = ele(new StringList(val));
keys.push_back(key);
return;
}
auto list = *it->second.sl;
// list.insert(list.end(), val.begin(), val.end());
list = Utils::concatVector(list, val);
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
for (size_t i = 0; i < list.size(); ++i) {
for (size_t j = 0; j < i; ++j) {
if (list[i] == list[j]) {
throw std::runtime_error(Utils::formatText(
R"(duplicated items "%1" and "%2" in catalogue)", {
list[i],
list[j],
}));
}
}
}
#endif
}
CommandCatalogue::CommandCatalogue() : SharedBase(new CommandCataloguePrivate()) {
}
void CommandCatalogue::addArguments(const std::string &name, const StringList &args) {
Q_D(CommandCatalogue);
addIndexes(d->arg.data, d->arguments, name, args);
}
void CommandCatalogue::addOptions(const std::string &name, const StringList &options) {
Q_D(CommandCatalogue);
addIndexes(d->opt.data, d->options, name, options);
}
void CommandCatalogue::addCommands(const std::string &name, const StringList &commands) {
Q_D(CommandCatalogue);
addIndexes(d->cmd.data, d->commands, name, commands);
}
CommandPrivate::CommandPrivate(std::string name, const std::string &desc)
: ArgumentHolderPrivate(Symbol::ST_Command, desc), name(std::move(name)) {
}
SharedBasePrivate *CommandPrivate::clone() const {
return new CommandPrivate(*this);
}
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
void CommandPrivate::checkAddedCommand(const Command &cmd) const {
const auto &name = cmd.name();
// Empty command name?
if (name.empty()) {
throw std::runtime_error("command doesn't have a name");
}
// Duplicated command name?
if (std::any_of(commands.begin(), commands.end(), [&name](const Command &command) {
return command.name() == name; //
})) {
throw std::runtime_error(Utils::formatText("command name \"%1\" duplicated", {name}));
}
}
void CommandPrivate::checkAddedOption(const Option &opt,
const std::string &exclusiveGroup) const {
const auto &d = opt.d_func();
// Empty token?
if (d->tokens.empty()) {
throw std::runtime_error("option doesn't have any token");
}
for (const auto &token : d->tokens) {
// empty token?
if (token.empty()) {
throw std::runtime_error("empty option token");
}
// Invalid token?
if (!(token.front() == '-' || token.front() == '/')) {
throw std::runtime_error(
Utils::formatText("option token \"%1\" is invalid", {token}));
}
// Duplicated token?
if (std::any_of(options.begin(), options.end(), [&token](const Option &opt) {
for (const auto &item : opt.d_func()->tokens)
if (item == token)
return true;
const auto &tokens = opt.d_func()->tokens;
return std::any_of(tokens.begin(), tokens.end(),
[&token](const std::string &cur) {
return cur == token; //
});
})) {
throw std::runtime_error(
Utils::formatText("option token \"%1\" duplicated", {token}));
}
}
// Global and exclusive option?
if (!exclusiveGroup.empty() && opt.isGlobal()) {
throw std::runtime_error(Utils::formatText(
"global option \"%s\" cannot be in any exclusive group", {opt.token()}));
}
switch (opt.priorLevel()) {
case Option::AutoSetWhenNoSymbols: {
// Auto-option but required?
if (d->required) {
throw std::runtime_error(
Utils::formatText("auto-option \"%s\" cannot be required", {opt.token()}));
}
// Auto-option with argument?
if (!d->arguments.empty()) {
throw std::runtime_error(Utils::formatText(
"auto-option \"%s\" cannot have any argument", {opt.token()}));
}
break;
}
case Option::ExclusiveToOptions:
case Option::ExclusiveToAll: {
// Multiple exclusively prior option?
if (opt.isRequired() &&
std::any_of(options.begin(), options.end(), [](const Option &opt) {
return opt.priorLevel() >= Option::ExclusiveToOptions && opt.isRequired();
})) {
throw std::runtime_error("at most one exclusively prior and required option");
}
break;
}
default:
break;
}
// Inconsistent exclusive group?
if (!exclusiveGroup.empty()) {
for (size_t i = 0; i < options.size(); ++i) {
if (optionGroupNames[i] == exclusiveGroup &&
options[i].isRequired() != opt.isRequired()) {
throw std::runtime_error(
Utils::formatText(R"(option "%1" is %2, but exclusive group "3" isn't)",
{
opt.token(),
opt.isRequired() ? "required" : "required",
exclusiveGroup,
}));
}
}
}
}
#endif
Command::Command() : Command({}, {}) {
}
Command::Command(const std::string &name, const std::string &desc,
const std::vector<Argument> &args)
: ArgumentHolder(new CommandPrivate(name, desc)) {
if (!args.empty())
addArguments(args);
}
std::string Command::helpText(Symbol::HelpPosition pos, int displayOptions, void *extra) const {
Q_D2(Command);
if (auto ss = ArgumentHolder::helpText(pos, displayOptions, extra); !ss.empty()) {
return ss;
}
switch (pos) {
case HP_Usage: {
// `extra` should be null or a 3 pointer array
auto a = reinterpret_cast<void **>(extra);
// all options
const auto *globalOptions =
a ? reinterpret_cast<std::vector<Option> *>(a[0]) : nullptr;
auto options = d->options;
if (globalOptions) {
// options.insert(options.begin(), globalOptions->begin(),
// globalOptions->end());
options = OptionPrivate::reorderOptions(options, *globalOptions);
}
StringList groupNames(globalOptions ? globalOptions->size() : 0);
// groupNames.insert(groupNames.end(), d->optionGroupNames.begin(),
// d->optionGroupNames.end());
groupNames = Utils::concatVector(groupNames, d->optionGroupNames);
auto textProvider = a ? reinterpret_cast<Parser::TextProvider>(a[1]) : nullptr;
if (!textProvider) {
textProvider = Parser::defaultTextProvider();
}
std::string ss;
// write command name
ss += d->name;
// Build exclusive option group indexes
// group name -> option subscripts (vector<int> *)
GenericMap exclusiveGroupIndexes = [](const CommandPrivate *d) {
GenericMap res;
for (int i = 0; i < d->optionGroupNames.size(); ++i) {
const auto &group = d->optionGroupNames[i];
if (group.empty())
continue;
auto it = res.find(group);
if (it != res.end()) {
it->second.il->push_back(i);
continue;
}
res[group] = ele(new IntList({i}));
}
return res;
}(d);
auto addArgumentsHelp = [&](bool front) {
if (bool((displayOptions & Parser::ShowOptionsBehindArguments)) != front &&
!d->arguments.empty()) {
ss += " " + displayedArguments(displayOptions);
}
};
std::vector<int> visitedOptions(options.size()); // pointer -> none
int visitedCount = 0;
auto addVisited = [&visitedOptions, &visitedCount](int idx) {
if (visitedOptions[idx])
return;
visitedOptions[idx] = 1;
visitedCount++;
};
auto addExclusiveOptions = [&](int optIdx, bool required) {
const auto &opt = options[optIdx];
const auto &groupName = groupNames[optIdx];
do {
if (groupName.empty())
break;
const auto &optionIndexes =
*exclusiveGroupIndexes.find(groupName)->second.il;
if (optionIndexes.size() <= 1)
break;
// Add exclusive groups
if (required)
ss += "(";
StringList exclusiveOptions;
exclusiveOptions.reserve(optionIndexes.size());
for (const auto &idx : optionIndexes) {
const auto &curOpt = options[idx];
exclusiveOptions.push_back(
curOpt.helpText(Symbol::HP_Usage, displayOptions));
addVisited(idx);
}
ss += Utils::join(exclusiveOptions, " | ");
if (required)
ss += ")";
return;
} while (false);
// Add single
ss += opt.helpText(Symbol::HP_Usage, displayOptions);
addVisited(optIdx);
};
auto addOptionsHelp = [&](bool required) {
for (int i = 0; i < options.size(); ++i) {
const auto &opt = options[i];
if (opt.isRequired() != required || visitedOptions[i]) {
continue;
}
ss += " ";
if (!required)
ss += "[";
addExclusiveOptions(i, required);
if (!required)
ss += "]";
}
};
int frontOptionInsertionPos = ss.size();
// write forward arguments
addArgumentsHelp(true);
// write required options
if (!(displayOptions & Parser::DontShowRequiredOptionsOnUsage))
addOptionsHelp(true);
// write optional options
if ((displayOptions & Parser::ShowOptionalOptionsOnUsage) &&
visitedCount < options.size())
addOptionsHelp(false);
// write backward arguments
addArgumentsHelp(false);
if (!d->commands.empty()) {
ss += " [" + textProvider(Strings::Token, Strings::OptionalCommands) + "]";
}
if (visitedCount < options.size() || !d->commands.empty()) {
std::string optionHint =
" [" + textProvider(Strings::Token, Strings::OptionalOptions) + "]";
if ((displayOptions & Parser::ShowOptionsHintFront)) {
ss = ss.substr(0, frontOptionInsertionPos) + optionHint +
ss.substr(frontOptionInsertionPos);
} else {
ss += optionHint;
}
}
// release indexes
for (const auto &pair : std::as_const(exclusiveGroupIndexes)) {
delete pair.second.il;
}
return ss;
}
case HP_ErrorText:
case HP_FirstColumn: {
return d->name;
}
case HP_SecondColumn: {
return d->desc;
}
}
return {};
}
std::string Command::name() const {
Q_D2(Command);
return d->name;
}
void Command::setName(const std::string &name) {
Q_D(Command);
d->name = name;
}
void Command::addOptions(const std::vector<Option> &options, const std::string &group) {
Q_D(Command);
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
for (const auto &opt : options) {
d->checkAddedOption(opt, group);
d->options.push_back(opt);
d->optionGroupNames.push_back(group);
}
#else
// d->options.insert(d->options.end(), options.begin(), options.end());
// d->optionGroupNames.insert(d->optionGroupNames.end(), options.size(), group);
d->options = Utils::concatVector(d->options, options);
d->optionGroupNames = Utils::concatVector(d->optionGroupNames, {options.size(), group});
#endif
}
int Command::commandCount() const {
Q_D2(Command);
return int(d->commands.size());
}
Command Command::command(int index) const {
Q_D2(Command);
return d->commands[index];
}
void Command::addCommands(const std::vector<Command> &commands) {
Q_D(Command);
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
for (const auto &cmd : commands) {
d->checkAddedCommand(cmd);
d->commands.push_back(cmd);
}
#else
// d->commands.insert(d->commands.end(), commands.begin(), commands.end());
d->commands = Utils::concatVector(d->commands, commands);
#endif
}
std::string Command::detailedDescription() const {
Q_D2(Command);
return d->detailedDescription;
}
void Command::setDetailedDescription(const std::string &detailedDescription) {
Q_D(Command);
d->detailedDescription = detailedDescription;
}
Command::Handler Command::handler() const {
Q_D2(Command);
return d->handler;
}
void Command::setHandler(const Handler &handler) {
Q_D(Command);
d->handler = handler;
}
CommandCatalogue Command::catalogue() const {
Q_D2(Command);
return d->catalogue;
}
void Command::setCatalogue(const CommandCatalogue &catalogue) {
Q_D(Command);
d->catalogue = catalogue;
}
std::string Command::versionString() const {
Q_D2(Command);
return d->version;
}
void Command::addVersionOption(const std::string &version, const StringList &tokens,
const std::string &desc) {
Q_D(Command);
d->version = version;
Option versionOption(Option::Version, tokens, desc);
versionOption.setPriorLevel(Option::IgnoreMissingSymbols);
addOption(versionOption);
}
void Command::addHelpOption(bool showHelpIfNoArg, bool global, const StringList &tokens,
const std::string &desc) {
Q_D(Command);
Option helpOption(Option::Help, tokens, desc);
helpOption.setPriorLevel(showHelpIfNoArg ? Option::AutoSetWhenNoSymbols
: Option::IgnoreMissingSymbols);
helpOption.setGlobal(global);
addOption(helpOption);
}
HelpLayout Command::helpLayout() const {
Q_D2(Command);
return d->helpLayout;
}
void Command::setHelpLayout(const HelpLayout &helpLayout) {
Q_D(Command);
d->helpLayout = helpLayout;
}
}

View File

@@ -0,0 +1,53 @@
#ifndef COMMAND_P_H
#define COMMAND_P_H
#include "argument_p.h"
#include "command.h"
#include "map_p.h"
namespace SysCmdLine {
class CommandCataloguePrivate : public SharedBasePrivate {
public:
SharedBasePrivate *clone() const;
StringListMapWrapper arg;
StringListMapWrapper opt;
StringListMapWrapper cmd;
StringList arguments;
StringList options;
StringList commands;
};
class CommandPrivate : public ArgumentHolderPrivate {
public:
CommandPrivate(std::string name, const std::string &desc);
SharedBasePrivate *clone() const override;
public:
std::string name;
std::vector<Option> options;
std::vector<std::string> optionGroupNames;
std::vector<Command> commands;
std::string version;
std::string detailedDescription;
CommandCatalogue catalogue;
Command::Handler handler;
HelpLayout helpLayout;
#ifdef SYSCMDLINE_ENABLE_VALIDITY_CHECK
void checkAddedCommand(const Command &cmd) const;
void checkAddedOption(const Option &opt, const std::string &exclusiveGroup) const;
#endif
};
}
#endif // COMMAND_P_H

View File

@@ -0,0 +1,161 @@
#include "helplayout.h"
#include "helplayout_p.h"
#include <algorithm>
#include "system.h"
#include "parser_p.h"
#include "utils_p.h"
namespace SysCmdLine {
static inline void printLast(bool hasNext) {
if (hasNext)
u8info("\n");
}
static void defaultTextsPrinter(MessageType messageType, bool highlight,
const HelpLayout::Context &ctx) {
if (ctx.text->lines.empty())
return;
if (ctx.text->title.empty()) {
// Content
u8debug(messageType, highlight, "%s\n", ctx.text->lines.data());
} else {
// Title
u8debug(messageType, highlight, "%s:\n", ctx.text->title.data());
// Content
auto lines = Utils::split(ctx.text->lines, "\n");
for (const auto &line : std::as_const(lines))
u8debug(messageType, highlight, "%s%s\n", ctx.parser->d_func()->indent().data(),
line.data());
}
printLast(ctx.hasNext);
}
static void defaultInfoPrinter(const HelpLayout::Context &ctx) {
defaultTextsPrinter(MT_Debug, false, ctx);
}
static void defaultWarnPrinter(const HelpLayout::Context &ctx) {
defaultTextsPrinter(MT_Warning, false, ctx);
}
static void defaultErrorPrinter(const HelpLayout::Context &ctx) {
defaultTextsPrinter(MT_Critical, true, ctx);
}
static void defaultListPrinter(const HelpLayout::Context &ctx) {
if (ctx.list->firstColumn.empty())
return;
// Title
u8info("%s:\n", ctx.text->title.data());
const auto &list = ctx.list;
const auto &parserData = ctx.parser->d_func();
int widest = ctx.firstColumnLength;
if (widest == 0) {
for (const auto &item : list->firstColumn)
widest = std::max(widest, int(item.size()));
}
std::vector<std::string> res;
for (size_t i = 0; i < list->firstColumn.size(); ++i) {
const auto &first = list->firstColumn[i];
const auto &second = list->secondColumn[i];
auto lines = Utils::split(second, "\n");
if (lines.empty())
lines.emplace_back();
{
std::string ss;
ss += parserData->indent();
ss += first;
ss += std::string(widest - first.size(), ' ');
ss += parserData->spacing();
ss += lines.front();
res.push_back(ss);
}
for (size_t j = 1; j < lines.size(); ++j) {
std::string ss;
ss += parserData->indent();
ss += std::string(widest, ' ');
ss += parserData->spacing();
ss += std::as_const(lines)[j];
res.push_back(ss);
}
}
for (const auto &line : std::as_const(res)) {
u8info("%s\n", line.data());
}
printLast(ctx.hasNext);
}
HelpLayout::HelpLayout() : SharedBase(new HelpLayoutPrivate()) {
}
void HelpLayout::addHelpTextItem(HelpTextItem type, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back(
{HelpLayoutPrivate::HelpText, type, out ? out : defaultInfoPrinter, {}, {}});
}
void HelpLayout::addHelpListItem(HelpListItem type, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back(
{HelpLayoutPrivate::HelpList, type, out ? out : defaultListPrinter, {}, {}});
}
void HelpLayout::addMessageItem(MessageItem type, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back({HelpLayoutPrivate::Message, type, out ? out : [](MessageItem item) {
switch (item) {
case MI_Warning:
return defaultWarnPrinter;
case MI_Critical:
return defaultErrorPrinter;
default:
break ;
}
return defaultInfoPrinter;
}(type), {}, {}});
}
void HelpLayout::addUserHelpTextItem(const Text &text, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back(
{HelpLayoutPrivate::UserHelpText, 0, out ? out : defaultInfoPrinter, text, {}});
}
void HelpLayout::addUserHelpListItem(const List &list, const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back(
{HelpLayoutPrivate::UserHelpList, 0, out ? out : defaultListPrinter, {}, list});
}
void HelpLayout::addUserIntroItem(const Output &out) {
Q_D(HelpLayout);
d->itemDataList.push_back({HelpLayoutPrivate::UserIntro, 0, out, {}, {}});
}
HelpLayout HelpLayout::defaultHelpLayout() {
static HelpLayout instance = []() {
HelpLayout res;
res.addHelpTextItem(HT_Prologue);
res.addMessageItem(MI_Information);
res.addMessageItem(MI_Warning);
res.addMessageItem(MI_Critical);
res.addHelpTextItem(HT_Description);
res.addHelpTextItem(HT_Usage);
res.addHelpListItem(HL_Arguments);
res.addHelpListItem(HL_Options);
res.addHelpListItem(HL_Commands);
res.addHelpTextItem(HT_Epilogue);
return res;
}();
return instance;
}
}

View File

@@ -0,0 +1,41 @@
#ifndef HELPLAYOUT_P_H
#define HELPLAYOUT_P_H
#include <utility>
#include "sharedbase_p.h"
#include "helplayout.h"
namespace SysCmdLine {
class HelpLayoutPrivate : public SharedBasePrivate {
public:
HelpLayoutPrivate *clone() const {
return new HelpLayoutPrivate(*this);
}
enum ItemType {
HelpText,
HelpList,
Message,
UserHelpText,
UserHelpList,
UserIntro,
};
struct ItemData {
ItemType itemType;
int index;
HelpLayout::Output out;
// Maybe
HelpLayout::Text text;
HelpLayout::List list;
};
std::vector<ItemData> itemDataList;
};
}
#endif // HELPLAYOUT_P_H

View File

@@ -0,0 +1,114 @@
#ifndef MAP_P_H
#define MAP_P_H
#include <map>
#include <string>
#include <vector>
namespace SysCmdLine {
using StringList = std::vector<std::string>;
using IntList = std::vector<int>;
union Ele {
int i;
size_t s;
IntList *il;
StringList *sl;
void *p;
};
inline Ele ele(int i) noexcept {
#if __cplusplus >= 202002L
return Ele{.i = i};
#else
Ele res;
res.i = i;
return res;
#endif
}
inline Ele ele(size_t s) noexcept {
#if __cplusplus >= 202002L
return Ele{.s = s};
#else
Ele res;
res.s = s;
return res;
#endif
}
inline Ele ele(IntList *il) noexcept {
#if __cplusplus >= 202002L
return Ele{.il = il};
#else
Ele res;
res.il = il;
return res;
#endif
}
inline Ele ele(StringList *sl) noexcept {
#if __cplusplus >= 202002L
return Ele{.sl = sl};
#else
Ele res;
res.sl = sl;
return res;
#endif
}
inline Ele ele(void *p) noexcept {
#if __cplusplus >= 202002L
return Ele{.p = p};
#else
Ele res;
res.p = p;
return res;
#endif
}
// Avoiding template specialization greatly helps to reduce the binary size,
// so we only use this map in the library implementation.
using GenericMap = std::map<std::string, Ele>;
struct StringListMapWrapper {
StringListMapWrapper() = default;
StringListMapWrapper(const StringListMapWrapper &other) {
for (const auto &pair : other.data) {
data[pair.first] = ele(new StringList(*pair.second.sl));
}
}
~StringListMapWrapper() {
for (const auto &pair : std::as_const(data)) {
delete pair.second.sl;
}
}
GenericMap data;
};
struct IntListMapWrapper {
IntListMapWrapper() = default;
IntListMapWrapper(const IntListMapWrapper &other) {
for (const auto &pair : other.data) {
data[pair.first] = ele(new IntList(*pair.second.il));
}
}
~IntListMapWrapper() {
for (const auto &pair : std::as_const(data)) {
delete pair.second.il;
}
}
GenericMap data;
};
}
#endif // MAP_P_H

View File

@@ -0,0 +1,214 @@
#include "option.h"
#include "option_p.h"
#include "utils_p.h"
#include "strings.h"
#include "parser.h"
namespace SysCmdLine {
static std::vector<std::string> defaultTokensForRole(Option::Role role) {
std::vector<std::string> res;
switch (role) {
case Option::Help:
res = {"-h", "--help"};
break;
case Option::Version:
res = {"-v", "--version"};
break;
case Option::Verbose:
res = {"-V", "--verbose"};
break;
case Option::Debug:
res = {"-d", "--debug"};
break;
default:
break;
}
return res;
}
OptionPrivate::OptionPrivate(Option::Role role, const std::vector<std::string> &tokens,
const std::string &desc, bool required)
: ArgumentHolderPrivate(Symbol::ST_Option, desc), role(role), tokens(tokens),
required(required), shortMatchRule(Option::NoShortMatch), priorLevel(Option::NoPrior),
global(false), maxOccurrence(1) {
}
SymbolPrivate *OptionPrivate::clone() const {
return new OptionPrivate(*this);
}
std::vector<Option> OptionPrivate::reorderOptions(const std::vector<Option> &options,
const std::vector<Option> &globalOptions) {
std::vector<Option> res;
std::vector<Option> roles;
for (const auto &opt : options) {
if (opt.role() == Option::NoRole) {
res.push_back(opt);
continue;
}
roles.push_back(opt);
}
for (const auto &opt : globalOptions) {
if (opt.role() == Option::NoRole) {
res.push_back(opt);
continue;
}
roles.push_back(opt);
}
for (const auto &opt : std::as_const(roles)) {
res.push_back(opt);
}
return res;
}
// template <typename... Args>
// Option(const char *firstToken, Args &&...args, const std::string &desc)
// : Option(std::vector<std::string>(), desc) {
// std::vector<std::string> tokens;
// tokens.emplace_back(firstToken);
// (tokens.emplace_back(std::forward<Args>(args)), ...);
// setTokens(tokens);
// }
Option::Option() : Option(NoRole, {}) {
}
Option::Option(Role role, const std::vector<std::string> &tokens, const std::string &desc)
: ArgumentHolder(new OptionPrivate(
role, tokens.empty() ? defaultTokensForRole(role) : tokens, desc, false)) {
}
Option::Option(const std::string &token, const std::string &desc, const Argument &arg,
bool required)
: Option(std::vector<std::string>{token}, desc, arg, required) {
}
Option::Option(const std::vector<std::string> &tokens, const std::string &desc,
const Argument &arg, bool required)
: ArgumentHolder(new OptionPrivate(NoRole, tokens, desc, required)) {
if (!arg.d_func()->name.empty())
addArgument(arg);
}
std::string Option::helpText(Symbol::HelpPosition pos, int displayOptions, void *extra) const {
Q_D2(Option);
if (auto ss = ArgumentHolder::helpText(pos, displayOptions, extra); !ss.empty()) {
return ss;
}
switch (pos) {
case Symbol::HP_Usage: {
std::string appendix;
if (!d->arguments.empty()) {
appendix = " " + displayedArguments(displayOptions);
}
return d->tokens.front() + appendix;
}
case Symbol::HP_ErrorText: {
return d->tokens.front();
}
case Symbol::HP_FirstColumn: {
std::string appendix;
if (!d->arguments.empty()) {
appendix = " " + displayedArguments(displayOptions);
}
return Utils::join(d->tokens, ", ") + appendix;
}
case Symbol::HP_SecondColumn: {
auto textProvider = reinterpret_cast<Parser::TextProvider>(extra);
if (!textProvider) {
textProvider = Parser::defaultTextProvider();
}
std::string appendix;
std::string desc = d->desc;
if (desc.empty() && d->role != NoRole) {
desc = textProvider(Strings::OptionRole,
static_cast<Strings::OptionRoleText>(d->role));
}
// Required
if (d->required && (displayOptions & Parser::ShowOptionIsRequired)) {
appendix += " [" + textProvider(Strings::Title, Strings::Required) + "]";
}
return desc + appendix;
}
}
return {};
}
Option::Role Option::role() const {
Q_D2(Option);
return d->role;
}
void Option::setRole(Role role) {
Q_D(Option);
d->role = role;
}
const std::vector<std::string> &Option::tokens() const {
Q_D2(Option);
return d->tokens;
}
void Option::setTokens(const std::vector<std::string> &tokens) {
Q_D(Option);
d->tokens = tokens;
}
bool Option::isRequired() const {
Q_D2(Option);
return d->required;
}
void Option::setRequired(bool required) {
Q_D(Option);
d->required = required;
}
Option::ShortMatchRule Option::shortMatchRule() const {
Q_D2(Option);
return d->shortMatchRule;
}
void Option::setShortMatchRule(ShortMatchRule shortMatchRule) {
Q_D(Option);
d->shortMatchRule = shortMatchRule;
}
Option::PriorLevel Option::priorLevel() const {
Q_D2(Option);
return d->priorLevel;
}
void Option::setPriorLevel(PriorLevel priorLevel) {
Q_D(Option);
d->priorLevel = priorLevel;
}
bool Option::isGlobal() const {
Q_D2(Option);
return d->global;
}
void Option::setGlobal(bool on) {
Q_D(Option);
d->global = on;
}
int Option::maxOccurrence() const {
Q_D2(Option);
return d->maxOccurrence;
}
void Option::setMaxOccurrence(int max) {
Q_D(Option);
d->maxOccurrence = max;
}
}

View File

@@ -0,0 +1,31 @@
#ifndef OPTION_P_H
#define OPTION_P_H
#include "argument_p.h"
#include "option.h"
namespace SysCmdLine {
class OptionPrivate : public ArgumentHolderPrivate {
public:
OptionPrivate(Option::Role role, const std::vector<std::string> &tokens,
const std::string &desc, bool required);
SymbolPrivate *clone() const override;
public:
Option::Role role;
std::vector<std::string> tokens;
bool required;
Option::ShortMatchRule shortMatchRule;
Option::PriorLevel priorLevel;
bool global;
int maxOccurrence;
static std::vector<Option> reorderOptions(const std::vector<Option> &options,
const std::vector<Option> &globalOptions);
};
}
#endif // OPTION_P_H

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
#ifndef PARSER_P_H
#define PARSER_P_H
#include "sharedbase_p.h"
#include "parser.h"
namespace SysCmdLine {
class ParserPrivate : public SharedBasePrivate {
public:
ParserPrivate();
ParserPrivate *clone() const;
Command rootCommand;
std::string prologue, epilogue;
int displayOptions;
int sizeConfig[3] = {
4,
4,
80,
};
Parser::TextProvider textProvider;
inline std::string indent() const {
return std::string(sizeConfig[Parser::ST_Indent], ' ');
}
inline std::string spacing() const {
return std::string(sizeConfig[Parser::ST_Spacing], ' ');
}
};
}
#endif // PARSER_P_H

View File

@@ -0,0 +1,725 @@
#include "parseresult.h"
#include "parseresult_p.h"
#include <algorithm>
#include <stdexcept>
#include "utils_p.h"
#include "strings.h"
#include "system.h"
#include "parser_p.h"
#include "option_p.h"
#include "helplayout_p.h"
#include "command_p.h"
namespace SysCmdLine {
const OptionData &OptionData::sharedNull() {
static OptionData _data;
return _data;
}
static const std::vector<Value> &sharedEmptyValues() {
static std::vector<Value> _data;
return _data;
}
OptionResult::OptionResult() : data(&OptionData::sharedNull()) {
}
Option OptionResult::option() const {
auto &v = *reinterpret_cast<const OptionData *>(data);
return v.option ? *v.option : Option();
}
int OptionResult::indexOf(const std::string &name) const {
auto &v = *reinterpret_cast<const OptionData *>(data);
auto it = v.argNameIndexes.find(name);
if (it == v.argNameIndexes.end())
return -1;
return it->second.i;
}
int OptionResult::count() const {
auto &v = *reinterpret_cast<const OptionData *>(data);
return v.count;
}
std::vector<Value> OptionResult::allValues(int index) const {
auto &v = *reinterpret_cast<const OptionData *>(data);
if (index < 0 || index >= v.argSize)
return {};
std::vector<Value> allValues;
for (int i = 0; i < v.count; ++i) {
const auto &values = v.argResult[i][index];
// allValues.insert(allValues.end(), values.begin(), values.end());
allValues = Utils::concatVector(allValues, values);
}
return allValues;
}
const std::vector<Value> &OptionResult::values(int index, int n) const {
auto &v = *reinterpret_cast<const OptionData *>(data);
if (index < 0 || index >= v.argSize)
return sharedEmptyValues();
if (n < 0 || n >= v.count)
return sharedEmptyValues();
return v.argResult[n][index];
}
Value OptionResult::value(int index, int n) const {
auto &v = *reinterpret_cast<const OptionData *>(data);
if (index < 0 || index >= v.argSize)
return {};
if (n < 0 || n >= v.count)
return v.option->d_func()->arguments[index].defaultValue();
const auto &args = v.argResult[n][index];
return args.empty() ? Value() : args.front();
}
std::string ParseResultPrivate::correctionText() const {
std::vector<std::string> expectedValues;
switch (error) {
case ParseResult::UnknownOption: {
for (int i = 0; i < core.allOptionsSize; ++i) {
const auto &opt = *core.allOptionsResult[i].option;
for (const auto &token : opt.d_func()->tokens) {
expectedValues.push_back(token);
}
}
break;
}
case ParseResult::InvalidArgumentValue: {
auto d = errorArgument->d_func();
for (const auto &item : d->expectedValues) {
expectedValues.push_back(item.toString());
}
break;
}
case ParseResult::UnknownCommand: {
for (const auto &cmd : command->d_func()->commands) {
expectedValues.push_back(cmd.name());
}
break;
}
default:
return {};
}
auto input = errorPlaceholders[0];
auto suggestions = Utils::calcClosestTexts(expectedValues, input, int(input.size()) / 2);
if (suggestions.empty())
return {};
std::string ss;
const auto &parserData = parser.d_func();
ss += Utils::formatText(
parserData->textProvider(Strings::Information, Strings::MatchCommand), {input});
for (const auto &item : std::as_const(suggestions)) {
ss += "\n" + parserData->indent() + item;
}
return ss;
}
std::vector<Option> ParseResultPrivate::globalOptions() const {
std::vector<Option> res;
res.reserve(core.globalOptionsSize);
for (int i = 0; i < core.globalOptionsSize; ++i) {
res.push_back(*core.allOptionsResult[i].option);
}
return res;
}
void ParseResultPrivate::showMessage(const std::string &info, const std::string &warn,
const std::string &err, bool isMsg) const {
// Make it as a POD structure
struct Lists {
HelpLayout::List *data;
int size;
};
// Avoid using `std::function` to reduce binary size
const auto &getLists = [](int displayOptions, const GenericMap &catalog,
const StringList &catalogNames, // catalog
const GenericMap &symbolIndexes, // name -> index
int symbolCount, const Symbol *(*getter)(int, const void *),
const void *user, // get symbol from index
std::string (*getName)(const Symbol *), // get name of symbol
int *maxWidth, void *extra,
const std::string &defaultTitle) -> Lists {
Lists res;
int index = 0;
res.data = new HelpLayout::List[catalogNames.size() + 1];
// symbol indexes
std::vector<int> visitedIndexes(symbolCount);
// catalogues
for (const auto &catalogName : catalogNames) {
auto &list = res.data[index];
list.title = catalogName;
auto &symbolNames = *catalog.find(catalogName)->second.sl;
bool empty = true;
for (const auto &name : symbolNames) {
auto it = symbolIndexes.find(name);
if (it == symbolIndexes.end())
continue;
auto idx = it->second.i;
const auto &sym = getter(idx, user);
auto first = sym->helpText(Symbol::HP_FirstColumn, displayOptions, extra);
auto second = sym->helpText(Symbol::HP_SecondColumn, displayOptions, extra);
*maxWidth = std::max(int(first.size()), *maxWidth);
list.firstColumn.emplace_back(std::move(first));
list.secondColumn.emplace_back(std::move(second));
visitedIndexes[idx] = 1;
empty = false;
}
if (!empty)
index++; // index inc
}
// rest
{
auto &list = res.data[index];
list.title = defaultTitle;
bool empty = true;
for (int i = 0; i < symbolCount; ++i) {
if (visitedIndexes[i])
continue;
const auto &sym = getter(i, user);
auto first = sym->helpText(Symbol::HP_FirstColumn, displayOptions, extra);
auto second = sym->helpText(Symbol::HP_SecondColumn, displayOptions, extra);
*maxWidth = std::max(int(first.size()), *maxWidth);
list.firstColumn.emplace_back(std::move(first));
list.secondColumn.emplace_back(std::move(second));
empty = false;
}
if (!empty)
index++; // index inc
}
res.size = index;
return res;
};
const auto &d = command->d_func();
const auto &parserData = parser.d_func();
const auto &catalogueData = d->catalogue.d_func();
const auto displayOptions = parserData->displayOptions;
bool noHelp = isMsg && (displayOptions & Parser::DontShowHelpOnError);
bool noIntro = isMsg && (displayOptions & Parser::DontShowIntroOnError);
int maxWidth = 0;
auto reorderedOptions = OptionPrivate::reorderOptions(d->options, globalOptions());
// Alloc
Lists argLists =
noHelp ? Lists{nullptr, 0}
: getLists(
displayOptions, catalogueData->arg.data, catalogueData->arguments,
core.argNameIndexes, int(d->arguments.size()),
[](int i, const void *user) -> const Symbol * {
return &reinterpret_cast<decltype(d)>(user)->arguments[i]; //
},
d,
[](const Symbol *s) {
return static_cast<const Argument *>(s)->name(); //
},
&maxWidth, reinterpret_cast<void *>(parserData->textProvider),
parserData->textProvider(Strings::Title, Strings::Arguments));
Lists optLists =
noHelp
? Lists{nullptr, 0}
: getLists(
displayOptions, catalogueData->opt.data, catalogueData->options,
core.allOptionTokenIndexes, int(core.allOptionsSize),
[](int i, const void *user) -> const Symbol * {
return &(*reinterpret_cast<const decltype(reorderedOptions) *>(user))[i];
},
&reorderedOptions,
[](const Symbol *s) {
return static_cast<const Option *>(s)->token(); //
},
&maxWidth, reinterpret_cast<void *>(parserData->textProvider),
parserData->textProvider(Strings::Title, Strings::Options));
Lists cmdLists = noHelp
? Lists{nullptr, 0}
: getLists(
displayOptions, catalogueData->cmd.data, catalogueData->commands,
core.cmdNameIndexes, int(d->commands.size()),
[](int i, const void *user) -> const Symbol * {
return &reinterpret_cast<decltype(d)>(user)->commands[i]; //
},
d,
[](const Symbol *s) {
return static_cast<const Command *>(s)->name(); //
},
&maxWidth, reinterpret_cast<void *>(parserData->textProvider),
parserData->textProvider(Strings::Title, Strings::Commands));
// Get valid help layout
const HelpLayoutPrivate *helpLayoutData = nullptr;
{
int cmdCnt = stack.size() + 1;
auto cmds = new const Command *[cmdCnt]; // alloc
auto cmd = &parserData->rootCommand;
cmds[0] = cmd;
int cmdIdx = 0;
for (const auto &idx : std::as_const(stack)) {
cmd = &cmd->d_func()->commands[idx];
cmds[++cmdIdx] = cmd;
}
for (int i = cmdCnt - 1; i >= 0; --i) {
auto curHelpLayoutData = cmds[i]->d_func()->helpLayout.d_func();
if (!curHelpLayoutData->itemDataList.empty()) {
helpLayoutData = curHelpLayoutData;
break;
}
}
delete[] cmds; // free
if (!helpLayoutData) {
static HelpLayout defaultHelpLayout = HelpLayout::defaultHelpLayout();
helpLayoutData = defaultHelpLayout.d_func();
}
}
if ((displayOptions & Parser::AlignAllCatalogues)) {
for (const auto &helpItem : helpLayoutData->itemDataList) {
if (helpItem.itemType != HelpLayoutPrivate::UserHelpList) {
continue;
}
for (const auto &item : helpItem.list.firstColumn) {
maxWidth = std::max(int(item.size()), maxWidth);
}
}
} else {
maxWidth = 0;
}
const auto &cmdDesc = d->detailedDescription.empty() ? d->desc : d->detailedDescription;
// Get last
int last = int(helpLayoutData->itemDataList.size()) - 1;
for (int i = last; i >= 0; --i) {
const auto &item = helpLayoutData->itemDataList[i];
bool empty = true;
switch (item.itemType) {
case HelpLayoutPrivate::HelpText: {
switch (static_cast<HelpLayout::HelpTextItem>(item.index)) {
case HelpLayout::HT_Prologue: {
if (noIntro)
break;
empty = parserData->prologue.empty();
break;
}
case HelpLayout::HT_Epilogue: {
if (noIntro)
break;
empty = parserData->epilogue.empty();
break;
}
case HelpLayout::HT_Description: {
if (noHelp)
break;
empty = cmdDesc.empty();
break;
}
case HelpLayout::HT_Usage: {
if (noHelp)
break;
empty = false;
break;
}
}
break;
}
case HelpLayoutPrivate::HelpList: {
if (noHelp)
break;
switch (static_cast<HelpLayout::HelpListItem>(item.index)) {
case HelpLayout::HL_Arguments: {
empty = argLists.size == 0;
break;
}
case HelpLayout::HL_Options: {
empty = optLists.size == 0;
break;
}
case HelpLayout::HL_Commands: {
empty = cmdLists.size == 0;
break;
}
}
break;
}
case HelpLayoutPrivate::Message: {
switch (static_cast<HelpLayout::MessageItem>(item.index)) {
case HelpLayout::MI_Information: {
empty = info.empty();
break;
}
case HelpLayout::MI_Warning: {
empty = warn.empty();
break;
}
case HelpLayout::MI_Critical: {
empty = err.empty();
break;
}
}
break;
}
case HelpLayoutPrivate::UserHelpText: {
if (noHelp)
break;
empty = item.text.lines.empty();
break;
}
case HelpLayoutPrivate::UserHelpList: {
if (noHelp)
break;
empty = item.list.firstColumn.empty();
break;
}
case HelpLayoutPrivate::UserIntro: {
if (noIntro)
break;
empty = false;
break;
}
}
if (!empty) {
last = i;
break;
}
}
// Output
for (int i = 0; i <= last; ++i) {
const auto &item = helpLayoutData->itemDataList[i];
HelpLayout::Context ctx;
ctx.parser = &parser;
bool hasNext = i < last;
ctx.hasNext = hasNext;
switch (item.itemType) {
case HelpLayoutPrivate::HelpText: {
HelpLayout::Text text;
ctx.text = &text;
switch (static_cast<HelpLayout::HelpTextItem>(item.index)) {
case HelpLayout::HT_Prologue: {
if (noIntro)
break;
text.lines = parserData->prologue;
break;
}
case HelpLayout::HT_Epilogue: {
if (noIntro)
break;
text.lines = parserData->epilogue;
break;
}
case HelpLayout::HT_Description: {
if (noHelp)
break;
text.title =
parserData->textProvider(Strings::Title, Strings::Description);
text.lines = cmdDesc;
break;
}
case HelpLayout::HT_Usage: {
if (noHelp)
break;
std::vector<Option> allOptions;
allOptions.reserve(core.globalOptionsSize);
for (int j = 0; j < core.globalOptionsSize; ++j) {
allOptions.emplace_back(*core.allOptionsResult[j].option);
}
{
// get parent names
auto p = &parserData->rootCommand;
for (int index : stack) {
text.lines = p->name() + " ";
p = &p->d_func()->commands[index];
}
}
void *a[2] = {
&allOptions,
reinterpret_cast<void *>(parserData->textProvider),
};
text.title = parserData->textProvider(Strings::Title, Strings::Usage);
text.lines += command->helpText(Symbol::HP_Usage, displayOptions, a);
break;
}
}
item.out(ctx);
break;
}
case HelpLayoutPrivate::HelpList: {
if (noHelp)
break;
const auto &listHasNext = [](int j, const Lists &lists) {
return j < lists.size - 1;
};
ctx.firstColumnLength = maxWidth;
switch (static_cast<HelpLayout::HelpListItem>(item.index)) {
case HelpLayout::HL_Arguments: {
for (int j = 0; j < argLists.size; ++j) {
ctx.hasNext = hasNext || listHasNext(j, argLists);
ctx.list = &argLists.data[j];
item.out(ctx);
}
break;
}
case HelpLayout::HL_Options: {
for (int j = 0; j < optLists.size; ++j) {
ctx.hasNext = hasNext || listHasNext(j, optLists);
ctx.list = &optLists.data[j];
item.out(ctx);
}
break;
}
case HelpLayout::HL_Commands: {
for (int j = 0; j < cmdLists.size; ++j) {
ctx.hasNext = hasNext || listHasNext(j, cmdLists);
ctx.list = &cmdLists.data[j];
item.out(ctx);
}
break;
}
}
break;
}
case HelpLayoutPrivate::Message: {
HelpLayout::Text text;
ctx.text = &text;
switch (static_cast<HelpLayout::MessageItem>(item.index)) {
case HelpLayout::MI_Information: {
text.lines = info;
break;
}
case HelpLayout::MI_Warning: {
text.lines = warn;
break;
}
case HelpLayout::MI_Critical: {
text.lines = err;
break;
}
}
item.out(ctx);
break;
}
case HelpLayoutPrivate::UserHelpText: {
if (noHelp)
break;
ctx.text = &item.text;
item.out(ctx);
break;
}
case HelpLayoutPrivate::UserHelpList: {
if (noHelp)
break;
ctx.list = &item.list;
ctx.firstColumnLength = maxWidth;
item.out(ctx);
break;
}
case HelpLayoutPrivate::UserIntro: {
if (noHelp)
break;
item.out(ctx);
break;
}
}
}
// Free
delete[] argLists.data;
delete[] optLists.data;
delete[] cmdLists.data;
}
ParseResult::ParseResult() : SharedBase(nullptr) {
}
Command ParseResult::rootCommand() const {
Q_D2(ParseResult);
return d->parser.rootCommand();
}
const std::vector<std::string> &ParseResult::arguments() const {
Q_D2(ParseResult);
return d->arguments;
}
int ParseResult::invoke(int errCode) const {
Q_D2(ParseResult);
if (d->error != NoError) {
showError();
return errCode;
}
return dispatch();
}
int ParseResult::dispatch() const {
Q_D2(ParseResult);
if (d->error != NoError) {
throw std::runtime_error("don't call dispatch when parsing fails");
}
const auto &cmdData = d->command->d_func();
const auto &handler = cmdData->handler;
// If version is empty, you should do something in the handler
if (d->roleSet[Option::Version] && !cmdData->version.empty()) {
u8info("%s\n", cmdData->version.data());
return 0;
}
if (d->roleSet[Option::Help]) {
showHelpText();
return 0;
}
if (!handler) {
throw std::runtime_error(
Utils::formatText(R"(command "%1" doesn't have a valid handler)", {
cmdData->name,
}));
}
return handler(*this);
}
ParseResult::Error ParseResult::error() const {
Q_D2(ParseResult);
return d->error;
}
std::string ParseResult::errorText() const {
Q_D2(ParseResult);
if (d->error == NoError)
return {};
return Utils::formatText(d->parser.d_func()->textProvider(Strings::ParseError, d->error),
d->errorPlaceholders);
}
std::string ParseResult::correctionText() const {
Q_D2(ParseResult);
return d->correctionText();
}
std::string ParseResult::cancellationToken() const {
Q_D2(ParseResult);
return d->cancellationToken;
}
Command ParseResult::command() const {
Q_D2(ParseResult);
return *d->command;
}
std::vector<Option> ParseResult::globalOptions() const {
Q_D2(ParseResult);
return d->globalOptions();
}
std::vector<int> ParseResult::commandIndexStack() const {
Q_D2(ParseResult);
return d->stack;
}
int ParseResult::indexOfArgument(const std::string &name) const {
Q_D2(ParseResult);
auto it = d->core.argNameIndexes.find(name);
if (it == d->core.argNameIndexes.end())
return -1;
return it->second.i;
}
int ParseResult::indexOfOption(const std::string &token) const {
Q_D2(ParseResult);
auto it = d->core.allOptionTokenIndexes.find(token);
if (it == d->core.allOptionTokenIndexes.end())
return -1;
return it->second.i;
}
void ParseResult::showError() const {
Q_D2(ParseResult);
if (d->error == NoError)
return;
const auto &parserData = d->parser.d_func();
const auto &displayOptions = parserData->displayOptions;
d->showMessage(
(displayOptions & Parser::SkipCorrection) ? std::string() : d->correctionText(), {},
parserData->textProvider(Strings::Title, Strings::Error) + ": " + errorText(), true);
}
void ParseResult::showHelpText() const {
Q_D2(ParseResult);
d->showMessage({}, {}, {});
}
void ParseResult::showMessage(const std::string &info, const std::string &warn,
const std::string &err) const {
Q_D2(ParseResult);
const auto &displayOptions = d->parser.d_func()->displayOptions;
d->showMessage(info, warn, err, true);
}
bool ParseResult::isRoleSet(Option::Role role) const {
Q_D2(ParseResult);
return d->roleSet[role];
}
const std::vector<Value> &ParseResult::values(int index) const {
Q_D2(ParseResult);
if (index < 0 || index >= d->core.argSize)
return sharedEmptyValues();
return d->core.argResult[index];
}
Value ParseResult::value(int index) const {
Q_D2(ParseResult);
if (index < 0 || index >= d->core.argSize)
return {};
const auto &args = d->core.argResult[index];
return args.empty() ? d->command->d_func()->arguments[index].defaultValue() : args.front();
}
OptionResult ParseResult::option(int index) const {
Q_D2(ParseResult);
if (index < 0 || index >= d->core.allOptionsSize)
return {};
return {&d->core.allOptionsResult[index]};
}
ParseResult::ParseResult(ParseResultPrivate *d) : SharedBase(d) {
}
}

View File

@@ -0,0 +1,100 @@
#ifndef PARSERESULT_P_H
#define PARSERESULT_P_H
#include "sharedbase_p.h"
#include "parser.h"
#include "map_p.h"
namespace SysCmdLine {
struct ArgumentHolderData {
int optionalArgIndex;
int multiValueArgIndex;
GenericMap argNameIndexes; // name -> index of argument
int argSize; // equal to `argumentCount()`
ArgumentHolderData() : optionalArgIndex(-1), multiValueArgIndex(-1), argSize(0) {
}
};
struct OptionData : public ArgumentHolderData {
const Option *option; // MUST BE SET
std::vector<Value> **argResult; // first: occurrence, second: arg index
int count; // occurrence times
OptionData() : option(nullptr), argResult(nullptr), count(0) {
}
~OptionData() {
for (int i = 0; i < count; ++i) {
delete[] argResult[i];
}
delete[] argResult;
}
static const OptionData &sharedNull();
};
struct ParseResultData2 : public ArgumentHolderData {
std::vector<Value> *argResult; // arg result
OptionData *allOptionsResult; // option result
int allOptionsSize; // command option count + global option count
int globalOptionsSize; // global option count
GenericMap allOptionTokenIndexes; // token -> index of `allOptionsResult`
GenericMap cmdNameIndexes; // name -> index of command
~ParseResultData2() {
delete[] allOptionsResult;
delete[] argResult;
}
};
class ParseResultPrivate : public SharedBasePrivate {
public:
ParseResultPrivate()
: error(ParseResult::NoError), errorOption(nullptr), errorArgument(nullptr) {
}
ParseResultPrivate(const ParseResultPrivate &) = delete;
ParseResultPrivate &operator=(const ParseResultPrivate &) = delete;
// This is a read-only class, we can store the pointers pointing to
// its own data, and we don't need to implement the clone method
SharedBasePrivate *clone() const {
return nullptr;
}
// parse information
Parser parser;
std::vector<std::string> arguments;
// error related
ParseResult::Error error;
std::vector<std::string> errorPlaceholders;
const Option *errorOption;
const Argument *errorArgument;
std::string cancellationToken;
// success results
std::vector<int> stack;
const Command *command; // MUST BE SET
ParseResultData2 core;
bool roleSet[5] = {};
std::string correctionText() const;
std::vector<Option> globalOptions() const;
// isMsg: mainly to display message rather than help
void showMessage(const std::string &info, const std::string &catalogNames,
const std::string &symbolIndexes, bool isMsg = false) const;
};
}
#endif // PARSERESULT_P_H

View File

@@ -0,0 +1,50 @@
#include "sharedbase.h"
#include "sharedbase_p.h"
namespace SysCmdLine {
// static int g_cnt;
SharedBasePrivate::SharedBasePrivate() : ref(1) {
// printf("construct %d\n", ++g_cnt);
}
SharedBasePrivate::~SharedBasePrivate() {
// printf("destruct %d\n", --g_cnt);
}
SharedBase::~SharedBase() {
ref_deref(d_ptr);
}
SharedBase::SharedBase(const SharedBase &other) : d_ptr(other.d_ptr) {
ref_ref(d_ptr);
}
SharedBase &SharedBase::operator=(const SharedBase &other) {
if (this == &other) {
return *this;
}
ref_assign(d_ptr, other.d_ptr);
return *this;
}
bool SharedBase::isDetached() const {
Q_D2(SharedBase);
return d && d->ref.load() == 1;
}
void SharedBase::detach() {
auto &d = d_ptr;
if (d->ref.load() != 1) {
auto x = d->clone();
if (d->ref.fetch_sub(1) == 1) // atomicity
delete d;
d = x;
}
}
SharedBase::SharedBase(SharedBasePrivate *d) : d_ptr(d) {
}
}

View File

@@ -0,0 +1,51 @@
#ifndef SHAREDBASE_P_H
#define SHAREDBASE_P_H
#include <atomic>
namespace SysCmdLine {
class SharedBasePrivate {
public:
SharedBasePrivate();
virtual ~SharedBasePrivate();
virtual SharedBasePrivate *clone() const = 0;
// Declare basic copy constructor
SharedBasePrivate(const SharedBasePrivate &) : ref(0) {
}
SharedBasePrivate &operator=(const SharedBasePrivate &) = delete;
std::atomic_int ref;
};
inline void ref_ref(SharedBasePrivate *d) {
if (d)
d->ref.fetch_add(1);
}
inline void ref_deref(SharedBasePrivate *d) {
if (d && d->ref.fetch_sub(1) == 1)
delete d;
}
inline void ref_assign(SharedBasePrivate *&d, SharedBasePrivate *d2) {
if (d2)
d2->ref.fetch_add(1);
if (d && d->ref.fetch_sub(1) == 1)
delete d;
d = d2;
}
#define Q_D(T) \
detach(); \
T##Private *const d = reinterpret_cast<T##Private *>(d_ptr)
#define Q_D2(T) \
const T##Private *const d = reinterpret_cast<const T##Private *>(d_ptr)
}
#endif // SHAREDBASE_P_H

View File

@@ -0,0 +1,65 @@
#include "symbol.h"
#include "symbol_p.h"
#include <utility>
namespace SysCmdLine {
SymbolPrivate::SymbolPrivate(Symbol::SymbolType type, std::string desc)
: type(type), desc(std::move(desc)) {
}
/*!
Returns the symbol type.
*/
Symbol::SymbolType Symbol::type() const {
Q_D2(Symbol);
return d->type;
}
/*!
Returns the symbol description.
*/
std::string Symbol::description() const {
Q_D2(Symbol);
return d->desc;
}
/*!
Sets the symbol description.
*/
void Symbol::setDescription(const std::string &desc) {
Q_D(Symbol);
d->desc = desc;
}
/*!
Returns the help provider.
*/
Symbol::HelpProvider Symbol::helpProvider() const {
Q_D2(Symbol);
return d->helpProvider;
}
/*!
Sets the help provider.
*/
void Symbol::setHelpProvider(const Symbol::HelpProvider &helpProvider) {
Q_D(Symbol);
d->helpProvider = helpProvider;
}
/*!
Returns the help text at given position with given display options.
*/
std::string Symbol::helpText(Symbol::HelpPosition pos, int displayOptions, void *extra) const {
Q_D2(Symbol);
if (d->helpProvider)
return d->helpProvider(this, pos, displayOptions, extra);
return {};
}
Symbol::Symbol(SymbolPrivate *d) : SharedBase(d) {
}
}

View File

@@ -0,0 +1,23 @@
#ifndef SYMBOL_P_H
#define SYMBOL_P_H
#include <atomic>
#include "symbol.h"
#include "sharedbase_p.h"
namespace SysCmdLine {
class SymbolPrivate : public SharedBasePrivate {
public:
SymbolPrivate(Symbol::SymbolType type, std::string desc);
public:
Symbol::SymbolType type;
std::string desc;
Symbol::HelpProvider helpProvider;
};
}
#endif // SYMBOL_P_H

View File

@@ -0,0 +1,520 @@
#include "system.h"
#include <algorithm>
#include <limits>
#include <mutex>
#include <cstring>
#include <cstdarg>
#ifdef _WIN32
# include <windows.h>
# include <shellapi.h>
# include "utils_p.h"
#else
# include <limits.h>
# include <sys/stat.h>
# include <utime.h>
# ifdef __APPLE__
# include <crt_externs.h>
# include <mach-o/dyld.h>
# else
# include <fstream>
# endif
# include <filesystem>
#endif
#ifdef _WIN32
# ifndef WC_ERR_INVALID_CHARS
# define WC_ERR_INVALID_CHARS 0x00000080
# endif
#endif
namespace SysCmdLine {
#ifdef _WIN32
static std::wstring winGetFullModuleFileName(HMODULE hModule) {
// Use stack buffer for the first try
wchar_t stackBuf[MAX_PATH + 1];
// Call
wchar_t *buf = stackBuf;
auto size = ::GetModuleFileNameW(nullptr, buf, MAX_PATH);
if (size == 0) {
return {};
}
if (size > MAX_PATH) {
// Re-alloc
buf = new wchar_t[size + 1]; // The return size doesn't contain the terminating 0
// Call
if (::GetModuleFileNameW(nullptr, buf, size) == 0) {
delete[] buf;
return {};
}
}
std::replace(buf, buf + size, L'\\', L'/');
// Return
std::wstring res(buf);
if (buf != stackBuf) {
delete[] buf;
}
return res;
}
#elif defined(__APPLE__)
static std::string macGetExecutablePath() {
// Use stack buffer for the first try
char stackBuf[PATH_MAX + 1];
// "_NSGetExecutablePath" will return "-1" if the buffer is not large enough
// and "*bufferSize" will be set to the size required.
// Call
unsigned int size = PATH_MAX + 1;
char *buf = stackBuf;
if (_NSGetExecutablePath(buf, &size) != 0) {
// Re-alloc
buf = new char[size]; // The return size contains the terminating 0
// Call
if (_NSGetExecutablePath(buf, &size) != 0) {
delete[] buf;
return {};
}
}
// Return
std::string res(buf);
if (buf != stackBuf) {
delete[] buf;
}
return res;
}
#endif
/*!
Returns the wide string converted from UTF-8 multi-byte string.
*/
std::string wideToUtf8(const std::wstring &s) {
#ifdef _WIN32
if (s.empty()) {
return {};
}
const auto utf16Length = static_cast<int>(s.size());
if (utf16Length >= (std::numeric_limits<int>::max)()) {
return {};
}
const int utf8Length = ::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s.data(),
utf16Length, nullptr, 0, nullptr, nullptr);
if (utf8Length <= 0) {
return {};
}
std::string utf8Str;
utf8Str.resize(utf8Length);
::WideCharToMultiByte(CP_UTF8, WC_ERR_INVALID_CHARS, s.data(), utf16Length, utf8Str.data(),
utf8Length, nullptr, nullptr);
return utf8Str;
#else
return std::filesystem::path(s).string();
#endif
}
/*!
Returns the UTF-8 multi-byte string converted from wide string.
*/
std::wstring utf8ToWide(const std::string &s) {
#ifdef _WIN32
if (s.empty()) {
return {};
}
const auto utf8Length = static_cast<int>(s.size());
if (utf8Length >= (std::numeric_limits<int>::max)()) {
return {};
}
const int utf16Length =
::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), utf8Length, nullptr, 0);
if (utf16Length <= 0) {
return {};
}
std::wstring utf16Str;
utf16Str.resize(utf16Length);
::MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, s.data(), utf8Length, utf16Str.data(),
utf16Length);
return utf16Str;
#else
return std::filesystem::path(s).wstring();
#endif
}
#ifdef _WIN32
/*!
Returns the UTF-8 multi-byte string converted from ANSI string.
*/
std::string ansiToUtf8(const std::string &s) {
if (s.empty()) {
return {};
}
const auto ansiLength = static_cast<int>(s.size());
if (ansiLength >= (std::numeric_limits<int>::max)()) {
return {};
}
const int utf16Length =
::MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, s.data(), ansiLength, nullptr, 0);
if (utf16Length <= 0) {
return {};
}
std::wstring utf16Str;
utf16Str.resize(utf16Length);
::MultiByteToWideChar(CP_ACP, MB_ERR_INVALID_CHARS, s.data(), ansiLength, utf16Str.data(),
utf16Length);
return wideToUtf8(utf16Str);
}
#endif
/*!
Returns the application file path, the path separator of which is <tt>/</tt>.
*/
std::string appPath() {
static const auto res = []() -> std::string {
#ifdef _WIN32
return wideToUtf8(winGetFullModuleFileName(nullptr));
#elif defined(__APPLE__)
return macGetExecutablePath();
#else
char buf[PATH_MAX];
if (!realpath("/proc/self/exe", buf)) {
return {};
}
return buf;
#endif
}();
return res;
}
/*!
Returns the application directory path, the path separator of which is <tt>/</tt>.
*/
std::string appDirectory() {
auto appDir = appPath();
auto slashIdx = appDir.find_last_of('/');
if (slashIdx != std::string::npos) {
appDir = appDir.substr(0, slashIdx);
}
return appDir;
}
/*!
Returns the application file name.
*/
std::string appFileName() {
auto appName = appPath();
auto slashIdx = appName.find_last_of('/');
if (slashIdx != std::string::npos) {
appName = appName.substr(slashIdx + 1);
}
return appName;
}
/*!
Returns the application name. On Windows, the file extension will be stripped.
*/
std::string appName() {
auto appName = appFileName();
#ifdef _WIN32
auto dotIdx = appName.find_last_of('.');
if (dotIdx != std::string::npos) {
std::string suffix = Utils::toLower(appName.substr(dotIdx + 1));
if (suffix == "exe") {
appName = appName.substr(0, dotIdx);
}
}
#endif
return appName;
}
/*!
Returns the command line argument list in UTF-8.
*/
std::vector<std::string> commandLineArguments() {
std::vector<std::string> res;
#ifdef _WIN32
int argc;
auto argvW = ::CommandLineToArgvW(::GetCommandLineW(), &argc);
if (argvW == nullptr)
return {};
res.reserve(argc);
for (int i = 0; i != argc; ++i) {
res.push_back(wideToUtf8(argvW[i]));
}
::LocalFree(argvW);
#elif defined(__APPLE__)
auto argv = *(_NSGetArgv());
for (int i = 0; argv[i] != nullptr; ++i) {
res.push_back(argv[i]);
}
#else
std::ifstream file("/proc/self/cmdline", std::ios::in);
if (file.fail())
return {};
std::string s;
while (std::getline(file, s, '\0')) {
res.push_back(s);
}
file.close();
#endif
return res;
}
class PrintScopeGuard {
public:
static std::mutex &global_mtx() {
static std::mutex _instance;
return _instance;
}
explicit PrintScopeGuard(int foreground, int background)
: consoleChanged(!(foreground == DefaultColor && background == DefaultColor)) {
global_mtx().lock();
#ifdef _WIN32
_codepage = ::GetConsoleOutputCP();
::SetConsoleOutputCP(CP_UTF8);
if (consoleChanged) {
WORD winColor = 0;
if (foreground != DefaultColor) {
winColor |= (foreground & Intensified) ? FOREGROUND_INTENSITY : 0;
switch (foreground & 0xF) {
case Red:
winColor |= FOREGROUND_RED;
break;
case Green:
winColor |= FOREGROUND_GREEN;
break;
case Blue:
winColor |= FOREGROUND_BLUE;
break;
case Yellow:
winColor |= FOREGROUND_RED | FOREGROUND_GREEN;
break;
case Purple:
winColor |= FOREGROUND_RED | FOREGROUND_BLUE;
break;
case Cyan:
winColor |= FOREGROUND_GREEN | FOREGROUND_BLUE;
break;
case White:
winColor |= FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
default:
break;
}
}
if (background != DefaultColor) {
winColor |= (background & Intensified) ? BACKGROUND_INTENSITY : 0;
switch (background & 0xF) {
case Red:
winColor |= BACKGROUND_RED;
break;
case Green:
winColor |= BACKGROUND_GREEN;
break;
case Blue:
winColor |= BACKGROUND_BLUE;
break;
case Yellow:
winColor |= BACKGROUND_RED | BACKGROUND_GREEN;
break;
case Purple:
winColor |= BACKGROUND_RED | BACKGROUND_BLUE;
break;
case Cyan:
winColor |= BACKGROUND_GREEN | BACKGROUND_BLUE;
break;
case White:
winColor |= BACKGROUND_RED | BACKGROUND_GREEN | BACKGROUND_BLUE;
default:
break;
}
}
_hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
GetConsoleScreenBufferInfo(_hConsole, &_csbi);
SetConsoleTextAttribute(_hConsole, winColor);
}
#else
if (consoleChanged) {
const char *strList[3];
int strListSize = 0;
if (foreground != DefaultColor) {
bool light = foreground & Intensified;
const char *colorStr = nullptr;
switch (foreground & 0xF) {
case Red:
colorStr = light ? "91" : "31";
break;
case Green:
colorStr = light ? "92" : "32";
break;
case Blue:
colorStr = light ? "94" : "34";
break;
case Yellow:
colorStr = light ? "93" : "33";
break;
case Purple:
colorStr = light ? "95" : "35";
break;
case Cyan:
colorStr = light ? "96" : "36";
break;
case White:
colorStr = light ? "97" : "37";
break;
default:
break;
}
if (colorStr) {
strList[strListSize] = colorStr;
strListSize++;
}
}
if (background != DefaultColor) {
bool light = background & Intensified;
const char *colorStr = nullptr;
switch (background & 0xF) {
case Red:
colorStr = light ? "101" : "41";
break;
case Green:
colorStr = light ? "102" : "42";
break;
case Blue:
colorStr = light ? "104" : "44";
break;
case Yellow:
colorStr = light ? "103" : "43";
break;
case Purple:
colorStr = light ? "105" : "45";
break;
case Cyan:
colorStr = light ? "106" : "46";
break;
case White:
colorStr = light ? "107" : "47";
break;
default:
break;
}
if (colorStr) {
strList[strListSize] = colorStr;
strListSize++;
}
}
if (strListSize > 0) {
char buf[20];
int bufSize = 0;
auto buf_puts = [&buf, &bufSize](const char *s) {
auto len = strlen(s);
for (; *s != '\0'; ++s) {
buf[bufSize++] = *s;
}
};
buf_puts("\033[");
for (int i = 0; i < strListSize - 1; ++i) {
buf_puts(strList[i]);
buf_puts(";");
}
buf_puts(strList[strListSize - 1]);
buf_puts("m");
buf[bufSize] = '\0';
printf("%s", buf);
}
}
#endif
}
~PrintScopeGuard() {
#ifdef _WIN32
::SetConsoleOutputCP(_codepage);
if (consoleChanged) {
SetConsoleTextAttribute(_hConsole, _csbi.wAttributes);
}
#else
if (consoleChanged) {
// ANSI escape code to reset text color to default
const char *resetColor = "\033[0m";
printf("%s", resetColor);
}
#endif
global_mtx().unlock();
}
private:
bool consoleChanged;
#ifdef _WIN32
UINT _codepage;
HANDLE _hConsole;
CONSOLE_SCREEN_BUFFER_INFO _csbi;
#endif
};
int u8printf(int foreground, int background, const char *fmt, ...) {
PrintScopeGuard _guard(foreground, background);
va_list args;
va_start(args, fmt);
int res = std::vprintf(fmt, args);
va_end(args);
return res;
}
int u8vprintf(int foreground, int background, const char *fmt, va_list args) {
PrintScopeGuard _guard(foreground, background);
return std::vprintf(fmt, args);
}
/*!
Prints the formatted text in UTF-8 with given message type.
*/
int u8debug(MessageType messageType, bool highlight, const char *fmt, ...) {
int color;
switch (messageType) {
case MT_Message:
color = White;
break;
case MT_Healthy:
color = Green;
break;
case MT_Warning:
color = Yellow;
break;
case MT_Critical:
color = Red;
break;
default:
color = DefaultColor;
break;
}
if (highlight) {
color |= Intensified;
}
va_list args;
va_start(args, fmt);
int res = u8vprintf(color, DefaultColor, fmt, args);
va_end(args);
return res;
}
int u8info(const char *fmt, ...) {
va_list args;
va_start(args, fmt);
int res = u8vprintf(DefaultColor, DefaultColor, fmt, args);
va_end(args);
return res;
}
}

View File

@@ -0,0 +1,104 @@
#include "utils_p.h"
#include <algorithm>
namespace SysCmdLine::Utils {
std::vector<std::string> split(const std::string &s, const std::string &delimiter) {
std::vector<std::string> tokens;
std::string::size_type start = 0;
std::string::size_type end = s.find(delimiter);
while (end != std::string::npos) {
tokens.push_back(s.substr(start, end - start));
start = end + delimiter.size();
end = s.find(delimiter, start);
}
tokens.push_back(s.substr(start));
return tokens;
}
std::string join(const std::vector<std::string> &v, const std::string &delimiter) {
if (v.empty())
return {};
std::string res;
for (int i = 0; i < v.size() - 1; ++i) {
res.append(v[i]);
res.append(delimiter);
}
res.append(v.back());
return res;
}
std::string formatText(const std::string &format, const std::vector<std::string> &args) {
std::string result = format;
for (size_t i = 0; i < args.size(); i++) {
std::string placeholder = "%" + std::to_string(i + 1);
size_t pos = result.find(placeholder);
while (pos != std::string::npos) {
result.replace(pos, placeholder.length(), args[i]);
pos = result.find(placeholder, pos + args[i].size());
}
}
return result;
}
static int levenshteinDistance(const std::string &s1, const std::string &s2) {
auto len1 = s1.size();
auto len2 = s2.size();
// Alloc
int **dp = new int *[len1 + 1];
for (int i = 0; i <= len1; i++) {
dp[i] = new int[len2 + 1];
}
for (int i = 0; i <= len1; i++) {
for (int j = 0; j <= len2; j++) {
if (i == 0) {
dp[i][j] = j;
} else if (j == 0) {
dp[i][j] = i;
} else if (s1[i - 1] == s2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = 1 + std::min({dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]});
}
}
}
int res = dp[len1][len2];
// Free
for (int i = 0; i <= len1; i++) {
delete[] dp[i];
}
delete[] dp;
return res;
}
std::vector<std::string> calcClosestTexts(const std::vector<std::string> &texts,
const std::string &input, int threshold) {
std::vector<std::string> suggestions;
for (const auto &cmd : texts) {
int distance = levenshteinDistance(input, cmd);
if (distance <= threshold) {
suggestions.push_back(cmd);
}
}
return suggestions;
}
std::string toUpper(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), ::toupper);
return s;
}
std::string toLower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
return s;
}
}

View File

@@ -0,0 +1,69 @@
#ifndef UTILS_H
#define UTILS_H
#include <cstring>
#include <string>
#include <vector>
namespace SysCmdLine::Utils {
std::vector<std::string> split(const std::string &s, const std::string &delimiter);
std::string join(const std::vector<std::string> &v, const std::string &delimiter);
std::string formatText(const std::string &format, const std::vector<std::string> &args);
std::vector<std::string> calcClosestTexts(const std::vector<std::string> &texts,
const std::string &input, int threshold);
std::string toUpper(std::string s);
std::string toLower(std::string s);
inline bool starts_with(const std::string_view &s, const std::string_view &prefix) {
#if __cplusplus >= 202002L
return s.starts_with(prefix);
#else
return s.size() >= prefix.size() && s.substr(0, prefix.size()) == prefix;
#endif
}
template <class Container, class T>
inline bool contains(const Container &container, const T &key) {
#if __cplusplus >= 202002L
return container.contains(key);
#else
return container.count(key) != 0;
#endif
}
template <class T>
std::vector<T> concatVector(const std::vector<T> &v1, const std::vector<T> &v2) {
std::vector<T> res;
res.reserve(v1.size() + v2.size());
for (const auto &item : v1)
res.emplace_back(item);
for (const auto &item : v2)
res.emplace_back(item);
return res;
}
template <class T>
std::basic_string<T> trim(const std::basic_string<T> &str) {
auto start = str.begin();
while (start != str.end() && std::isspace(*start)) {
start++;
}
auto end = str.end();
do {
end--;
} while (std::distance(start, end) > 0 && std::isspace(*end));
return {start, end + 1};
}
}
#endif // UTILS_H

View File

@@ -0,0 +1,360 @@
#include "value.h"
#include <algorithm>
#include "utils_p.h"
namespace SysCmdLine {
static constexpr const char True_Literal[] = "true";
static constexpr const char False_Literal[] = "false";
Value::Value(Value::Type type) : _type(type) {
switch (type) {
case Bool:
data.b = false;
break;
case Int:
data.i = 0;
break;
case Int64:
data.l = 0;
break;
case Double:
data.d = 0;
break;
case String:
data.s = new std::string();
break;
default:
break;
}
}
Value::~Value() {
if (_type == String)
delete data.s;
}
Value::Value(const Value &other) {
_type = other._type;
switch (_type) {
case Bool:
data.b = other.data.b;
break;
case Int:
data.i = other.data.i;
break;
case Int64:
data.l = other.data.l;
break;
case Double:
data.d = other.data.d;
break;
case String:
data.s = new std::string(*other.data.s);
break;
default:
break;
}
}
Value::Value(Value &&other) noexcept {
_type = other._type;
std::swap(data, other.data);
other._type = Null;
}
Value &Value::operator=(const Value &other) {
if (this == &other) {
return *this;
}
if (_type == String) {
delete data.s;
}
_type = other._type;
switch (_type) {
case Bool:
data.b = other.data.b;
break;
case Int:
data.i = other.data.i;
break;
case Int64:
data.l = other.data.l;
break;
case Double:
data.d = other.data.d;
break;
case String:
data.s = new std::string(*other.data.s);
break;
default:
break;
}
return *this;
}
Value &Value::operator=(Value &&other) noexcept {
if (this == &other) {
return *this;
}
std::swap(data, other.data);
std::swap(_type, other._type);
return *this;
}
bool Value::isEmpty() const {
switch (_type) {
case Bool:
return !data.b;
case Int:
return data.i == 0;
case Int64:
return data.l == 0;
case Double:
return data.d == 0;
case String:
return data.s->empty();
default:
break;
}
return true;
}
bool Value::toBool() const {
return _type == Bool && data.b;
}
int Value::toInt() const {
switch (_type) {
case Bool:
return data.b;
case Int:
return data.i;
case Int64:
return int(data.l);
case Double:
return int(data.d);
default:
break;
}
return 0;
}
int64_t Value::toInt64() const {
switch (_type) {
case Bool:
return data.b;
case Int:
return data.i;
case Int64:
return data.l;
case Double:
return int64_t(data.d);
default:
break;
}
return 0;
}
double Value::toDouble() const {
switch (_type) {
case Bool:
return data.b;
case Int:
return data.i;
case Int64:
return double(data.l);
case Double:
return data.d;
default:
break;
}
return 0;
}
std::string Value::toString() const {
switch (_type) {
case Bool:
return data.b ? True_Literal : False_Literal;
case Int:
return std::to_string(data.i);
case Int64:
return std::to_string(data.l);
case Double:
return std::to_string(data.d);
case String:
return *data.s;
default:
break;
}
return {};
}
bool Value::operator==(const Value &other) const {
if (_type != other._type)
return false;
switch (_type) {
case Bool:
return data.b == other.data.b;
case Int:
return data.i == other.data.i;
case Int64:
return data.l == other.data.l;
case Double:
return data.d == other.data.d;
case String:
return *data.s == *other.data.s;
default:
break;
}
return true;
}
bool Value::operator!=(const Value &other) const {
return !(*this == other);
}
static void determineBase(std::string &s, int &base, int &f) {
if (s.front() == '+') {
s = s.substr(1);
} else if (s.front() == '-') {
f = -1;
s = s.substr(1);
}
if (s.size() > 2 && s.front() == '0') {
switch (s.at(1)) {
case 'x':
case 'X':
s = s.substr(2);
base = 16;
break;
case 'b':
case 'B':
s = s.substr(2);
base = 2;
break;
case 'o':
case 'O':
s = s.substr(2);
base = 8;
break;
case 'd':
case 'D':
s = s.substr(2);
break;
default:
break;
}
}
}
Value Value::fromString(const std::string &s, Value::Type type) {
Value res;
switch (type) {
case Bool: {
if (Utils::toLower(s) == True_Literal) {
res = true;
} else if (Utils::toLower(s) == False_Literal) {
res = false;
}
break;
}
case Int: {
std::string::size_type idx;
try {
std::string s1 = s;
int base = 10;
int f = 1;
determineBase(s1, base, f);
res = std::stoi(s1, &idx, base) * f;
if (idx < s1.size()) {
res = {};
}
} catch (...) {
}
break;
}
case Int64: {
std::string::size_type idx;
try {
std::string s1 = s;
int base = 10;
int f = 1;
determineBase(s1, base, f);
res = (int64_t) std::stoll(s1, &idx, base) * f;
if (idx < s1.size()) {
res = {};
}
} catch (...) {
}
break;
}
case Double: {
std::string::size_type idx;
try {
res = std::stod(s, &idx);
if (idx < s.size()) {
res = {};
}
} catch (...) {
}
break;
}
case String: {
if (!s.empty()) {
res = s;
}
break;
}
default:
break;
}
return res;
}
std::vector<std::string> Value::toStringList(const std::vector<Value> &values) {
std::vector<std::string> res(values.size());
std::transform(values.begin(), values.end(), res.begin(), [](const Value &val) {
return val.toString(); //
});
return res;
}
const char *Value::typeName(Value::Type type) {
const char *expected;
switch (type) {
case Value::Bool: {
expected = "boolean";
break;
}
case Value::Int: {
expected = "int";
break;
}
case Value::Int64: {
expected = "long";
break;
}
case Value::Double: {
expected = "double";
break;
}
case Value::String: {
expected = "string";
break;
}
default:
expected = "null";
break;
}
return expected;
}
}

View File

@@ -0,0 +1,5 @@
@PACKAGE_INIT@
include(CMakeFindDependencyMacro)
include("${CMAKE_CURRENT_LIST_DIR}/syscmdlineTargets.cmake")

View File

@@ -0,0 +1 @@
add_subdirectory(basic)

View File

@@ -0,0 +1,7 @@
project(tst_basic)
file(GLOB _src *.h *.cpp)
add_executable(${PROJECT_NAME} ${_src})
target_link_libraries(${PROJECT_NAME} PRIVATE syscmdline)

Some files were not shown because too many files have changed in this diff Show More